mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 20:54:16 +01:00
chore: convert Dalamud to file-scoped namespaces
This commit is contained in:
parent
b093323acc
commit
987ff8dc8f
368 changed files with 55081 additions and 55450 deletions
|
|
@ -1,28 +1,27 @@
|
||||||
namespace Dalamud
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum describing the language the game loads in.
|
||||||
|
/// </summary>
|
||||||
|
public enum ClientLanguage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum describing the language the game loads in.
|
/// Indicating a Japanese game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ClientLanguage
|
Japanese,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicating a Japanese game client.
|
|
||||||
/// </summary>
|
|
||||||
Japanese,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating an English game client.
|
/// Indicating an English game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
English,
|
English,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating a German game client.
|
/// Indicating a German game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
German,
|
German,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicating a French game client.
|
/// Indicating a French game client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
French,
|
French,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,26 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public static class ClientLanguageExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension methods for the <see cref="ClientLanguage"/> class.
|
/// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ClientLanguageExtensions
|
/// <param name="language">Language to convert.</param>
|
||||||
|
/// <returns>Converted language.</returns>
|
||||||
|
public static Lumina.Data.Language ToLumina(this ClientLanguage language)
|
||||||
{
|
{
|
||||||
/// <summary>
|
return language switch
|
||||||
/// Converts a Dalamud ClientLanguage to the corresponding Lumina variant.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="language">Language to convert.</param>
|
|
||||||
/// <returns>Converted language.</returns>
|
|
||||||
public static Lumina.Data.Language ToLumina(this ClientLanguage language)
|
|
||||||
{
|
{
|
||||||
return language switch
|
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
||||||
{
|
ClientLanguage.English => Lumina.Data.Language.English,
|
||||||
ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
|
ClientLanguage.German => Lumina.Data.Language.German,
|
||||||
ClientLanguage.English => Lumina.Data.Language.English,
|
ClientLanguage.French => Lumina.Data.Language.French,
|
||||||
ClientLanguage.German => Lumina.Data.Language.German,
|
_ => throw new ArgumentOutOfRangeException(nameof(language)),
|
||||||
ClientLanguage.French => Lumina.Data.Language.French,
|
};
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(language)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
namespace Dalamud.Configuration
|
namespace Dalamud.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPluginConfiguration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration to store settings for a dalamud plugin.
|
/// Gets or sets configuration version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPluginConfiguration
|
int Version { get; set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets configuration version.
|
|
||||||
/// </summary>
|
|
||||||
int Version { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,368 +10,367 @@ using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal
|
namespace Dalamud.Configuration.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class containing Dalamud settings.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal sealed class DalamudConfiguration : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private static readonly JsonSerializerSettings SerializerSettings = new()
|
||||||
/// Class containing Dalamud settings.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
internal sealed class DalamudConfiguration : IServiceType
|
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerSettings SerializerSettings = new()
|
TypeNameHandling = TypeNameHandling.All,
|
||||||
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
|
Formatting = Formatting.Indented,
|
||||||
|
};
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private string configPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamudConfiguration">The current dalamud configuration.</param>
|
||||||
|
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that occurs when dalamud configuration is saved.
|
||||||
|
/// </summary>
|
||||||
|
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of muted works.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> BadWords { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||||
|
/// </summary>
|
||||||
|
public bool DutyFinderTaskbarFlash { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
|
||||||
|
/// </summary>
|
||||||
|
public bool DutyFinderChatMessage { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the language code to load Dalamud localization with.
|
||||||
|
/// </summary>
|
||||||
|
public string LanguageOverride { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
|
/// </summary>
|
||||||
|
public string LastVersion { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
|
/// </summary>
|
||||||
|
public string LastChangelogMajorMinor { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the chat type used by default for plugin messages.
|
||||||
|
/// </summary>
|
||||||
|
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
|
||||||
|
/// </summary>
|
||||||
|
public bool DoPluginTest { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key to opt into Dalamud staging builds.
|
||||||
|
/// </summary>
|
||||||
|
public string? DalamudBetaKey { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of custom repos.
|
||||||
|
/// </summary>
|
||||||
|
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of hidden plugins.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> HiddenPluginInternalName { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of seen plugins.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> SeenPluginInternalName { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
|
||||||
|
/// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder.
|
||||||
|
/// However by specifiying this value manually, you can add arbitrary files outside the normal
|
||||||
|
/// file paths.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
|
||||||
|
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
|
||||||
|
/// injected Dalamud instance.
|
||||||
|
/// </summary>
|
||||||
|
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the global UI scale.
|
||||||
|
/// </summary>
|
||||||
|
public float GlobalUiScale { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseAxisFontsFromGame { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
|
||||||
|
///
|
||||||
|
/// Before gamma is applied...
|
||||||
|
/// * ...TTF fonts loaded with stb or FreeType are in linear space.
|
||||||
|
/// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
|
||||||
|
/// </summary>
|
||||||
|
public float FontGammaLevel { get; set; } = 1.4f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
|
||||||
|
/// </summary>
|
||||||
|
public bool ToggleUiHide { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
|
||||||
|
/// </summary>
|
||||||
|
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
|
||||||
|
/// </summary>
|
||||||
|
public bool ToggleUiHideDuringGpose { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
|
||||||
|
/// </summary>
|
||||||
|
public bool PrintPluginsWelcomeMsg { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoUpdatePlugins { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
|
||||||
|
/// </summary>
|
||||||
|
public bool DoButtonsSystemMenu { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default Dalamud debug log level on startup.
|
||||||
|
/// </summary>
|
||||||
|
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to write to log files synchronously.
|
||||||
|
/// </summary>
|
||||||
|
public bool LogSynchronously { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
||||||
|
/// </summary>
|
||||||
|
public bool LogAutoScroll { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the debug log should open at startup.
|
||||||
|
/// </summary>
|
||||||
|
public bool LogOpenAtStartup { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
|
||||||
|
/// </summary>
|
||||||
|
public bool DevBarOpenAtStartup { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
|
||||||
|
/// </summary>
|
||||||
|
public bool AssertsEnabledAtStartup { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDocking { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether viewports should always be disabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisableViewport { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not focus management is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFocusManagementEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAntiAntiDebugEnabled { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to resume game main thread after plugins load.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsResumeGameAfterPluginLoad { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
||||||
|
/// </summary>
|
||||||
|
public string DalamudBetaKind { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
||||||
|
/// It is reset immediately when read.
|
||||||
|
/// </summary>
|
||||||
|
public bool PluginSafeMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating the wait time between plugin unload and plugin assembly unload.
|
||||||
|
/// Uses default value that may change between versions if set to null.
|
||||||
|
/// </summary>
|
||||||
|
public int? PluginWaitBeforeFree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of saved styles.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("SavedStyles")]
|
||||||
|
public List<StyleModelV1>? SavedStylesOld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of saved styles.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("SavedStylesVersioned")]
|
||||||
|
public List<StyleModel>? SavedStyles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the currently chosen style.
|
||||||
|
/// </summary>
|
||||||
|
public string ChosenStyle { get; set; } = "Dalamud Standard";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableRmtFiltering { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the order of DTR elements, by title.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? DtrOrder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of ignored DTR elements, by title.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? DtrIgnore { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the spacing used for DTR entries.
|
||||||
|
/// </summary>
|
||||||
|
public int DtrSpacing { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to swap the
|
||||||
|
/// direction in which elements are drawn in the DTR.
|
||||||
|
/// False indicates that elements will be drawn from the end of
|
||||||
|
/// the left side of the Server Info bar, and continue leftwards.
|
||||||
|
/// True indicates the opposite.
|
||||||
|
/// </summary>
|
||||||
|
public bool DtrSwapDirection { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the title screen menu is shown.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowTsm { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not market board data should be uploaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMbCollect { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language.
|
||||||
|
/// </summary>
|
||||||
|
public string EffectiveLanguage
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
TypeNameHandling = TypeNameHandling.All,
|
var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
|
||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
|
||||||
Formatting = Formatting.Indented,
|
|
||||||
};
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
private string configPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dalamudConfiguration">The current dalamud configuration.</param>
|
|
||||||
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that occurs when dalamud configuration is saved.
|
|
||||||
/// </summary>
|
|
||||||
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of muted works.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> BadWords { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
|
||||||
/// </summary>
|
|
||||||
public bool DutyFinderTaskbarFlash { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
|
|
||||||
/// </summary>
|
|
||||||
public bool DutyFinderChatMessage { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the language code to load Dalamud localization with.
|
|
||||||
/// </summary>
|
|
||||||
public string LanguageOverride { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
|
||||||
/// </summary>
|
|
||||||
public string LastVersion { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
|
||||||
/// </summary>
|
|
||||||
public string LastChangelogMajorMinor { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the chat type used by default for plugin messages.
|
|
||||||
/// </summary>
|
|
||||||
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
|
|
||||||
/// </summary>
|
|
||||||
public bool DoPluginTest { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a key to opt into Dalamud staging builds.
|
|
||||||
/// </summary>
|
|
||||||
public string? DalamudBetaKey { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of custom repos.
|
|
||||||
/// </summary>
|
|
||||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of hidden plugins.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> HiddenPluginInternalName { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of seen plugins.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> SeenPluginInternalName { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
|
|
||||||
/// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder.
|
|
||||||
/// However by specifiying this value manually, you can add arbitrary files outside the normal
|
|
||||||
/// file paths.
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
|
|
||||||
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
|
|
||||||
/// injected Dalamud instance.
|
|
||||||
/// </summary>
|
|
||||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the global UI scale.
|
|
||||||
/// </summary>
|
|
||||||
public float GlobalUiScale { get; set; } = 1.0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
|
|
||||||
/// </summary>
|
|
||||||
public bool UseAxisFontsFromGame { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
|
|
||||||
///
|
|
||||||
/// Before gamma is applied...
|
|
||||||
/// * ...TTF fonts loaded with stb or FreeType are in linear space.
|
|
||||||
/// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
|
|
||||||
/// </summary>
|
|
||||||
public float FontGammaLevel { get; set; } = 1.4f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
|
|
||||||
/// </summary>
|
|
||||||
public bool ToggleUiHide { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
|
|
||||||
/// </summary>
|
|
||||||
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
|
|
||||||
/// </summary>
|
|
||||||
public bool ToggleUiHideDuringGpose { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
|
|
||||||
/// </summary>
|
|
||||||
public bool PrintPluginsWelcomeMsg { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoUpdatePlugins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
|
|
||||||
/// </summary>
|
|
||||||
public bool DoButtonsSystemMenu { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the default Dalamud debug log level on startup.
|
|
||||||
/// </summary>
|
|
||||||
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to write to log files synchronously.
|
|
||||||
/// </summary>
|
|
||||||
public bool LogSynchronously { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
|
||||||
/// </summary>
|
|
||||||
public bool LogAutoScroll { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the debug log should open at startup.
|
|
||||||
/// </summary>
|
|
||||||
public bool LogOpenAtStartup { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
|
|
||||||
/// </summary>
|
|
||||||
public bool DevBarOpenAtStartup { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
|
|
||||||
/// </summary>
|
|
||||||
public bool AssertsEnabledAtStartup { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDocking { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether viewports should always be disabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDisableViewport { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not focus management is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsFocusManagementEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAntiAntiDebugEnabled { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to resume game main thread after plugins load.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsResumeGameAfterPluginLoad { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
|
||||||
/// </summary>
|
|
||||||
public string DalamudBetaKind { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
|
||||||
/// It is reset immediately when read.
|
|
||||||
/// </summary>
|
|
||||||
public bool PluginSafeMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating the wait time between plugin unload and plugin assembly unload.
|
|
||||||
/// Uses default value that may change between versions if set to null.
|
|
||||||
/// </summary>
|
|
||||||
public int? PluginWaitBeforeFree { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of saved styles.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("SavedStyles")]
|
|
||||||
public List<StyleModelV1>? SavedStylesOld { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of saved styles.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("SavedStylesVersioned")]
|
|
||||||
public List<StyleModel>? SavedStyles { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the currently chosen style.
|
|
||||||
/// </summary>
|
|
||||||
public string ChosenStyle { get; set; } = "Dalamud Standard";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool DisableRmtFiltering { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the order of DTR elements, by title.
|
|
||||||
/// </summary>
|
|
||||||
public List<string>? DtrOrder { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of ignored DTR elements, by title.
|
|
||||||
/// </summary>
|
|
||||||
public List<string>? DtrIgnore { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the spacing used for DTR entries.
|
|
||||||
/// </summary>
|
|
||||||
public int DtrSpacing { get; set; } = 10;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to swap the
|
|
||||||
/// direction in which elements are drawn in the DTR.
|
|
||||||
/// False indicates that elements will be drawn from the end of
|
|
||||||
/// the left side of the Server Info bar, and continue leftwards.
|
|
||||||
/// True indicates the opposite.
|
|
||||||
/// </summary>
|
|
||||||
public bool DtrSwapDirection { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the title screen menu is shown.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowTsm { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not market board data should be uploaded.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsMbCollect { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language.
|
|
||||||
/// </summary>
|
|
||||||
public string EffectiveLanguage
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(this.LanguageOverride))
|
|
||||||
{
|
|
||||||
var currentUiLang = CultureInfo.CurrentUICulture;
|
|
||||||
|
|
||||||
if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x))
|
|
||||||
return currentUiLang.TwoLetterISOLanguageName;
|
|
||||||
else
|
|
||||||
return languages[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.LanguageOverride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return languages[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not to show info on dev bar.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowDevBarInfo { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the last-used contact details for the plugin feedback form.
|
|
||||||
/// </summary>
|
|
||||||
public string LastFeedbackContactDetails { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of plugins that testing builds should be downloaded for.
|
|
||||||
/// </summary>
|
|
||||||
public List<PluginTestingOptIn>? PluginTestingOptIns { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load a configuration from the provided path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path to load the configuration file from.</param>
|
|
||||||
/// <returns>The deserialized configuration file.</returns>
|
|
||||||
public static DalamudConfiguration Load(string path)
|
|
||||||
{
|
|
||||||
DalamudConfiguration deserialized = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path), SerializerSettings);
|
if (string.IsNullOrEmpty(this.LanguageOverride))
|
||||||
|
{
|
||||||
|
var currentUiLang = CultureInfo.CurrentUICulture;
|
||||||
|
|
||||||
|
if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x))
|
||||||
|
return currentUiLang.TwoLetterISOLanguageName;
|
||||||
|
else
|
||||||
|
return languages[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.LanguageOverride;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
|
return languages[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialized ??= new DalamudConfiguration();
|
|
||||||
deserialized.configPath = path;
|
|
||||||
|
|
||||||
return deserialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save the configuration at the path it was loaded from.
|
|
||||||
/// </summary>
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
|
||||||
this.DalamudConfigurationSaved?.Invoke(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not to show info on dev bar.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowDevBarInfo { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last-used contact details for the plugin feedback form.
|
||||||
|
/// </summary>
|
||||||
|
public string LastFeedbackContactDetails { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of plugins that testing builds should be downloaded for.
|
||||||
|
/// </summary>
|
||||||
|
public List<PluginTestingOptIn>? PluginTestingOptIns { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a configuration from the provided path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to load the configuration file from.</param>
|
||||||
|
/// <returns>The deserialized configuration file.</returns>
|
||||||
|
public static DalamudConfiguration Load(string path)
|
||||||
|
{
|
||||||
|
DalamudConfiguration deserialized = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path), SerializerSettings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialized ??= new DalamudConfiguration();
|
||||||
|
deserialized.configPath = path;
|
||||||
|
|
||||||
|
return deserialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the configuration at the path it was loaded from.
|
||||||
|
/// </summary>
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||||
|
this.DalamudConfigurationSaved?.Invoke(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
namespace Dalamud.Configuration
|
namespace Dalamud.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional locations to load dev plugins from.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class DevPluginLocationSettings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional locations to load dev plugins from.
|
/// Gets or sets the dev pluign path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class DevPluginLocationSettings
|
public string Path { get; set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the dev pluign path.
|
|
||||||
/// </summary>
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled { get; set; }
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone this object.
|
/// Clone this object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A shallow copy of this object.</returns>
|
/// <returns>A shallow copy of this object.</returns>
|
||||||
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
namespace Dalamud.Configuration.Internal
|
namespace Dalamud.Configuration.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Settings for DevPlugins.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class DevPluginSettings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Settings for DevPlugins.
|
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class DevPluginSettings
|
public bool StartOnBoot { get; set; } = true;
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
|
|
||||||
/// </summary>
|
|
||||||
public bool StartOnBoot { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutomaticReloading { get; set; } = false;
|
public bool AutomaticReloading { get; set; } = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,37 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal
|
namespace Dalamud.Configuration.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Environmental configuration settings.
|
||||||
|
/// </summary>
|
||||||
|
internal class EnvironmentConfiguration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Environmental configuration settings.
|
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class EnvironmentConfiguration
|
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
|
||||||
/// </summary>
|
|
||||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
/// Gets a value indicating whether the DalamudForceReloaded setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
/// Gets a value indicating whether the DalamudForceMinHook setting has been enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not Dalamud context menus should be disabled.
|
/// Gets a value indicating whether or not Dalamud context menus should be disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");
|
public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");
|
||||||
|
|
||||||
private static bool GetEnvironmentVariable(string name)
|
private static bool GetEnvironmentVariable(string name)
|
||||||
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
=> bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,28 @@
|
||||||
namespace Dalamud.Configuration
|
namespace Dalamud.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third party repository for dalamud plugins.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ThirdPartyRepoSettings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Third party repository for dalamud plugins.
|
/// Gets or sets the third party repo url.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ThirdPartyRepoSettings
|
public string Url { get; set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the third party repo url.
|
|
||||||
/// </summary>
|
|
||||||
public string Url { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the third party repo is enabled.
|
/// Gets or sets a value indicating whether the third party repo is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled { get; set; }
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a short name for the repo url.
|
/// Gets or sets a short name for the repo url.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone this object.
|
/// Clone this object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A shallow copy of this object.</returns>
|
/// <returns>A shallow copy of this object.</returns>
|
||||||
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,149 +2,148 @@ using System.IO;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Configuration
|
namespace Dalamud.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PluginConfigurations
|
||||||
{
|
{
|
||||||
|
private readonly DirectoryInfo configDirectory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration to store settings for a dalamud plugin.
|
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PluginConfigurations
|
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
||||||
|
public PluginConfigurations(string storageFolder)
|
||||||
{
|
{
|
||||||
private readonly DirectoryInfo configDirectory;
|
this.configDirectory = new DirectoryInfo(storageFolder);
|
||||||
|
this.configDirectory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginConfigurations"/> class.
|
/// Save/Load plugin configuration.
|
||||||
/// </summary>
|
/// NOTE: Save/Load are still using Type information for now,
|
||||||
/// <param name="storageFolder">Directory for storage of plugin configuration files.</param>
|
/// despite LoadForType superseding Load and not requiring or using it.
|
||||||
public PluginConfigurations(string storageFolder)
|
/// It might be worth removing the Type info from Save, to strip it from all future saved configs,
|
||||||
|
/// and then Load() can probably be removed entirely.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">Plugin configuration.</param>
|
||||||
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
|
public void Save(IPluginConfiguration config, string pluginName)
|
||||||
|
{
|
||||||
|
File.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load plugin configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
|
/// <returns>Plugin configuration.</returns>
|
||||||
|
public IPluginConfiguration? Load(string pluginName)
|
||||||
|
{
|
||||||
|
var path = this.GetConfigFile(pluginName);
|
||||||
|
|
||||||
|
if (!path.Exists)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return DeserializeConfig(File.ReadAllText(path.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete the configuration file and folder for the specified plugin.
|
||||||
|
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginName">The name of the plugin.</param>
|
||||||
|
public void Delete(string pluginName)
|
||||||
|
{
|
||||||
|
var directory = this.GetDirectoryPath(pluginName);
|
||||||
|
if (directory.Exists)
|
||||||
|
directory.Delete(true);
|
||||||
|
|
||||||
|
var file = this.GetConfigFile(pluginName);
|
||||||
|
if (file.Exists)
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get plugin directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
|
/// <returns>Plugin directory path.</returns>
|
||||||
|
public string GetDirectory(string pluginName)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
this.configDirectory = new DirectoryInfo(storageFolder);
|
var path = this.GetDirectoryPath(pluginName);
|
||||||
this.configDirectory.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save/Load plugin configuration.
|
|
||||||
/// NOTE: Save/Load are still using Type information for now,
|
|
||||||
/// despite LoadForType superseding Load and not requiring or using it.
|
|
||||||
/// It might be worth removing the Type info from Save, to strip it from all future saved configs,
|
|
||||||
/// and then Load() can probably be removed entirely.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">Plugin configuration.</param>
|
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
|
||||||
public void Save(IPluginConfiguration config, string pluginName)
|
|
||||||
{
|
|
||||||
File.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load plugin configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
|
||||||
/// <returns>Plugin configuration.</returns>
|
|
||||||
public IPluginConfiguration? Load(string pluginName)
|
|
||||||
{
|
|
||||||
var path = this.GetConfigFile(pluginName);
|
|
||||||
|
|
||||||
if (!path.Exists)
|
if (!path.Exists)
|
||||||
return null;
|
|
||||||
|
|
||||||
return DeserializeConfig(File.ReadAllText(path.FullName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delete the configuration file and folder for the specified plugin.
|
|
||||||
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">The name of the plugin.</param>
|
|
||||||
public void Delete(string pluginName)
|
|
||||||
{
|
|
||||||
var directory = this.GetDirectoryPath(pluginName);
|
|
||||||
if (directory.Exists)
|
|
||||||
directory.Delete(true);
|
|
||||||
|
|
||||||
var file = this.GetConfigFile(pluginName);
|
|
||||||
if (file.Exists)
|
|
||||||
file.Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get plugin directory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
|
||||||
/// <returns>Plugin directory path.</returns>
|
|
||||||
public string GetDirectory(string pluginName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var path = this.GetDirectoryPath(pluginName);
|
path.Create();
|
||||||
if (!path.Exists)
|
|
||||||
{
|
|
||||||
path.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.FullName;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return path.FullName;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
/// <summary>
|
|
||||||
/// Load Plugin configuration. Parameterized deserialization.
|
|
||||||
/// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig().
|
|
||||||
/// Eventually there may be an additional pluginInterface method that can call this directly
|
|
||||||
/// without reflection - for now this is in support of the existing plugin api.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">Plugin Name.</param>
|
|
||||||
/// <typeparam name="T">Configuration Type.</typeparam>
|
|
||||||
/// <returns>Plugin Configuration.</returns>
|
|
||||||
public T LoadForType<T>(string pluginName) where T : IPluginConfiguration
|
|
||||||
{
|
{
|
||||||
var path = this.GetConfigFile(pluginName);
|
return string.Empty;
|
||||||
|
|
||||||
return !path.Exists ? default : JsonConvert.DeserializeObject<T>(File.ReadAllText(path.FullName));
|
|
||||||
|
|
||||||
// intentionally no type handling - it will break when updating a plugin at runtime
|
|
||||||
// and turns out to be unnecessary when we fully qualify the object type
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get FileInfo to plugin config file.
|
/// Load Plugin configuration. Parameterized deserialization.
|
||||||
/// </summary>
|
/// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig().
|
||||||
/// <param name="pluginName">InternalName of the plugin.</param>
|
/// Eventually there may be an additional pluginInterface method that can call this directly
|
||||||
/// <returns>FileInfo of the config file.</returns>
|
/// without reflection - for now this is in support of the existing plugin api.
|
||||||
public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
/// </summary>
|
||||||
|
/// <param name="pluginName">Plugin Name.</param>
|
||||||
|
/// <typeparam name="T">Configuration Type.</typeparam>
|
||||||
|
/// <returns>Plugin Configuration.</returns>
|
||||||
|
public T LoadForType<T>(string pluginName) where T : IPluginConfiguration
|
||||||
|
{
|
||||||
|
var path = this.GetConfigFile(pluginName);
|
||||||
|
|
||||||
/// <summary>
|
return !path.Exists ? default : JsonConvert.DeserializeObject<T>(File.ReadAllText(path.FullName));
|
||||||
/// Serializes a plugin configuration object.
|
|
||||||
/// </summary>
|
// intentionally no type handling - it will break when updating a plugin at runtime
|
||||||
/// <param name="config">The configuration object.</param>
|
// and turns out to be unnecessary when we fully qualify the object type
|
||||||
/// <returns>A string representing the serialized configuration object.</returns>
|
}
|
||||||
internal static string SerializeConfig(object? config)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get FileInfo to plugin config file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginName">InternalName of the plugin.</param>
|
||||||
|
/// <returns>FileInfo of the config file.</returns>
|
||||||
|
public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json"));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes a plugin configuration object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">The configuration object.</param>
|
||||||
|
/// <returns>A string representing the serialized configuration object.</returns>
|
||||||
|
internal static string SerializeConfig(object? config)
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
|
TypeNameHandling = TypeNameHandling.Objects,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes a plugin configuration from a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The serialized configuration.</param>
|
||||||
|
/// <returns>The configuration object, or null.</returns>
|
||||||
|
internal static IPluginConfiguration? DeserializeConfig(string data)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
||||||
|
data,
|
||||||
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
TypeNameHandling = TypeNameHandling.Objects,
|
TypeNameHandling = TypeNameHandling.Objects,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserializes a plugin configuration from a string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">The serialized configuration.</param>
|
|
||||||
/// <returns>The configuration object, or null.</returns>
|
|
||||||
internal static IPluginConfiguration? DeserializeConfig(string data)
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<IPluginConfiguration>(
|
|
||||||
data,
|
|
||||||
new JsonSerializerSettings
|
|
||||||
{
|
|
||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
|
||||||
TypeNameHandling = TypeNameHandling.Objects,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,131 +19,130 @@ using Serilog;
|
||||||
[assembly: InternalsVisibleTo("Dalamud.Test")]
|
[assembly: InternalsVisibleTo("Dalamud.Test")]
|
||||||
[assembly: InternalsVisibleTo("Dalamud.DevHelpers")]
|
[assembly: InternalsVisibleTo("Dalamud.DevHelpers")]
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main Dalamud class containing all subsystems.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class Dalamud : IServiceType
|
||||||
{
|
{
|
||||||
|
#region Internals
|
||||||
|
|
||||||
|
private readonly ManualResetEvent unloadSignal;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main Dalamud class containing all subsystems.
|
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class Dalamud : IServiceType
|
/// <param name="info">DalamudStartInfo instance.</param>
|
||||||
|
/// <param name="configuration">The Dalamud configuration.</param>
|
||||||
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
|
public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
||||||
{
|
{
|
||||||
#region Internals
|
this.unloadSignal = new ManualResetEvent(false);
|
||||||
|
this.unloadSignal.Reset();
|
||||||
|
|
||||||
private readonly ManualResetEvent unloadSignal;
|
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
||||||
|
|
||||||
#endregion
|
if (!configuration.IsResumeGameAfterPluginLoad)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">DalamudStartInfo instance.</param>
|
|
||||||
/// <param name="configuration">The Dalamud configuration.</param>
|
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
|
||||||
public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
|
||||||
{
|
{
|
||||||
this.unloadSignal = new ManualResetEvent(false);
|
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
||||||
this.unloadSignal.Reset();
|
try
|
||||||
|
{
|
||||||
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
_ = ServiceManager.InitializeEarlyLoadableServices();
|
||||||
|
}
|
||||||
if (!configuration.IsResumeGameAfterPluginLoad)
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Service initialization failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = ServiceManager.InitializeEarlyLoadableServices();
|
var tasks = new[]
|
||||||
|
{
|
||||||
|
ServiceManager.InitializeEarlyLoadableServices(),
|
||||||
|
ServiceManager.BlockingResolved,
|
||||||
|
};
|
||||||
|
|
||||||
|
await Task.WhenAny(tasks);
|
||||||
|
var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
|
||||||
|
if (faultedTasks.Any())
|
||||||
|
throw new AggregateException(faultedTasks);
|
||||||
|
|
||||||
|
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "Service initialization failure");
|
Log.Error(e, "Service initialization failure");
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
else
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
try
|
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
||||||
{
|
}
|
||||||
var tasks = new[]
|
});
|
||||||
{
|
|
||||||
ServiceManager.InitializeEarlyLoadableServices(),
|
|
||||||
ServiceManager.BlockingResolved,
|
|
||||||
};
|
|
||||||
|
|
||||||
await Task.WhenAny(tasks);
|
|
||||||
var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
|
|
||||||
if (faultedTasks.Any())
|
|
||||||
throw new AggregateException(faultedTasks);
|
|
||||||
|
|
||||||
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Service initialization failure");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets location of stored assets.
|
|
||||||
/// </summary>
|
|
||||||
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory!);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue an unload of Dalamud when it gets the chance.
|
|
||||||
/// </summary>
|
|
||||||
public void Unload()
|
|
||||||
{
|
|
||||||
Log.Information("Trigger unload");
|
|
||||||
this.unloadSignal.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for an unload request to start.
|
|
||||||
/// </summary>
|
|
||||||
public void WaitForUnload()
|
|
||||||
{
|
|
||||||
this.unloadSignal.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose subsystems related to plugin handling.
|
|
||||||
/// </summary>
|
|
||||||
public void DisposePlugins()
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
Service<DalamudIME>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// a render call after it has been disposed, which can crash if it attempts to
|
|
||||||
// use any resources that it freed in its own Dispose method
|
|
||||||
Service<InterfaceManager>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
Service<DalamudInterface>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
Service<PluginManager>.GetNullable()?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Replace the built-in exception handler with a debug one.
|
|
||||||
/// </summary>
|
|
||||||
internal void ReplaceExceptionHandler()
|
|
||||||
{
|
|
||||||
var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??";
|
|
||||||
var releaseFilter = Service<SigScanner>.Get().ScanText(releaseSig);
|
|
||||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
|
||||||
|
|
||||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
|
||||||
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets location of stored assets.
|
||||||
|
/// </summary>
|
||||||
|
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory!);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue an unload of Dalamud when it gets the chance.
|
||||||
|
/// </summary>
|
||||||
|
public void Unload()
|
||||||
|
{
|
||||||
|
Log.Information("Trigger unload");
|
||||||
|
this.unloadSignal.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wait for an unload request to start.
|
||||||
|
/// </summary>
|
||||||
|
public void WaitForUnload()
|
||||||
|
{
|
||||||
|
this.unloadSignal.WaitOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose subsystems related to plugin handling.
|
||||||
|
/// </summary>
|
||||||
|
public void DisposePlugins()
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
Service<DalamudIME>.GetNullable()?.Dispose();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// a render call after it has been disposed, which can crash if it attempts to
|
||||||
|
// use any resources that it freed in its own Dispose method
|
||||||
|
Service<InterfaceManager>.GetNullable()?.Dispose();
|
||||||
|
|
||||||
|
Service<DalamudInterface>.GetNullable()?.Dispose();
|
||||||
|
|
||||||
|
Service<PluginManager>.GetNullable()?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replace the built-in exception handler with a debug one.
|
||||||
|
/// </summary>
|
||||||
|
internal void ReplaceExceptionHandler()
|
||||||
|
{
|
||||||
|
var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??";
|
||||||
|
var releaseFilter = Service<SigScanner>.Get().ScanText(releaseSig);
|
||||||
|
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
||||||
|
|
||||||
|
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||||
|
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,167 +4,166 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Struct containing information needed to initialize Dalamud.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public record DalamudStartInfo : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct containing information needed to initialize Dalamud.
|
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
public DalamudStartInfo()
|
||||||
public record DalamudStartInfo : IServiceType
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
// ignored
|
||||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public DalamudStartInfo()
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">Object to copy values from.</param>
|
|
||||||
public DalamudStartInfo(DalamudStartInfo other)
|
|
||||||
{
|
|
||||||
this.WorkingDirectory = other.WorkingDirectory;
|
|
||||||
this.ConfigurationPath = other.ConfigurationPath;
|
|
||||||
this.PluginDirectory = other.PluginDirectory;
|
|
||||||
this.DefaultPluginDirectory = other.DefaultPluginDirectory;
|
|
||||||
this.AssetDirectory = other.AssetDirectory;
|
|
||||||
this.Language = other.Language;
|
|
||||||
this.GameVersion = other.GameVersion;
|
|
||||||
this.DelayInitializeMs = other.DelayInitializeMs;
|
|
||||||
this.TroubleshootingPackData = other.TroubleshootingPackData;
|
|
||||||
this.NoLoadPlugins = other.NoLoadPlugins;
|
|
||||||
this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
|
|
||||||
this.BootLogPath = other.BootLogPath;
|
|
||||||
this.BootShowConsole = other.BootShowConsole;
|
|
||||||
this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
|
|
||||||
this.BootWaitMessageBox = other.BootWaitMessageBox;
|
|
||||||
this.BootWaitDebugger = other.BootWaitDebugger;
|
|
||||||
this.BootVehEnabled = other.BootVehEnabled;
|
|
||||||
this.BootVehFull = other.BootVehFull;
|
|
||||||
this.BootEnableEtw = other.BootEnableEtw;
|
|
||||||
this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
|
|
||||||
this.BootEnabledGameFixes = other.BootEnabledGameFixes;
|
|
||||||
this.BootUnhookDlls = other.BootUnhookDlls;
|
|
||||||
this.CrashHandlerShow = other.CrashHandlerShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
|
||||||
/// </summary>
|
|
||||||
public string? WorkingDirectory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path to the configuration file.
|
|
||||||
/// </summary>
|
|
||||||
public string? ConfigurationPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path to the directory for installed plugins.
|
|
||||||
/// </summary>
|
|
||||||
public string? PluginDirectory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path to the directory for developer plugins.
|
|
||||||
/// </summary>
|
|
||||||
public string? DefaultPluginDirectory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path to core Dalamud assets.
|
|
||||||
/// </summary>
|
|
||||||
public string? AssetDirectory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the language of the game client.
|
|
||||||
/// </summary>
|
|
||||||
public ClientLanguage Language { get; set; } = ClientLanguage.English;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current game version code.
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(GameVersionConverter))]
|
|
||||||
public GameVersion? GameVersion { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets troubleshooting information to attach when generating a tspack file.
|
|
||||||
/// </summary>
|
|
||||||
public string TroubleshootingPackData { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
|
||||||
/// </summary>
|
|
||||||
public int DelayInitializeMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether no plugins should be loaded.
|
|
||||||
/// </summary>
|
|
||||||
public bool NoLoadPlugins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether no third-party plugins should be loaded.
|
|
||||||
/// </summary>
|
|
||||||
public bool NoLoadThirdPartyPlugins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path the boot log file is supposed to be written to.
|
|
||||||
/// </summary>
|
|
||||||
public string? BootLogPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether a Boot console should be shown.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootShowConsole { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootDisableFallbackConsole { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a flag indicating where Dalamud should wait with a message box.
|
|
||||||
/// </summary>
|
|
||||||
public int BootWaitMessageBox { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootWaitDebugger { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the VEH should be enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootVehEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the VEH should be doing full crash dumps.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootVehFull { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not ETW should be enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool BootEnableEtw { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value choosing the OpenProcess hookmode.
|
|
||||||
/// </summary>
|
|
||||||
public int BootDotnetOpenProcessHookMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of enabled game fixes.
|
|
||||||
/// </summary>
|
|
||||||
public List<string>? BootEnabledGameFixes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of DLLs that should be unhooked.
|
|
||||||
/// </summary>
|
|
||||||
public List<string>? BootUnhookDlls { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to show crash handler console window.
|
|
||||||
/// </summary>
|
|
||||||
public bool CrashHandlerShow { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">Object to copy values from.</param>
|
||||||
|
public DalamudStartInfo(DalamudStartInfo other)
|
||||||
|
{
|
||||||
|
this.WorkingDirectory = other.WorkingDirectory;
|
||||||
|
this.ConfigurationPath = other.ConfigurationPath;
|
||||||
|
this.PluginDirectory = other.PluginDirectory;
|
||||||
|
this.DefaultPluginDirectory = other.DefaultPluginDirectory;
|
||||||
|
this.AssetDirectory = other.AssetDirectory;
|
||||||
|
this.Language = other.Language;
|
||||||
|
this.GameVersion = other.GameVersion;
|
||||||
|
this.DelayInitializeMs = other.DelayInitializeMs;
|
||||||
|
this.TroubleshootingPackData = other.TroubleshootingPackData;
|
||||||
|
this.NoLoadPlugins = other.NoLoadPlugins;
|
||||||
|
this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
|
||||||
|
this.BootLogPath = other.BootLogPath;
|
||||||
|
this.BootShowConsole = other.BootShowConsole;
|
||||||
|
this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
|
||||||
|
this.BootWaitMessageBox = other.BootWaitMessageBox;
|
||||||
|
this.BootWaitDebugger = other.BootWaitDebugger;
|
||||||
|
this.BootVehEnabled = other.BootVehEnabled;
|
||||||
|
this.BootVehFull = other.BootVehFull;
|
||||||
|
this.BootEnableEtw = other.BootEnableEtw;
|
||||||
|
this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
|
||||||
|
this.BootEnabledGameFixes = other.BootEnabledGameFixes;
|
||||||
|
this.BootUnhookDlls = other.BootUnhookDlls;
|
||||||
|
this.CrashHandlerShow = other.CrashHandlerShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||||
|
/// </summary>
|
||||||
|
public string? WorkingDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the configuration file.
|
||||||
|
/// </summary>
|
||||||
|
public string? ConfigurationPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the directory for installed plugins.
|
||||||
|
/// </summary>
|
||||||
|
public string? PluginDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the directory for developer plugins.
|
||||||
|
/// </summary>
|
||||||
|
public string? DefaultPluginDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to core Dalamud assets.
|
||||||
|
/// </summary>
|
||||||
|
public string? AssetDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the language of the game client.
|
||||||
|
/// </summary>
|
||||||
|
public ClientLanguage Language { get; set; } = ClientLanguage.English;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current game version code.
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(GameVersionConverter))]
|
||||||
|
public GameVersion? GameVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets troubleshooting information to attach when generating a tspack file.
|
||||||
|
/// </summary>
|
||||||
|
public string TroubleshootingPackData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
||||||
|
/// </summary>
|
||||||
|
public int DelayInitializeMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether no plugins should be loaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoLoadPlugins { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether no third-party plugins should be loaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoLoadThirdPartyPlugins { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path the boot log file is supposed to be written to.
|
||||||
|
/// </summary>
|
||||||
|
public string? BootLogPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether a Boot console should be shown.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootShowConsole { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootDisableFallbackConsole { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating where Dalamud should wait with a message box.
|
||||||
|
/// </summary>
|
||||||
|
public int BootWaitMessageBox { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootWaitDebugger { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the VEH should be enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootVehEnabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the VEH should be doing full crash dumps.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootVehFull { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not ETW should be enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool BootEnableEtw { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value choosing the OpenProcess hookmode.
|
||||||
|
/// </summary>
|
||||||
|
public int BootDotnetOpenProcessHookMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of enabled game fixes.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? BootEnabledGameFixes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of DLLs that should be unhooked.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? BootUnhookDlls { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to show crash handler console window.
|
||||||
|
/// </summary>
|
||||||
|
public bool CrashHandlerShow { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,331 +19,330 @@ using Lumina.Excel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Data
|
namespace Dalamud.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed class DataManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||||
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
|
||||||
/// </summary>
|
private readonly Thread luminaResourceThread;
|
||||||
[PluginInterface]
|
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.ServiceConstructor]
|
||||||
public sealed class DataManager : IDisposable, IServiceType
|
private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
|
||||||
{
|
{
|
||||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
this.Language = dalamudStartInfo.Language;
|
||||||
|
|
||||||
private readonly Thread luminaResourceThread;
|
// Set up default values so plugins do not null-reference when data is being loaded.
|
||||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
var baseDir = dalamud.AssetDirectory.FullName;
|
||||||
private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
|
try
|
||||||
{
|
{
|
||||||
this.Language = dalamudStartInfo.Language;
|
Log.Verbose("Starting data load...");
|
||||||
|
|
||||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||||
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!;
|
||||||
|
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
||||||
|
|
||||||
var baseDir = dalamud.AssetDirectory.FullName;
|
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
||||||
try
|
|
||||||
|
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
||||||
|
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")))!;
|
||||||
|
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
||||||
|
|
||||||
|
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
||||||
|
|
||||||
|
using (Timings.Start("Lumina Init"))
|
||||||
{
|
{
|
||||||
Log.Verbose("Starting data load...");
|
var luminaOptions = new LuminaOptions
|
||||||
|
|
||||||
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!;
|
|
||||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
|
||||||
|
|
||||||
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")))!;
|
|
||||||
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
|
||||||
|
|
||||||
using (Timings.Start("Lumina Init"))
|
|
||||||
{
|
{
|
||||||
var luminaOptions = new LuminaOptions
|
LoadMultithreaded = true,
|
||||||
{
|
CacheFileResources = true,
|
||||||
LoadMultithreaded = true,
|
|
||||||
CacheFileResources = true,
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
PanicOnSheetChecksumMismatch = true,
|
PanicOnSheetChecksumMismatch = true,
|
||||||
#else
|
#else
|
||||||
PanicOnSheetChecksumMismatch = false,
|
PanicOnSheetChecksumMismatch = false,
|
||||||
#endif
|
#endif
|
||||||
DefaultExcelLanguage = this.Language.ToLumina(),
|
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var processModule = Process.GetCurrentProcess().MainModule;
|
var processModule = Process.GetCurrentProcess().MainModule;
|
||||||
if (processModule != null)
|
if (processModule != null)
|
||||||
|
{
|
||||||
|
this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Could not main module.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IsDataReady = true;
|
||||||
|
|
||||||
|
this.luminaCancellationTokenSource = new();
|
||||||
|
|
||||||
|
var luminaCancellationToken = this.luminaCancellationTokenSource.Token;
|
||||||
|
this.luminaResourceThread = new(() =>
|
||||||
|
{
|
||||||
|
while (!luminaCancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (this.GameData.FileHandleManager.HasPendingFileLoads)
|
||||||
{
|
{
|
||||||
this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions);
|
this.GameData.ProcessFileHandleQueue();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Could not main module.");
|
Thread.Sleep(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
this.IsDataReady = true;
|
this.luminaResourceThread.Start();
|
||||||
|
|
||||||
this.luminaCancellationTokenSource = new();
|
|
||||||
|
|
||||||
var luminaCancellationToken = this.luminaCancellationTokenSource.Token;
|
|
||||||
this.luminaResourceThread = new(() =>
|
|
||||||
{
|
|
||||||
while (!luminaCancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (this.GameData.FileHandleManager.HasPendingFileLoads)
|
|
||||||
{
|
|
||||||
this.GameData.ProcessFileHandleQueue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Thread.Sleep(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.luminaResourceThread.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not download data.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// Gets the current game client language.
|
|
||||||
/// </summary>
|
|
||||||
public ClientLanguage Language { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the OpCodes sent by the server to the client.
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the OpCodes sent by the client to the server.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="Lumina"/> object which gives access to any excel/game data.
|
|
||||||
/// </summary>
|
|
||||||
public GameData GameData { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelModule Excel => this.GameData.Excel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether Game Data is ready to be read.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDataReady { get; private set; }
|
|
||||||
|
|
||||||
#region Lumina Wrappers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
|
||||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
|
||||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
|
||||||
{
|
{
|
||||||
return this.Excel.GetSheet<T>();
|
Log.Error(ex, "Could not download data.");
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="language">Language of the sheet to get.</param>
|
|
||||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
|
||||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
|
||||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
|
||||||
{
|
|
||||||
return this.Excel.GetSheet<T>(language.ToLumina());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="FileResource"/> with the given path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path inside of the game files.</param>
|
|
||||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
|
||||||
public FileResource? GetFile(string path)
|
|
||||||
{
|
|
||||||
return this.GetFile<FileResource>(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of resource.</typeparam>
|
|
||||||
/// <param name="path">The path inside of the game files.</param>
|
|
||||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
|
||||||
public T? GetFile<T>(string path) where T : FileResource
|
|
||||||
{
|
|
||||||
var filePath = GameData.ParseFilePath(path);
|
|
||||||
if (filePath == null)
|
|
||||||
return default;
|
|
||||||
return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if the file with the given path exists within the game's index files.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path inside of the game files.</param>
|
|
||||||
/// <returns>True if the file exists.</returns>
|
|
||||||
public bool FileExists(string path)
|
|
||||||
{
|
|
||||||
return this.GameData.FileExists(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetIcon(uint iconId)
|
|
||||||
{
|
|
||||||
return this.GetIcon(this.Language, iconId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given quality.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetIcon(bool isHq, uint iconId)
|
|
||||||
{
|
|
||||||
var type = isHq ? "hq/" : string.Empty;
|
|
||||||
return this.GetIcon(type, iconId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconLanguage">The requested language.</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
|
||||||
{
|
|
||||||
var type = iconLanguage switch
|
|
||||||
{
|
|
||||||
ClientLanguage.Japanese => "ja/",
|
|
||||||
ClientLanguage.English => "en/",
|
|
||||||
ClientLanguage.German => "de/",
|
|
||||||
ClientLanguage.French => "fr/",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.GetIcon(type, iconId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetIcon(string? type, uint iconId)
|
|
||||||
{
|
|
||||||
type ??= string.Empty;
|
|
||||||
if (type.Length > 0 && !type.EndsWith("/"))
|
|
||||||
type += "/";
|
|
||||||
|
|
||||||
var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
|
|
||||||
var file = this.GetFile<TexFile>(filePath);
|
|
||||||
|
|
||||||
if (type == string.Empty || file != default)
|
|
||||||
return file;
|
|
||||||
|
|
||||||
// Couldn't get specific type, try for generic version.
|
|
||||||
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
|
|
||||||
file = this.GetFile<TexFile>(filePath);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the HQ icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
public TexFile? GetHqIcon(uint iconId)
|
|
||||||
=> this.GetIcon(true, iconId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the passed <see cref="TexFile"/> as a drawable ImGui TextureWrap.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
|
||||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
|
||||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
|
||||||
{
|
|
||||||
return tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The internal path to the texture.</param>
|
|
||||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
|
||||||
public TextureWrap? GetImGuiTexture(string path)
|
|
||||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(iconId));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given quality.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconLanguage">The requested language.</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the HQ icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose this DataManager.
|
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
this.luminaCancellationTokenSource.Cancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current game client language.
|
||||||
|
/// </summary>
|
||||||
|
public ClientLanguage Language { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the OpCodes sent by the server to the client.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the OpCodes sent by the client to the server.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Lumina"/> object which gives access to any excel/game data.
|
||||||
|
/// </summary>
|
||||||
|
public GameData GameData { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelModule Excel => this.GameData.Excel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Game Data is ready to be read.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDataReady { get; private set; }
|
||||||
|
|
||||||
|
#region Lumina Wrappers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||||
|
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||||
|
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||||
|
{
|
||||||
|
return this.Excel.GetSheet<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="language">Language of the sheet to get.</param>
|
||||||
|
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||||
|
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||||
|
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||||
|
{
|
||||||
|
return this.Excel.GetSheet<T>(language.ToLumina());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="FileResource"/> with the given path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path inside of the game files.</param>
|
||||||
|
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||||
|
public FileResource? GetFile(string path)
|
||||||
|
{
|
||||||
|
return this.GetFile<FileResource>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of resource.</typeparam>
|
||||||
|
/// <param name="path">The path inside of the game files.</param>
|
||||||
|
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||||
|
public T? GetFile<T>(string path) where T : FileResource
|
||||||
|
{
|
||||||
|
var filePath = GameData.ParseFilePath(path);
|
||||||
|
if (filePath == null)
|
||||||
|
return default;
|
||||||
|
return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile<T>(filePath.Category, filePath) : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the file with the given path exists within the game's index files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path inside of the game files.</param>
|
||||||
|
/// <returns>True if the file exists.</returns>
|
||||||
|
public bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return this.GameData.FileExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
|
public TexFile? GetIcon(uint iconId)
|
||||||
|
{
|
||||||
|
return this.GetIcon(this.Language, iconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given quality.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
|
public TexFile? GetIcon(bool isHq, uint iconId)
|
||||||
|
{
|
||||||
|
var type = isHq ? "hq/" : string.Empty;
|
||||||
|
return this.GetIcon(type, iconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconLanguage">The requested language.</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
|
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
||||||
|
{
|
||||||
|
var type = iconLanguage switch
|
||||||
|
{
|
||||||
|
ClientLanguage.Japanese => "ja/",
|
||||||
|
ClientLanguage.English => "en/",
|
||||||
|
ClientLanguage.German => "de/",
|
||||||
|
ClientLanguage.French => "fr/",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.GetIcon(type, iconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
|
public TexFile? GetIcon(string? type, uint iconId)
|
||||||
|
{
|
||||||
|
type ??= string.Empty;
|
||||||
|
if (type.Length > 0 && !type.EndsWith("/"))
|
||||||
|
type += "/";
|
||||||
|
|
||||||
|
var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
|
||||||
|
var file = this.GetFile<TexFile>(filePath);
|
||||||
|
|
||||||
|
if (type == string.Empty || file != default)
|
||||||
|
return file;
|
||||||
|
|
||||||
|
// Couldn't get specific type, try for generic version.
|
||||||
|
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
|
||||||
|
file = this.GetFile<TexFile>(filePath);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TexFile"/> containing the HQ icon with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||||
|
public TexFile? GetHqIcon(uint iconId)
|
||||||
|
=> this.GetIcon(true, iconId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the passed <see cref="TexFile"/> as a drawable ImGui TextureWrap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||||
|
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||||
|
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
||||||
|
{
|
||||||
|
return tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The internal path to the texture.</param>
|
||||||
|
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||||
|
public TextureWrap? GetImGuiTexture(string path)
|
||||||
|
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
|
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||||
|
=> this.GetImGuiTexture(this.GetIcon(iconId));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given quality.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
|
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
||||||
|
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given language.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconLanguage">The requested language.</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
|
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
||||||
|
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
|
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
||||||
|
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a <see cref="TextureWrap"/> containing the HQ icon with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iconId">The icon ID.</param>
|
||||||
|
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||||
|
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
||||||
|
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose this DataManager.
|
||||||
|
/// </summary>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
this.luminaCancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,341 +18,340 @@ using Serilog.Events;
|
||||||
|
|
||||||
using static Dalamud.NativeFunctions;
|
using static Dalamud.NativeFunctions;
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main entrypoint for the Dalamud system.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EntryPoint
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entrypoint for the Dalamud system.
|
/// Log level switch for runtime log level change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EntryPoint
|
public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||||
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
|
public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>HGLOBAL for message.</returns>
|
||||||
|
public delegate IntPtr VehDelegate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize Dalamud.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||||
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
|
public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
|
||||||
/// Log level switch for runtime log level change.
|
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr)!;
|
||||||
/// </summary>
|
|
||||||
public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose);
|
|
||||||
|
|
||||||
/// <summary>
|
if ((info.BootWaitMessageBox & 4) != 0)
|
||||||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
|
||||||
/// </summary>
|
|
||||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
|
||||||
public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent);
|
|
||||||
|
|
||||||
/// <summary>
|
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
|
||||||
/// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <returns>HGLOBAL for message.</returns>
|
|
||||||
public delegate IntPtr VehDelegate();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize Dalamud.
|
/// Returns stack trace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
/// <returns>HGlobal to wchar_t* stack trace c-string.</returns>
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
public static IntPtr VehCallback()
|
||||||
public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
|
return Marshal.StringToHGlobalUni(Environment.StackTrace);
|
||||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr)!;
|
|
||||||
|
|
||||||
if ((info.BootWaitMessageBox & 4) != 0)
|
|
||||||
MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
|
|
||||||
|
|
||||||
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
/// <summary>
|
|
||||||
/// Returns stack trace.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>HGlobal to wchar_t* stack trace c-string.</returns>
|
|
||||||
public static IntPtr VehCallback()
|
|
||||||
{
|
{
|
||||||
try
|
return Marshal.StringToHGlobalUni("Fail: " + e);
|
||||||
{
|
|
||||||
return Marshal.StringToHGlobalUni(Environment.StackTrace);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Marshal.StringToHGlobalUni("Fail: " + e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets up logging.
|
/// Sets up logging.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseDirectory">Base directory.</param>
|
/// <param name="baseDirectory">Base directory.</param>
|
||||||
/// <param name="logConsole">Whether to log to console.</param>
|
/// <param name="logConsole">Whether to log to console.</param>
|
||||||
/// <param name="logSynchronously">Log synchronously.</param>
|
/// <param name="logSynchronously">Log synchronously.</param>
|
||||||
internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously)
|
internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||||
var oldPath = Path.Combine(baseDirectory, "dalamud.old.log");
|
var oldPath = Path.Combine(baseDirectory, "dalamud.old.log");
|
||||||
var oldPathOld = Path.Combine(baseDirectory, "dalamud.log.old");
|
var oldPathOld = Path.Combine(baseDirectory, "dalamud.log.old");
|
||||||
#else
|
#else
|
||||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
||||||
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log");
|
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log");
|
||||||
var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
||||||
#endif
|
#endif
|
||||||
Log.CloseAndFlush();
|
Log.CloseAndFlush();
|
||||||
|
|
||||||
var oldFileOld = new FileInfo(oldPathOld);
|
var oldFileOld = new FileInfo(oldPathOld);
|
||||||
if (oldFileOld.Exists)
|
if (oldFileOld.Exists)
|
||||||
{
|
|
||||||
var oldFile = new FileInfo(oldPath);
|
|
||||||
if (oldFile.Exists)
|
|
||||||
oldFileOld.Delete();
|
|
||||||
else
|
|
||||||
oldFileOld.MoveTo(oldPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
|
|
||||||
|
|
||||||
var config = new LoggerConfiguration()
|
|
||||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
|
||||||
.MinimumLevel.ControlledBy(LogLevelSwitch);
|
|
||||||
|
|
||||||
if (logSynchronously)
|
|
||||||
{
|
|
||||||
config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config = config.WriteTo.Async(a => a.File(
|
|
||||||
logPath,
|
|
||||||
fileSizeLimitBytes: null,
|
|
||||||
buffered: false,
|
|
||||||
flushToDiskInterval: TimeSpan.FromSeconds(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logConsole)
|
|
||||||
config = config.WriteTo.Console();
|
|
||||||
|
|
||||||
Log.Logger = config.CreateLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
|
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
|
||||||
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
|
||||||
{
|
{
|
||||||
// Setup logger
|
|
||||||
InitLogging(info.WorkingDirectory!, info.BootShowConsole, true);
|
|
||||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
|
||||||
|
|
||||||
// Load configuration first to get some early persistent state, like log level
|
|
||||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
|
|
||||||
|
|
||||||
// Set the appropriate logging level from the configuration
|
|
||||||
#if !DEBUG
|
|
||||||
if (!configuration.LogSynchronously)
|
|
||||||
InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously);
|
|
||||||
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Log any unhandled exception.
|
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
|
||||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (info.DelayInitializeMs > 0)
|
|
||||||
{
|
|
||||||
Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs));
|
|
||||||
Thread.Sleep(info.DelayInitializeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Information(new string('-', 80));
|
|
||||||
Log.Information("Initializing a session..");
|
|
||||||
|
|
||||||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
|
||||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
|
||||||
|
|
||||||
if (!Util.IsLinux())
|
|
||||||
InitSymbolHandler(info);
|
|
||||||
|
|
||||||
var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
|
|
||||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
|
||||||
|
|
||||||
dalamud.WaitForUnload();
|
|
||||||
|
|
||||||
ServiceManager.UnloadAllServices();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Fatal(ex, "Unhandled exception on main thread.");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
|
||||||
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
|
||||||
|
|
||||||
Log.Information("Session has ended.");
|
|
||||||
Log.CloseAndFlush();
|
|
||||||
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
|
|
||||||
{
|
|
||||||
if (ev.LogEvent.Exception == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing
|
|
||||||
// something intentionally (or this is known).
|
|
||||||
if (ev.LogEvent.Level < LogEventLevel.Warning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InitSymbolHandler(DalamudStartInfo info)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(info.AssetDirectory))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
|
||||||
var searchPath = $".;{symbolPath}";
|
|
||||||
|
|
||||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
|
||||||
SymCleanup(GetCurrentProcess());
|
|
||||||
|
|
||||||
if (!SymInitialize(GetCurrentProcess(), searchPath, true))
|
|
||||||
throw new Win32Exception();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "SymbolHandler Initialize Failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Trim existing log file to a specified length, and optionally move the excess data to another file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logPath">Target log file to trim.</param>
|
|
||||||
/// <param name="logMaxSize">Maximum size of target log file.</param>
|
|
||||||
/// <param name="oldPath">.old file to move excess data to.</param>
|
|
||||||
/// <param name="oldMaxSize">Maximum size of .old file.</param>
|
|
||||||
private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize)
|
|
||||||
{
|
|
||||||
var logFile = new FileInfo(logPath);
|
|
||||||
var oldFile = new FileInfo(oldPath);
|
var oldFile = new FileInfo(oldPath);
|
||||||
var targetFiles = new[]
|
if (oldFile.Exists)
|
||||||
{
|
oldFileOld.Delete();
|
||||||
(logFile, logMaxSize),
|
else
|
||||||
(oldFile, oldMaxSize),
|
oldFileOld.MoveTo(oldPath);
|
||||||
};
|
|
||||||
var buffer = new byte[4096];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!logFile.Exists)
|
|
||||||
logFile.Create().Close();
|
|
||||||
|
|
||||||
// 1. Move excess data from logFile to oldFile
|
|
||||||
if (logFile.Length > logMaxSize)
|
|
||||||
{
|
|
||||||
using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
||||||
using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
|
||||||
|
|
||||||
var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
|
|
||||||
reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
|
|
||||||
|
|
||||||
for (var i = 0; i < amountToMove; i += buffer.Length)
|
|
||||||
writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Cull each of .log and .old files
|
|
||||||
foreach (var (file, maxSize) in targetFiles)
|
|
||||||
{
|
|
||||||
if (!file.Exists || file.Length <= maxSize)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
||||||
using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
|
|
||||||
|
|
||||||
reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
|
|
||||||
for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
|
|
||||||
writer.Write(buffer, 0, read);
|
|
||||||
|
|
||||||
writer.SetLength(maxSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is IOException)
|
|
||||||
{
|
|
||||||
foreach (var (file, _) in targetFiles)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (file.Exists)
|
|
||||||
file.Delete();
|
|
||||||
}
|
|
||||||
catch (Exception ex2)
|
|
||||||
{
|
|
||||||
Log.Error(ex2, "Failed to delete {file}", file.FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Error(ex, "Log cull failed");
|
|
||||||
|
|
||||||
/*
|
|
||||||
var caption = "XIVLauncher Error";
|
|
||||||
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
|
|
||||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
|
||||||
|
|
||||||
|
var config = new LoggerConfiguration()
|
||||||
|
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||||
|
.MinimumLevel.ControlledBy(LogLevelSwitch);
|
||||||
|
|
||||||
|
if (logSynchronously)
|
||||||
{
|
{
|
||||||
switch (args.ExceptionObject)
|
config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
|
||||||
{
|
}
|
||||||
case Exception ex:
|
else
|
||||||
Log.Fatal(ex, "Unhandled exception on AppDomain");
|
{
|
||||||
Troubleshooting.LogException(ex, "DalamudUnhandled");
|
config = config.WriteTo.Async(a => a.File(
|
||||||
|
logPath,
|
||||||
var info = "Further information could not be obtained";
|
fileSizeLimitBytes: null,
|
||||||
if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null)
|
buffered: false,
|
||||||
{
|
flushToDiskInterval: TimeSpan.FromSeconds(1)));
|
||||||
info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}";
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
|
|
||||||
var result = MessageBoxW(
|
|
||||||
Process.GetCurrentProcess().MainWindowHandle,
|
|
||||||
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?",
|
|
||||||
"Dalamud",
|
|
||||||
flags);
|
|
||||||
|
|
||||||
if (result == (int)User32.MessageBoxResult.IDYES)
|
|
||||||
{
|
|
||||||
Log.Information("User chose to disable plugins on next launch...");
|
|
||||||
var config = Service<DalamudConfiguration>.Get();
|
|
||||||
config.PluginSafeMode = true;
|
|
||||||
config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.CloseAndFlush();
|
|
||||||
Environment.Exit(-1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
|
||||||
|
|
||||||
Log.CloseAndFlush();
|
|
||||||
Environment.Exit(-1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
if (logConsole)
|
||||||
|
config = config.WriteTo.Console();
|
||||||
|
|
||||||
|
Log.Logger = config.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize all Dalamud subsystems and start running on the main thread.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
|
||||||
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
|
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
||||||
|
{
|
||||||
|
// Setup logger
|
||||||
|
InitLogging(info.WorkingDirectory!, info.BootShowConsole, true);
|
||||||
|
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||||
|
|
||||||
|
// Load configuration first to get some early persistent state, like log level
|
||||||
|
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
|
||||||
|
|
||||||
|
// Set the appropriate logging level from the configuration
|
||||||
|
#if !DEBUG
|
||||||
|
if (!configuration.LogSynchronously)
|
||||||
|
InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously);
|
||||||
|
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Log any unhandled exception.
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!args.Observed)
|
if (info.DelayInitializeMs > 0)
|
||||||
Log.Error(args.Exception, "Unobserved exception in Task.");
|
{
|
||||||
|
Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs));
|
||||||
|
Thread.Sleep(info.DelayInitializeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information(new string('-', 80));
|
||||||
|
Log.Information("Initializing a session..");
|
||||||
|
|
||||||
|
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
||||||
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
||||||
|
|
||||||
|
if (!Util.IsLinux())
|
||||||
|
InitSymbolHandler(info);
|
||||||
|
|
||||||
|
var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
|
||||||
|
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
||||||
|
|
||||||
|
dalamud.WaitForUnload();
|
||||||
|
|
||||||
|
ServiceManager.UnloadAllServices();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Fatal(ex, "Unhandled exception on main thread.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
||||||
|
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
||||||
|
|
||||||
|
Log.Information("Session has ended.");
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
|
||||||
|
{
|
||||||
|
if (ev.LogEvent.Exception == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing
|
||||||
|
// something intentionally (or this is known).
|
||||||
|
if (ev.LogEvent.Level < LogEventLevel.Warning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InitSymbolHandler(DalamudStartInfo info)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(info.AssetDirectory))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||||
|
var searchPath = $".;{symbolPath}";
|
||||||
|
|
||||||
|
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||||
|
SymCleanup(GetCurrentProcess());
|
||||||
|
|
||||||
|
if (!SymInitialize(GetCurrentProcess(), searchPath, true))
|
||||||
|
throw new Win32Exception();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "SymbolHandler Initialize Failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trim existing log file to a specified length, and optionally move the excess data to another file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logPath">Target log file to trim.</param>
|
||||||
|
/// <param name="logMaxSize">Maximum size of target log file.</param>
|
||||||
|
/// <param name="oldPath">.old file to move excess data to.</param>
|
||||||
|
/// <param name="oldMaxSize">Maximum size of .old file.</param>
|
||||||
|
private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize)
|
||||||
|
{
|
||||||
|
var logFile = new FileInfo(logPath);
|
||||||
|
var oldFile = new FileInfo(oldPath);
|
||||||
|
var targetFiles = new[]
|
||||||
|
{
|
||||||
|
(logFile, logMaxSize),
|
||||||
|
(oldFile, oldMaxSize),
|
||||||
|
};
|
||||||
|
var buffer = new byte[4096];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!logFile.Exists)
|
||||||
|
logFile.Create().Close();
|
||||||
|
|
||||||
|
// 1. Move excess data from logFile to oldFile
|
||||||
|
if (logFile.Length > logMaxSize)
|
||||||
|
{
|
||||||
|
using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||||
|
|
||||||
|
var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
|
||||||
|
reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
|
||||||
|
|
||||||
|
for (var i = 0; i < amountToMove; i += buffer.Length)
|
||||||
|
writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cull each of .log and .old files
|
||||||
|
foreach (var (file, maxSize) in targetFiles)
|
||||||
|
{
|
||||||
|
if (!file.Exists || file.Length <= maxSize)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
|
||||||
|
|
||||||
|
reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
|
||||||
|
for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
|
||||||
|
writer.Write(buffer, 0, read);
|
||||||
|
|
||||||
|
writer.SetLength(maxSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is IOException)
|
||||||
|
{
|
||||||
|
foreach (var (file, _) in targetFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (file.Exists)
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
catch (Exception ex2)
|
||||||
|
{
|
||||||
|
Log.Error(ex2, "Failed to delete {file}", file.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Error(ex, "Log cull failed");
|
||||||
|
|
||||||
|
/*
|
||||||
|
var caption = "XIVLauncher Error";
|
||||||
|
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
|
||||||
|
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
switch (args.ExceptionObject)
|
||||||
|
{
|
||||||
|
case Exception ex:
|
||||||
|
Log.Fatal(ex, "Unhandled exception on AppDomain");
|
||||||
|
Troubleshooting.LogException(ex, "DalamudUnhandled");
|
||||||
|
|
||||||
|
var info = "Further information could not be obtained";
|
||||||
|
if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null)
|
||||||
|
{
|
||||||
|
info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
|
||||||
|
var result = MessageBoxW(
|
||||||
|
Process.GetCurrentProcess().MainWindowHandle,
|
||||||
|
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?",
|
||||||
|
"Dalamud",
|
||||||
|
flags);
|
||||||
|
|
||||||
|
if (result == (int)User32.MessageBoxResult.IDYES)
|
||||||
|
{
|
||||||
|
Log.Information("User chose to disable plugins on next launch...");
|
||||||
|
var config = Service<DalamudConfiguration>.Get();
|
||||||
|
config.PluginSafeMode = true;
|
||||||
|
config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
Environment.Exit(-1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||||
|
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
Environment.Exit(-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
if (!args.Observed)
|
||||||
|
Log.Error(args.Exception, "Unobserved exception in Task.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,114 +5,113 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base memory address resolver.
|
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseAddressResolver
|
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsResolved { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup the resolver, calling the appropriate method based on the process architecture,
|
||||||
|
/// using the default SigScanner.
|
||||||
|
///
|
||||||
|
/// For plugins. Not intended to be called from Dalamud Service{T} constructors.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public void Setup() => this.Setup(Service<SigScanner>.Get());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup the resolver, calling the appropriate method based on the process architecture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
public void Setup(SigScanner scanner)
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Because C# don't allow to call virtual function while in ctor
|
||||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
// we have to do this shit :\
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
if (this.IsResolved)
|
||||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
|
||||||
/// </summary>
|
|
||||||
protected bool IsResolved { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup the resolver, calling the appropriate method based on the process architecture,
|
|
||||||
/// using the default SigScanner.
|
|
||||||
///
|
|
||||||
/// For plugins. Not intended to be called from Dalamud Service{T} constructors.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public void Setup() => this.Setup(Service<SigScanner>.Get());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup the resolver, calling the appropriate method based on the process architecture.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
|
||||||
public void Setup(SigScanner scanner)
|
|
||||||
{
|
{
|
||||||
// Because C# don't allow to call virtual function while in ctor
|
return;
|
||||||
// we have to do this shit :\
|
|
||||||
|
|
||||||
if (this.IsResolved)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanner.Is32BitProcess)
|
|
||||||
{
|
|
||||||
this.Setup32Bit(scanner);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.Setup64Bit(scanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.SetupInternal(scanner);
|
|
||||||
|
|
||||||
var className = this.GetType().Name;
|
|
||||||
var list = new List<(string, IntPtr)>();
|
|
||||||
lock (DebugScannedValues)
|
|
||||||
DebugScannedValues[className] = list;
|
|
||||||
|
|
||||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
|
||||||
{
|
|
||||||
list.Add((property.Name, (IntPtr)property.GetValue(this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.IsResolved = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (scanner.Is32BitProcess)
|
||||||
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
|
||||||
/// <param name="address">The address of the virtual table.</param>
|
|
||||||
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
|
||||||
/// <param name="count">The vfunc index.</param>
|
|
||||||
/// <returns>A delegate function pointer that can be invoked.</returns>
|
|
||||||
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class
|
|
||||||
{
|
{
|
||||||
// Get vtable
|
this.Setup32Bit(scanner);
|
||||||
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
}
|
||||||
|
else
|
||||||
// Get an address to the function
|
{
|
||||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
this.Setup64Bit(scanner);
|
||||||
|
|
||||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.SetupInternal(scanner);
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
|
||||||
/// </summary>
|
var className = this.GetType().Name;
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
var list = new List<(string, IntPtr)>();
|
||||||
protected virtual void Setup32Bit(SigScanner scanner)
|
lock (DebugScannedValues)
|
||||||
|
DebugScannedValues[className] = list;
|
||||||
|
|
||||||
|
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("32 bit version is not supported.");
|
list.Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.IsResolved = true;
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
|
||||||
protected virtual void Setup64Bit(SigScanner scanner)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("64 bit version is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
/// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
/// <typeparam name="T">The delegate to marshal the function pointer to.</typeparam>
|
||||||
protected virtual void SetupInternal(SigScanner scanner)
|
/// <param name="address">The address of the virtual table.</param>
|
||||||
{
|
/// <param name="vtableOffset">The offset from address to the vtable pointer.</param>
|
||||||
// Do nothing
|
/// <param name="count">The vfunc index.</param>
|
||||||
}
|
/// <returns>A delegate function pointer that can be invoked.</returns>
|
||||||
|
public T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class
|
||||||
|
{
|
||||||
|
// Get vtable
|
||||||
|
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
||||||
|
|
||||||
|
// Get an address to the function
|
||||||
|
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
||||||
|
|
||||||
|
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
protected virtual void Setup32Bit(SigScanner scanner)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("32 bit version is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
protected virtual void Setup64Bit(SigScanner scanner)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("64 bit version is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
|
protected virtual void SetupInternal(SigScanner scanner)
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,314 +20,313 @@ using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat events and public helper functions.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public class ChatHandlers : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||||
/// Chat events and public helper functions.
|
// {
|
||||||
/// </summary>
|
// { "", "<:ffxive071:585847382210642069>" },
|
||||||
[PluginInterface]
|
// { "", "<:ffxive083:585848592699490329>" },
|
||||||
[InterfaceVersion("1.0")]
|
// };
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public class ChatHandlers : IServiceType
|
// private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
|
||||||
|
// {
|
||||||
|
// { XivChatType.CrossParty, Color.DodgerBlue },
|
||||||
|
// { XivChatType.Party, Color.DodgerBlue },
|
||||||
|
// { XivChatType.FreeCompany, Color.DeepSkyBlue },
|
||||||
|
// { XivChatType.CrossLinkShell1, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell2, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell3, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell4, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell5, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell6, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell7, Color.ForestGreen },
|
||||||
|
// { XivChatType.CrossLinkShell8, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls1, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls2, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls3, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls4, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls5, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls6, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls7, Color.ForestGreen },
|
||||||
|
// { XivChatType.Ls8, Color.ForestGreen },
|
||||||
|
// { XivChatType.TellIncoming, Color.HotPink },
|
||||||
|
// { XivChatType.PvPTeam, Color.SandyBrown },
|
||||||
|
// { XivChatType.Urgent, Color.DarkViolet },
|
||||||
|
// { XivChatType.NoviceNetwork, Color.SaddleBrown },
|
||||||
|
// { XivChatType.Echo, Color.Gray },
|
||||||
|
// };
|
||||||
|
|
||||||
|
private readonly Regex rmtRegex = new(
|
||||||
|
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||||
{
|
{
|
||||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
|
||||||
// {
|
|
||||||
// { "", "<:ffxive071:585847382210642069>" },
|
|
||||||
// { "", "<:ffxive083:585848592699490329>" },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
|
|
||||||
// {
|
|
||||||
// { XivChatType.CrossParty, Color.DodgerBlue },
|
|
||||||
// { XivChatType.Party, Color.DodgerBlue },
|
|
||||||
// { XivChatType.FreeCompany, Color.DeepSkyBlue },
|
|
||||||
// { XivChatType.CrossLinkShell1, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell2, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell3, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell4, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell5, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell6, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell7, Color.ForestGreen },
|
|
||||||
// { XivChatType.CrossLinkShell8, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls1, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls2, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls3, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls4, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls5, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls6, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls7, Color.ForestGreen },
|
|
||||||
// { XivChatType.Ls8, Color.ForestGreen },
|
|
||||||
// { XivChatType.TellIncoming, Color.HotPink },
|
|
||||||
// { XivChatType.PvPTeam, Color.SandyBrown },
|
|
||||||
// { XivChatType.Urgent, Color.DarkViolet },
|
|
||||||
// { XivChatType.NoviceNetwork, Color.SaddleBrown },
|
|
||||||
// { XivChatType.Echo, Color.Gray },
|
|
||||||
// };
|
|
||||||
|
|
||||||
private readonly Regex rmtRegex = new(
|
|
||||||
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
|
||||||
{
|
{
|
||||||
|
ClientLanguage.Japanese,
|
||||||
|
new Regex[]
|
||||||
{
|
{
|
||||||
ClientLanguage.Japanese,
|
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||||
new Regex[]
|
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||||
{
|
|
||||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
|
||||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.English,
|
|
||||||
new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.German,
|
|
||||||
new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
|
||||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.French,
|
|
||||||
new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
|
||||||
private bool hasAutoUpdatedPlugins;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private ChatHandlers(ChatGui chatGui)
|
|
||||||
{
|
|
||||||
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
|
||||||
chatGui.ChatMessage += this.OnChatMessage;
|
|
||||||
|
|
||||||
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
|
||||||
{
|
|
||||||
Service<DalamudInterface>.GetNullable()?.OpenPluginInstaller();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last URL seen in chat.
|
|
||||||
/// </summary>
|
|
||||||
public string? LastLink { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Text to convert.</param>
|
|
||||||
/// <returns>SeString payload of italicized text.</returns>
|
|
||||||
public static SeString MakeItalics(string text)
|
|
||||||
=> MakeItalics(new TextPayload(text));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Text to convert.</param>
|
|
||||||
/// <returns>SeString payload of italicized text.</returns>
|
|
||||||
public static SeString MakeItalics(TextPayload text)
|
|
||||||
=> new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
|
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
|
||||||
{
|
|
||||||
var textVal = message.TextValue;
|
|
||||||
|
|
||||||
if (!this.configuration.DisableRmtFiltering)
|
|
||||||
{
|
|
||||||
var matched = this.rmtRegex.IsMatch(textVal);
|
|
||||||
if (matched)
|
|
||||||
{
|
|
||||||
// This seems to be a RMT ad - let's not show it
|
|
||||||
Log.Debug("Handled RMT ad: " + message.TextValue);
|
|
||||||
isHandled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if (this.configuration.BadWords != null &&
|
{
|
||||||
this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
ClientLanguage.English,
|
||||||
|
new Regex[]
|
||||||
{
|
{
|
||||||
// This seems to be in the user block list - let's not show it
|
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
||||||
Log.Debug("Blocklist triggered");
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.German,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
||||||
|
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.French,
|
||||||
|
new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
private bool hasSeenLoadingMsg;
|
||||||
|
private bool hasAutoUpdatedPlugins;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private ChatHandlers(ChatGui chatGui)
|
||||||
|
{
|
||||||
|
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
|
chatGui.ChatMessage += this.OnChatMessage;
|
||||||
|
|
||||||
|
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||||
|
{
|
||||||
|
Service<DalamudInterface>.GetNullable()?.OpenPluginInstaller();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last URL seen in chat.
|
||||||
|
/// </summary>
|
||||||
|
public string? LastLink { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to convert.</param>
|
||||||
|
/// <returns>SeString payload of italicized text.</returns>
|
||||||
|
public static SeString MakeItalics(string text)
|
||||||
|
=> MakeItalics(new TextPayload(text));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to convert.</param>
|
||||||
|
/// <returns>SeString payload of italicized text.</returns>
|
||||||
|
public static SeString MakeItalics(TextPayload text)
|
||||||
|
=> new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
|
||||||
|
|
||||||
|
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
|
{
|
||||||
|
var textVal = message.TextValue;
|
||||||
|
|
||||||
|
if (!this.configuration.DisableRmtFiltering)
|
||||||
|
{
|
||||||
|
var matched = this.rmtRegex.IsMatch(textVal);
|
||||||
|
if (matched)
|
||||||
|
{
|
||||||
|
// This seems to be a RMT ad - let's not show it
|
||||||
|
Log.Debug("Handled RMT ad: " + message.TextValue);
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
if (this.configuration.BadWords != null &&
|
||||||
|
this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
// This seems to be in the user block list - let's not show it
|
||||||
var clientState = Service<ClientState.ClientState>.GetNullable();
|
Log.Debug("Blocklist triggered");
|
||||||
if (clientState == null)
|
isHandled = true;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
this.PrintWelcomeMessage();
|
{
|
||||||
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
|
if (clientState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// For injections while logged in
|
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
||||||
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
this.PrintWelcomeMessage();
|
||||||
this.PrintWelcomeMessage();
|
|
||||||
|
|
||||||
if (!this.hasAutoUpdatedPlugins)
|
// For injections while logged in
|
||||||
this.AutoUpdatePlugins();
|
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||||
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
|
if (!this.hasAutoUpdatedPlugins)
|
||||||
|
this.AutoUpdatePlugins();
|
||||||
|
|
||||||
#if !DEBUG && false
|
#if !DEBUG && false
|
||||||
if (!this.hasSeenLoadingMsg)
|
if (!this.hasSeenLoadingMsg)
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (type == XivChatType.RetainerSale)
|
if (type == XivChatType.RetainerSale)
|
||||||
|
{
|
||||||
|
foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
|
||||||
{
|
{
|
||||||
foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
|
var matchInfo = regex.Match(message.TextValue);
|
||||||
|
|
||||||
|
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
||||||
|
// but we'd be checking the main match anyway
|
||||||
|
var itemInfo = matchInfo.Groups["item"];
|
||||||
|
if (!itemInfo.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||||
|
if (itemLink == default)
|
||||||
{
|
{
|
||||||
var matchInfo = regex.Match(message.TextValue);
|
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
||||||
|
|
||||||
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
|
||||||
// but we'd be checking the main match anyway
|
|
||||||
var itemInfo = matchInfo.Groups["item"];
|
|
||||||
if (!itemInfo.Success)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
|
||||||
if (itemLink == default)
|
|
||||||
{
|
|
||||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
|
|
||||||
|
|
||||||
var valueInfo = matchInfo.Groups["value"];
|
|
||||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
|
||||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
|
||||||
|
|
||||||
|
var valueInfo = matchInfo.Groups["value"];
|
||||||
|
// not sure if using a culture here would work correctly, so just strip symbols instead
|
||||||
|
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageCopy = message;
|
|
||||||
var senderCopy = sender;
|
|
||||||
|
|
||||||
var linkMatch = this.urlRegex.Match(message.TextValue);
|
|
||||||
if (linkMatch.Value.Length > 0)
|
|
||||||
this.LastLink = linkMatch.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintWelcomeMessage()
|
var messageCopy = message;
|
||||||
|
var senderCopy = sender;
|
||||||
|
|
||||||
|
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||||
|
if (linkMatch.Value.Length > 0)
|
||||||
|
this.LastLink = linkMatch.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintWelcomeMessage()
|
||||||
|
{
|
||||||
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
|
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
||||||
|
|
||||||
|
if (chatGui == null || pluginManager == null || dalamudInterface == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
||||||
|
|
||||||
|
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
||||||
|
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
||||||
|
|
||||||
|
if (this.configuration.PrintPluginsWelcomeMsg)
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.GetNullable();
|
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
||||||
var pluginManager = Service<PluginManager>.GetNullable();
|
|
||||||
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
|
||||||
|
|
||||||
if (chatGui == null || pluginManager == null || dalamudInterface == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
|
||||||
|
|
||||||
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
|
||||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
|
||||||
|
|
||||||
if (this.configuration.PrintPluginsWelcomeMsg)
|
|
||||||
{
|
{
|
||||||
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
|
||||||
{
|
|
||||||
chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
|
||||||
{
|
|
||||||
chatGui.PrintChat(new XivChatEntry
|
|
||||||
{
|
|
||||||
Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
|
|
||||||
Type = XivChatType.Notice,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
|
||||||
{
|
|
||||||
dalamudInterface.OpenChangelogWindow();
|
|
||||||
this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.configuration.LastVersion = assemblyVersion;
|
|
||||||
this.configuration.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasSeenLoadingMsg = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoUpdatePlugins()
|
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.GetNullable();
|
chatGui.PrintChat(new XivChatEntry
|
||||||
var pluginManager = Service<PluginManager>.GetNullable();
|
|
||||||
var notifications = Service<NotificationManager>.GetNullable();
|
|
||||||
|
|
||||||
if (chatGui == null || pluginManager == null || notifications == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
|
||||||
{
|
{
|
||||||
// Plugins aren't ready yet.
|
Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
|
||||||
return;
|
Type = XivChatType.Notice,
|
||||||
}
|
|
||||||
|
|
||||||
this.hasAutoUpdatedPlugins = true;
|
|
||||||
|
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
|
||||||
{
|
|
||||||
if (task.IsFaulted)
|
|
||||||
{
|
|
||||||
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedPlugins = task.Result;
|
|
||||||
if (updatedPlugins.Any())
|
|
||||||
{
|
|
||||||
if (this.configuration.AutoUpdatePlugins)
|
|
||||||
{
|
|
||||||
Service<PluginManager>.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
|
||||||
notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
chatGui.PrintChat(new XivChatEntry
|
|
||||||
{
|
|
||||||
Message = new SeString(new List<Payload>()
|
|
||||||
{
|
|
||||||
new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
|
|
||||||
new TextPayload(" ["),
|
|
||||||
new UIForegroundPayload(500),
|
|
||||||
this.openInstallerWindowLink,
|
|
||||||
new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")),
|
|
||||||
RawPayload.LinkTerminator,
|
|
||||||
new UIForegroundPayload(0),
|
|
||||||
new TextPayload("]"),
|
|
||||||
}),
|
|
||||||
Type = XivChatType.Urgent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
||||||
|
{
|
||||||
|
dalamudInterface.OpenChangelogWindow();
|
||||||
|
this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configuration.LastVersion = assemblyVersion;
|
||||||
|
this.configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasSeenLoadingMsg = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoUpdatePlugins()
|
||||||
|
{
|
||||||
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
|
var notifications = Service<NotificationManager>.GetNullable();
|
||||||
|
|
||||||
|
if (chatGui == null || pluginManager == null || notifications == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
||||||
|
{
|
||||||
|
// Plugins aren't ready yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasAutoUpdatedPlugins = true;
|
||||||
|
|
||||||
|
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsFaulted)
|
||||||
|
{
|
||||||
|
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedPlugins = task.Result;
|
||||||
|
if (updatedPlugins.Any())
|
||||||
|
{
|
||||||
|
if (this.configuration.AutoUpdatePlugins)
|
||||||
|
{
|
||||||
|
Service<PluginManager>.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
||||||
|
notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chatGui.PrintChat(new XivChatEntry
|
||||||
|
{
|
||||||
|
Message = new SeString(new List<Payload>()
|
||||||
|
{
|
||||||
|
new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
|
||||||
|
new TextPayload(" ["),
|
||||||
|
new UIForegroundPayload(500),
|
||||||
|
this.openInstallerWindowLink,
|
||||||
|
new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")),
|
||||||
|
RawPayload.LinkTerminator,
|
||||||
|
new UIForegroundPayload(0),
|
||||||
|
new TextPayload("]"),
|
||||||
|
}),
|
||||||
|
Type = XivChatType.Urgent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,71 @@
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Aetherytes
|
namespace Dalamud.Game.ClientState.Aetherytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an entry in the Aetheryte list.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AetheryteEntry
|
||||||
{
|
{
|
||||||
|
private readonly TeleportInfo data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents an entry in the Aetheryte list.
|
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AetheryteEntry
|
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||||
|
internal AetheryteEntry(TeleportInfo data)
|
||||||
{
|
{
|
||||||
private readonly TeleportInfo data;
|
this.data = data;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
|
||||||
internal AetheryteEntry(TeleportInfo data)
|
|
||||||
{
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Aetheryte ID.
|
|
||||||
/// </summary>
|
|
||||||
public uint AetheryteId => this.data.AetheryteId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Territory ID.
|
|
||||||
/// </summary>
|
|
||||||
public uint TerritoryId => this.data.TerritoryId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
|
|
||||||
/// </summary>
|
|
||||||
public byte SubIndex => this.data.SubIndex;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Ward. Zero if not a Shared Estate.
|
|
||||||
/// </summary>
|
|
||||||
public byte Ward => this.data.Ward;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Plot. Zero if not a Shared Estate.
|
|
||||||
/// </summary>
|
|
||||||
public byte Plot => this.data.Plot;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Cost in Gil to Teleport to this location.
|
|
||||||
/// </summary>
|
|
||||||
public uint GilCost => this.data.GilCost;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsFavourite => this.data.IsFavourite != 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSharedHouse => this.data.IsSharedHouse;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this Aetheryte is an Appartment or not.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAppartment => this.data.IsAppartment;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Aetheryte data related to this aetheryte.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Aetheryte ID.
|
||||||
|
/// </summary>
|
||||||
|
public uint AetheryteId => this.data.AetheryteId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Territory ID.
|
||||||
|
/// </summary>
|
||||||
|
public uint TerritoryId => this.data.TerritoryId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
|
||||||
|
/// </summary>
|
||||||
|
public byte SubIndex => this.data.SubIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Ward. Zero if not a Shared Estate.
|
||||||
|
/// </summary>
|
||||||
|
public byte Ward => this.data.Ward;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Plot. Zero if not a Shared Estate.
|
||||||
|
/// </summary>
|
||||||
|
public byte Plot => this.data.Plot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Cost in Gil to Teleport to this location.
|
||||||
|
/// </summary>
|
||||||
|
public uint GilCost => this.data.GilCost;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFavourite => this.data.IsFavourite != 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSharedHouse => this.data.IsSharedHouse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this Aetheryte is an Appartment or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAppartment => this.data.IsAppartment;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Aetheryte data related to this aetheryte.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,105 +7,104 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Aetherytes
|
namespace Dalamud.Game.ClientState.Aetherytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed partial class AetheryteList : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
[ServiceManager.ServiceDependency]
|
||||||
/// This collection represents the list of available Aetherytes in the Teleport window.
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
/// </summary>
|
private readonly ClientStateAddressResolver address;
|
||||||
[PluginInterface]
|
private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.ServiceConstructor]
|
||||||
public sealed partial class AetheryteList : IServiceType
|
private AetheryteList()
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
this.address = this.clientState.AddressResolver;
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList);
|
||||||
private readonly ClientStateAddressResolver address;
|
|
||||||
private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
|
||||||
private AetheryteList()
|
}
|
||||||
|
|
||||||
|
private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Aetherytes the local player has unlocked.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
this.address = this.clientState.AddressResolver;
|
|
||||||
this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList);
|
|
||||||
|
|
||||||
Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Aetherytes the local player has unlocked.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe int Length
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.clientState.LocalPlayer == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
this.Update();
|
|
||||||
|
|
||||||
if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return (int)TelepoStruct->TeleportList.Size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a Aetheryte Entry at the specified index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index.</param>
|
|
||||||
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
|
|
||||||
public unsafe AetheryteEntry? this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= this.Length)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.clientState.LocalPlayer == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
// this is very very important as otherwise it crashes
|
|
||||||
if (this.clientState.LocalPlayer == null)
|
if (this.clientState.LocalPlayer == null)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
this.updateAetheryteListFunc(this.address.Telepo, 0);
|
this.Update();
|
||||||
|
|
||||||
|
if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (int)TelepoStruct->TeleportList.Size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the list of available Aetherytes in the Teleport window.
|
/// Gets a Aetheryte Entry at the specified index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AetheryteList : IReadOnlyCollection<AetheryteEntry>
|
/// <param name="index">Index.</param>
|
||||||
|
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
|
||||||
|
public unsafe AetheryteEntry? this[int index]
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
get
|
||||||
public int Count => this.Length;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IEnumerator<AetheryteEntry> GetEnumerator()
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
if (index < 0 || index >= this.Length)
|
||||||
{
|
{
|
||||||
yield return this[i];
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (this.clientState.LocalPlayer == null)
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
return null;
|
||||||
{
|
|
||||||
return this.GetEnumerator();
|
return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// this is very very important as otherwise it crashes
|
||||||
|
if (this.clientState.LocalPlayer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.updateAetheryteListFunc(this.address.Telepo, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AetheryteList : IReadOnlyCollection<AetheryteEntry>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count => this.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<AetheryteEntry> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return this.GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,179 +7,178 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Buddy
|
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>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed partial class BuddyList : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const uint InvalidObjectID = 0xE0000000;
|
||||||
/// This collection represents the buddies present in your squadron or trust party.
|
|
||||||
/// It does not include the local player.
|
[ServiceManager.ServiceDependency]
|
||||||
/// </summary>
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
private readonly ClientStateAddressResolver address;
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed partial class BuddyList : IServiceType
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private BuddyList()
|
||||||
{
|
{
|
||||||
private const uint InvalidObjectID = 0xE0000000;
|
this.address = this.clientState.AddressResolver;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
}
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
/// <summary>
|
||||||
|
/// Gets the amount of battle buddies the local player has.
|
||||||
[ServiceManager.ServiceConstructor]
|
/// </summary>
|
||||||
private BuddyList()
|
public int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
this.address = this.clientState.AddressResolver;
|
var i = 0;
|
||||||
|
for (; i < 3; i++)
|
||||||
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;
|
var addr = this.GetBattleBuddyMemberAddress(i);
|
||||||
for (; i < 3; i++)
|
var member = this.CreateBuddyMemberReference(addr);
|
||||||
{
|
if (member == null)
|
||||||
var addr = this.GetBattleBuddyMemberAddress(i);
|
break;
|
||||||
var member = this.CreateBuddyMemberReference(addr);
|
|
||||||
if (member == null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return i;
|
||||||
/// 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>
|
|
||||||
public BuddyMember? CompanionBuddy
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var addr = this.GetCompanionBuddyMemberAddress();
|
|
||||||
return this.CreateBuddyMemberReference(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the active pet buddy.
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
|
||||||
{
|
|
||||||
if (this.clientState.LocalContentId == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var buddy = new BuddyMember(address);
|
|
||||||
if (buddy.ObjectId == InvalidObjectID)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return buddy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the buddies present in your squadron or trust party.
|
/// Gets a value indicating whether the local player's companion is present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
|
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>
|
||||||
|
public BuddyMember? CompanionBuddy
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
get
|
||||||
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IEnumerator<BuddyMember> GetEnumerator()
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
var addr = this.GetCompanionBuddyMemberAddress();
|
||||||
{
|
return this.CreateBuddyMemberReference(addr);
|
||||||
yield return this[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
/// Gets the active pet buddy.
|
||||||
|
/// </summary>
|
||||||
|
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>
|
||||||
|
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>
|
||||||
|
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||||
|
{
|
||||||
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (address == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var buddy = new BuddyMember(address);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,73 +4,72 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Buddy
|
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
|
||||||
{
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BuddyMember
|
/// <param name="address">Buddy address.</param>
|
||||||
|
internal BuddyMember(IntPtr address)
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
this.Address = address;
|
||||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Buddy address.</param>
|
|
||||||
internal BuddyMember(IntPtr address)
|
|
||||||
{
|
|
||||||
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>
|
|
||||||
public GameObject? GameObject => this.objectTable.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);
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.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>
|
||||||
|
public GameObject? GameObject => this.objectTable.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);
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
||||||
|
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,193 +13,192 @@ using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents the state of the game client at the time of access.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed class ClientState : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly ClientStateAddressResolver address;
|
||||||
/// This class represents the state of the game client at the time of access.
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
/// </summary>
|
|
||||||
[PluginInterface]
|
[ServiceManager.ServiceDependency]
|
||||||
[InterfaceVersion("1.0")]
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed class ClientState : IDisposable, IServiceType
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||||
|
|
||||||
|
private bool lastConditionNone = true;
|
||||||
|
private bool lastFramePvP = false;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo)
|
||||||
{
|
{
|
||||||
private readonly ClientStateAddressResolver address;
|
this.address = new ClientStateAddressResolver();
|
||||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
this.ClientLanguage = startInfo.Language;
|
||||||
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
|
||||||
|
|
||||||
private bool lastConditionNone = true;
|
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||||
private bool lastFramePvP = false;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||||
private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo)
|
|
||||||
|
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||||
|
|
||||||
|
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that gets fired when the current Territory changes.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ushort> TerritoryChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when a character is logging in.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler Login;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when a character is logging out.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler Logout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when a character is entering PvP.
|
||||||
|
/// </summary>
|
||||||
|
public event Action EnterPvP;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when a character is leaving PvP.
|
||||||
|
/// </summary>
|
||||||
|
public event Action LeavePvP;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that gets fired when a duty is ready.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<Lumina.Excel.GeneratedSheets.ContentFinderCondition> CfPop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the language of the client.
|
||||||
|
/// </summary>
|
||||||
|
public ClientLanguage ClientLanguage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current Territory the player resides in.
|
||||||
|
/// </summary>
|
||||||
|
public ushort TerritoryType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local player character, if one is present.
|
||||||
|
/// </summary>
|
||||||
|
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as PlayerCharacter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content ID of the local character.
|
||||||
|
/// </summary>
|
||||||
|
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether a character is logged in.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLoggedIn { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not the user is playing PvP.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPvP { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPvPExcludingDen { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets client state address resolver.
|
||||||
|
/// </summary>
|
||||||
|
internal ClientStateAddressResolver AddressResolver => this.address;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
|
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||||
|
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ServiceManager.CallWhenServicesReady]
|
||||||
|
private void ContinueConstruction()
|
||||||
|
{
|
||||||
|
this.setupTerritoryTypeHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
||||||
|
{
|
||||||
|
this.TerritoryType = terriType;
|
||||||
|
this.TerritoryChanged?.InvokeSafely(this, terriType);
|
||||||
|
|
||||||
|
Log.Debug("TerritoryType changed: {0}", terriType);
|
||||||
|
|
||||||
|
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
|
||||||
|
{
|
||||||
|
this.CfPop?.InvokeSafely(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkOnOnUpdateEvent(Framework framework1)
|
||||||
|
{
|
||||||
|
var condition = Service<Conditions.Condition>.GetNullable();
|
||||||
|
var gameGui = Service<GameGui>.GetNullable();
|
||||||
|
var data = Service<DataManager>.GetNullable();
|
||||||
|
|
||||||
|
if (condition == null || gameGui == null || data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (condition.Any() && this.lastConditionNone == true)
|
||||||
{
|
{
|
||||||
this.address = new ClientStateAddressResolver();
|
Log.Debug("Is login");
|
||||||
this.address.Setup(sigScanner);
|
this.lastConditionNone = false;
|
||||||
|
this.IsLoggedIn = true;
|
||||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
this.Login?.InvokeSafely(this, null);
|
||||||
|
gameGui.ResetUiHideState();
|
||||||
this.ClientLanguage = startInfo.Language;
|
|
||||||
|
|
||||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
|
||||||
|
|
||||||
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
|
||||||
|
|
||||||
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
|
||||||
|
|
||||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
if (!condition.Any() && this.lastConditionNone == false)
|
||||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that gets fired when the current Territory changes.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ushort> TerritoryChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when a character is logging in.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler Login;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when a character is logging out.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler Logout;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when a character is entering PvP.
|
|
||||||
/// </summary>
|
|
||||||
public event Action EnterPvP;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when a character is leaving PvP.
|
|
||||||
/// </summary>
|
|
||||||
public event Action LeavePvP;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that gets fired when a duty is ready.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<Lumina.Excel.GeneratedSheets.ContentFinderCondition> CfPop;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the language of the client.
|
|
||||||
/// </summary>
|
|
||||||
public ClientLanguage ClientLanguage { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current Territory the player resides in.
|
|
||||||
/// </summary>
|
|
||||||
public ushort TerritoryType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the local player character, if one is present.
|
|
||||||
/// </summary>
|
|
||||||
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as PlayerCharacter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the content ID of the local character.
|
|
||||||
/// </summary>
|
|
||||||
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether a character is logged in.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLoggedIn { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether or not the user is playing PvP.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPvP { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPvPExcludingDen { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets client state address resolver.
|
|
||||||
/// </summary>
|
|
||||||
internal ClientStateAddressResolver AddressResolver => this.address;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose of managed and unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
{
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
Log.Debug("Is logout");
|
||||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
this.lastConditionNone = true;
|
||||||
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
this.IsLoggedIn = false;
|
||||||
|
this.Logout?.InvokeSafely(this, null);
|
||||||
|
gameGui.ResetUiHideState();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
this.IsPvP = GameMain.IsInPvPArea();
|
||||||
private void ContinueConstruction()
|
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
||||||
|
|
||||||
|
if (this.IsPvP != this.lastFramePvP)
|
||||||
{
|
{
|
||||||
this.setupTerritoryTypeHook.Enable();
|
this.lastFramePvP = this.IsPvP;
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
if (this.IsPvP)
|
||||||
{
|
|
||||||
this.TerritoryType = terriType;
|
|
||||||
this.TerritoryChanged?.InvokeSafely(this, terriType);
|
|
||||||
|
|
||||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
|
||||||
|
|
||||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
|
|
||||||
{
|
|
||||||
this.CfPop?.InvokeSafely(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FrameworkOnOnUpdateEvent(Framework framework1)
|
|
||||||
{
|
|
||||||
var condition = Service<Conditions.Condition>.GetNullable();
|
|
||||||
var gameGui = Service<GameGui>.GetNullable();
|
|
||||||
var data = Service<DataManager>.GetNullable();
|
|
||||||
|
|
||||||
if (condition == null || gameGui == null || data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (condition.Any() && this.lastConditionNone == true)
|
|
||||||
{
|
{
|
||||||
Log.Debug("Is login");
|
this.EnterPvP?.InvokeSafely();
|
||||||
this.lastConditionNone = false;
|
|
||||||
this.IsLoggedIn = true;
|
|
||||||
this.Login?.InvokeSafely(this, null);
|
|
||||||
gameGui.ResetUiHideState();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!condition.Any() && this.lastConditionNone == false)
|
|
||||||
{
|
{
|
||||||
Log.Debug("Is logout");
|
this.LeavePvP?.InvokeSafely();
|
||||||
this.lastConditionNone = true;
|
|
||||||
this.IsLoggedIn = false;
|
|
||||||
this.Logout?.InvokeSafely(this, null);
|
|
||||||
gameGui.ResetUiHideState();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.IsPvP = GameMain.IsInPvPArea();
|
|
||||||
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
|
||||||
|
|
||||||
if (this.IsPvP != this.lastFramePvP)
|
|
||||||
{
|
|
||||||
this.lastFramePvP = this.IsPvP;
|
|
||||||
|
|
||||||
if (this.IsPvP)
|
|
||||||
{
|
|
||||||
this.EnterPvP?.InvokeSafely();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.LeavePvP?.InvokeSafely();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,128 +1,127 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
namespace Dalamud.Game.ClientState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client state memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
|
// Static offsets
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client state memory address resolver.
|
/// Gets the address of the actor table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
public IntPtr ObjectTable { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the buddy list.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr BuddyList { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of a pointer to the fate table.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is a static address to a pointer, not the address of the table itself.
|
||||||
|
/// </remarks>
|
||||||
|
public IntPtr FateTablePtr { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the Group Manager.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr GroupManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the local content id.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr LocalContentId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of job gauge data.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr JobGaugeData { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the keyboard state.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr KeyboardState { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr KeyboardStateIndexArray { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the target manager.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr TargetManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the condition flag array.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr ConditionFlags { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the Telepo instance.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Telepo { get; private set; }
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the method which sets the territory type.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr SetupTerritoryType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the method which polls the gamepads for data.
|
||||||
|
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr GamepadPoll { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the method which updates the list of available teleport locations.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr UpdateAetheryteList { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scan for and setup any configured address pointers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
// Static offsets
|
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
|
||||||
|
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
|
||||||
|
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
||||||
|
|
||||||
/// <summary>
|
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
|
||||||
/// Gets the address of the actor table.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ObjectTable { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
|
||||||
/// Gets the address of the buddy list.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr BuddyList { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
|
||||||
/// Gets the address of a pointer to the fate table.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is a static address to a pointer, not the address of the table itself.
|
|
||||||
/// </remarks>
|
|
||||||
public IntPtr FateTablePtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
|
||||||
/// Gets the address of the Group Manager.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr GroupManager { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
||||||
/// Gets the address of the local content id.
|
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
|
||||||
/// </summary>
|
|
||||||
public IntPtr LocalContentId { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
||||||
/// Gets the address of job gauge data.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr JobGaugeData { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
||||||
/// Gets the address of the keyboard state.
|
// lea rcx, ds:1DB9F74h[rax*4] KeyboardState
|
||||||
/// </summary>
|
// movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
|
||||||
public IntPtr KeyboardState { get; private set; }
|
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
||||||
|
this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
|
||||||
|
|
||||||
/// <summary>
|
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
||||||
/// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr KeyboardStateIndexArray { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
|
||||||
/// Gets the address of the target manager.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr TargetManager { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
|
||||||
/// Gets the address of the condition flag array.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ConditionFlags { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12");
|
||||||
/// Gets the address of the Telepo instance.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Telepo { get; private set; }
|
|
||||||
|
|
||||||
// Functions
|
this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50");
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the method which sets the territory type.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr SetupTerritoryType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the method which polls the gamepads for data.
|
|
||||||
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr GamepadPoll { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the method which updates the list of available teleport locations.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr UpdateAetheryteList { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for and setup any configured address pointers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
|
|
||||||
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
|
|
||||||
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
|
|
||||||
|
|
||||||
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.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
|
|
||||||
|
|
||||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
|
|
||||||
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
|
|
||||||
|
|
||||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
|
|
||||||
|
|
||||||
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
|
||||||
// lea rcx, ds:1DB9F74h[rax*4] KeyboardState
|
|
||||||
// movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
|
|
||||||
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
|
||||||
this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
|
|
||||||
|
|
||||||
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
|
|
||||||
|
|
||||||
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.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12");
|
|
||||||
|
|
||||||
this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,152 +4,151 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Conditions
|
namespace Dalamud.Game.ClientState.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed partial class Condition : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
public const int MaxConditionEntries = 100;
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||||
public sealed partial class Condition : IServiceType
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private Condition(ClientState clientState)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var resolver = clientState.AddressResolver;
|
||||||
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
this.Address = resolver.ConditionFlags;
|
||||||
/// </summary>
|
}
|
||||||
public const int MaxConditionEntries = 100;
|
|
||||||
|
|
||||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="ConditionChange"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The changed condition.</param>
|
||||||
|
/// <param name="value">The value the condition is set to.</param>
|
||||||
|
public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
/// <summary>
|
||||||
private Condition(ClientState clientState)
|
/// Event that gets fired when a condition is set.
|
||||||
|
/// Should only get fired for actual changes, so the previous value will always be !value.
|
||||||
|
/// </summary>
|
||||||
|
public event ConditionChangeDelegate? ConditionChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the condition array base pointer.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check the value of a specific condition/state flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The condition flag to check.</param>
|
||||||
|
public unsafe bool this[int flag]
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
var resolver = clientState.AddressResolver;
|
if (flag < 0 || flag >= MaxConditionEntries)
|
||||||
this.Address = resolver.ConditionFlags;
|
return false;
|
||||||
|
|
||||||
|
return *(bool*)(this.Address + flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="this[int]"/>
|
||||||
|
public unsafe bool this[ConditionFlag flag]
|
||||||
|
=> this[(int)flag];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any condition flags are set.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether any single flag is set.</returns>
|
||||||
|
public bool Any()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
|
{
|
||||||
|
var cond = this[i];
|
||||||
|
|
||||||
|
if (cond)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return false;
|
||||||
/// A delegate type used with the <see cref="ConditionChange"/> event.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="flag">The changed condition.</param>
|
|
||||||
/// <param name="value">The value the condition is set to.</param>
|
|
||||||
public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
|
|
||||||
|
|
||||||
/// <summary>
|
[ServiceManager.CallWhenServicesReady]
|
||||||
/// Event that gets fired when a condition is set.
|
private void ContinueConstruction(Framework framework)
|
||||||
/// Should only get fired for actual changes, so the previous value will always be !value.
|
{
|
||||||
/// </summary>
|
// Initialization
|
||||||
public event ConditionChangeDelegate? ConditionChange;
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
|
this.cache[i] = this[i];
|
||||||
|
|
||||||
/// <summary>
|
framework.Update += this.FrameworkUpdate;
|
||||||
/// Gets the condition array base pointer.
|
}
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
private void FrameworkUpdate(Framework framework)
|
||||||
/// Check the value of a specific condition/state flag.
|
{
|
||||||
/// </summary>
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
/// <param name="flag">The condition flag to check.</param>
|
|
||||||
public unsafe bool this[int flag]
|
|
||||||
{
|
{
|
||||||
get
|
var value = this[i];
|
||||||
|
|
||||||
|
if (value != this.cache[i])
|
||||||
{
|
{
|
||||||
if (flag < 0 || flag >= MaxConditionEntries)
|
this.cache[i] = value;
|
||||||
return false;
|
|
||||||
|
|
||||||
return *(bool*)(this.Address + flag);
|
try
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="this[int]"/>
|
|
||||||
public unsafe bool this[ConditionFlag flag]
|
|
||||||
=> this[(int)flag];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if any condition flags are set.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Whether any single flag is set.</returns>
|
|
||||||
public bool Any()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
|
||||||
{
|
|
||||||
var cond = this[i];
|
|
||||||
|
|
||||||
if (cond)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(Framework framework)
|
|
||||||
{
|
|
||||||
// Initialization
|
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
|
||||||
this.cache[i] = this[i];
|
|
||||||
|
|
||||||
framework.Update += this.FrameworkUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FrameworkUpdate(Framework framework)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
|
||||||
{
|
|
||||||
var value = this[i];
|
|
||||||
|
|
||||||
if (value != this.cache[i])
|
|
||||||
{
|
{
|
||||||
this.cache[i] = value;
|
this.ConditionChange?.Invoke((ConditionFlag)i, value);
|
||||||
|
}
|
||||||
try
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.ConditionChange?.Invoke((ConditionFlag)i, value);
|
Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown.");
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class Condition : IDisposable
|
||||||
|
{
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
/// Finalizes an instance of the <see cref="Condition" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class Condition : IDisposable
|
~Condition()
|
||||||
{
|
{
|
||||||
private bool isDisposed;
|
this.Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
/// Disposes this instance, alongside its hooks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
~Condition()
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
this.Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
{
|
{
|
||||||
this.Dispose(false);
|
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.isDisposed = true;
|
||||||
/// Disposes this instance, alongside its hooks.
|
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
this.Dispose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (this.isDisposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,468 +1,467 @@
|
||||||
namespace Dalamud.Game.ClientState.Conditions
|
namespace Dalamud.Game.ClientState.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Possible state flags (or conditions as they're called internally) that can be set on the local client.
|
||||||
|
///
|
||||||
|
/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to
|
||||||
|
/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to.
|
||||||
|
/// </summary>
|
||||||
|
public enum ConditionFlag
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Possible state flags (or conditions as they're called internally) that can be set on the local client.
|
/// Unused.
|
||||||
///
|
|
||||||
/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to
|
|
||||||
/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ConditionFlag
|
None = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unused.
|
/// Unable to execute command under normal conditions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
NormalConditions = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command under normal conditions.
|
/// Unable to execute command while unconscious.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NormalConditions = 1,
|
Unconscious = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while unconscious.
|
/// Unable to execute command during an emote.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unconscious = 2,
|
Emoting = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command during an emote.
|
/// Unable to execute command while mounted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Emoting = 3,
|
Mounted = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while mounted.
|
/// Unable to execute command while crafting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Mounted = 4,
|
Crafting = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while crafting.
|
/// Unable to execute command while gathering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Crafting = 5,
|
Gathering = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while gathering.
|
/// Unable to execute command while melding materia.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Gathering = 6,
|
MeldingMateria = 7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while melding materia.
|
/// Unable to execute command while operating a siege machine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MeldingMateria = 7,
|
OperatingSiegeMachine = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while operating a siege machine.
|
/// Unable to execute command while carrying an object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OperatingSiegeMachine = 8,
|
CarryingObject = 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while carrying an object.
|
/// Unable to execute command while mounted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CarryingObject = 9,
|
Mounted2 = 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while mounted.
|
/// Unable to execute command while in that position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Mounted2 = 10,
|
InThatPosition = 11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while in that position.
|
/// Unable to execute command while chocobo racing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InThatPosition = 11,
|
ChocoboRacing = 12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while chocobo racing.
|
/// Unable to execute command while playing a mini-game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ChocoboRacing = 12,
|
PlayingMiniGame = 13,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while playing a mini-game.
|
/// Unable to execute command while playing Lord of Verminion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PlayingMiniGame = 13,
|
PlayingLordOfVerminion = 14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while playing Lord of Verminion.
|
/// Unable to execute command while participating in a custom match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PlayingLordOfVerminion = 14,
|
ParticipatingInCustomMatch = 15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while participating in a custom match.
|
/// Unable to execute command while performing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ParticipatingInCustomMatch = 15,
|
Performing = 16,
|
||||||
|
|
||||||
/// <summary>
|
// Unknown17 = 17,
|
||||||
/// Unable to execute command while performing.
|
// Unknown18 = 18,
|
||||||
/// </summary>
|
// Unknown19 = 19,
|
||||||
Performing = 16,
|
// Unknown20 = 20,
|
||||||
|
// Unknown21 = 21,
|
||||||
// Unknown17 = 17,
|
// Unknown22 = 22,
|
||||||
// Unknown18 = 18,
|
// Unknown23 = 23,
|
||||||
// Unknown19 = 19,
|
// Unknown24 = 24,
|
||||||
// Unknown20 = 20,
|
|
||||||
// Unknown21 = 21,
|
/// <summary>
|
||||||
// Unknown22 = 22,
|
/// Unable to execute command while occupied.
|
||||||
// Unknown23 = 23,
|
/// </summary>
|
||||||
// Unknown24 = 24,
|
Occupied = 25,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command during combat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Occupied = 25,
|
InCombat = 26,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command during combat.
|
/// Unable to execute command while casting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InCombat = 26,
|
Casting = 27,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while casting.
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Casting = 27,
|
SufferingStatusAffliction = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SufferingStatusAffliction = 28,
|
SufferingStatusAffliction2 = 29,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SufferingStatusAffliction2 = 29,
|
Occupied30 = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Occupied30 = 30,
|
// todo: not sure if this is used for other event states/???
|
||||||
|
OccupiedInEvent = 31,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while occupied.
|
/// <summary>
|
||||||
/// </summary>
|
/// Unable to execute command while occupied.
|
||||||
// todo: not sure if this is used for other event states/???
|
/// </summary>
|
||||||
OccupiedInEvent = 31,
|
OccupiedInQuestEvent = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OccupiedInQuestEvent = 32,
|
Occupied33 = 33,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while bound by duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Occupied33 = 33,
|
BoundByDuty = 34,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while bound by duty.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BoundByDuty = 34,
|
OccupiedInCutSceneEvent = 35,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while in a dueling area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OccupiedInCutSceneEvent = 35,
|
InDuelingArea = 36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while in a dueling area.
|
/// Unable to execute command while a trade is open.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InDuelingArea = 36,
|
TradeOpen = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while a trade is open.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TradeOpen = 37,
|
Occupied38 = 38,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Occupied38 = 38,
|
Occupied39 = 39,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while occupied.
|
/// Unable to execute command while crafting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Occupied39 = 39,
|
Crafting40 = 40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while crafting.
|
/// Unable to execute command while preparing to craft.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Crafting40 = 40,
|
PreparingToCraft = 41,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while preparing to craft.
|
/// Unable to execute command while gathering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PreparingToCraft = 41,
|
Gathering42 = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while gathering.
|
/// Unable to execute command while fishing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Gathering42 = 42,
|
Fishing = 43,
|
||||||
|
|
||||||
/// <summary>
|
// Unknown44 = 44,
|
||||||
/// Unable to execute command while fishing.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
Fishing = 43,
|
/// Unable to execute command while between areas.
|
||||||
|
/// </summary>
|
||||||
// Unknown44 = 44,
|
BetweenAreas = 45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while between areas.
|
/// Unable to execute command while stealthed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BetweenAreas = 45,
|
Stealthed = 46,
|
||||||
|
|
||||||
/// <summary>
|
// Unknown47 = 47,
|
||||||
/// Unable to execute command while stealthed.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
Stealthed = 46,
|
/// Unable to execute command while jumping.
|
||||||
|
/// </summary>
|
||||||
// Unknown47 = 47,
|
Jumping = 48,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while jumping.
|
/// Unable to execute command while auto-run is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Jumping = 48,
|
AutorunActive = 49,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while auto-run is active.
|
/// Unable to execute command while occupied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutorunActive = 49,
|
// todo: used for other shits?
|
||||||
|
OccupiedSummoningBell = 50,
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while occupied.
|
/// <summary>
|
||||||
/// </summary>
|
/// Unable to execute command while between areas.
|
||||||
// todo: used for other shits?
|
/// </summary>
|
||||||
OccupiedSummoningBell = 50,
|
BetweenAreas51 = 51,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while between areas.
|
/// Unable to execute command due to system error.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BetweenAreas51 = 51,
|
SystemError = 52,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command due to system error.
|
/// Unable to execute command while logging out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SystemError = 52,
|
LoggingOut = 53,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while logging out.
|
/// Unable to execute command at this location.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LoggingOut = 53,
|
ConditionLocation = 54,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command at this location.
|
/// Unable to execute command while waiting for duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConditionLocation = 54,
|
WaitingForDuty = 55,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting for duty.
|
/// Unable to execute command while bound by duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForDuty = 55,
|
BoundByDuty56 = 56,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while bound by duty.
|
/// Unable to execute command at this time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BoundByDuty56 = 56,
|
Unknown57 = 57,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command at this time.
|
/// Unable to execute command while watching a cutscene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unknown57 = 57,
|
WatchingCutscene = 58,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while watching a cutscene.
|
/// Unable to execute command while waiting for Duty Finder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WatchingCutscene = 58,
|
WaitingForDutyFinder = 59,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting for Duty Finder.
|
/// Unable to execute command while creating a character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForDutyFinder = 59,
|
CreatingCharacter = 60,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while creating a character.
|
/// Unable to execute command while jumping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CreatingCharacter = 60,
|
Jumping61 = 61,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while jumping.
|
/// Unable to execute command while the PvP display is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Jumping61 = 61,
|
PvPDisplayActive = 62,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while the PvP display is active.
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PvPDisplayActive = 62,
|
SufferingStatusAffliction63 = 63,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
/// Unable to execute command while mounting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SufferingStatusAffliction63 = 63,
|
Mounting = 64,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while mounting.
|
/// Unable to execute command while carrying an item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Mounting = 64,
|
CarryingItem = 65,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while carrying an item.
|
/// Unable to execute command while using the Party Finder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CarryingItem = 65,
|
UsingPartyFinder = 66,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while using the Party Finder.
|
/// Unable to execute command while using housing functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UsingPartyFinder = 66,
|
UsingHousingFunctions = 67,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while using housing functions.
|
/// Unable to execute command while transformed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UsingHousingFunctions = 67,
|
Transformed = 68,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while transformed.
|
/// Unable to execute command while on the free trial.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Transformed = 68,
|
OnFreeTrial = 69,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while on the free trial.
|
/// Unable to execute command while being moved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OnFreeTrial = 69,
|
BeingMoved = 70,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while being moved.
|
/// Unable to execute command while mounting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BeingMoved = 70,
|
Mounting71 = 71,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while mounting.
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Mounting71 = 71,
|
SufferingStatusAffliction72 = 72,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
/// Unable to execute command while suffering status affliction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SufferingStatusAffliction72 = 72,
|
SufferingStatusAffliction73 = 73,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while suffering status affliction.
|
/// Unable to execute command while registering for a race or match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SufferingStatusAffliction73 = 73,
|
RegisteringForRaceOrMatch = 74,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while registering for a race or match.
|
/// Unable to execute command while waiting for a race or match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RegisteringForRaceOrMatch = 74,
|
WaitingForRaceOrMatch = 75,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting for a race or match.
|
/// Unable to execute command while waiting for a Triple Triad match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForRaceOrMatch = 75,
|
WaitingForTripleTriadMatch = 76,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting for a Triple Triad match.
|
/// Unable to execute command while in flight.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForTripleTriadMatch = 76,
|
InFlight = 77,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while in flight.
|
/// Unable to execute command while watching a cutscene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InFlight = 77,
|
WatchingCutscene78 = 78,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while watching a cutscene.
|
/// Unable to execute command while delving into a deep dungeon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WatchingCutscene78 = 78,
|
InDeepDungeon = 79,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while delving into a deep dungeon.
|
/// Unable to execute command while swimming.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InDeepDungeon = 79,
|
Swimming = 80,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while swimming.
|
/// Unable to execute command while diving.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Swimming = 80,
|
Diving = 81,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while diving.
|
/// Unable to execute command while registering for a Triple Triad match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Diving = 81,
|
RegisteringForTripleTriadMatch = 82,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while registering for a Triple Triad match.
|
/// Unable to execute command while waiting for a Triple Triad match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RegisteringForTripleTriadMatch = 82,
|
WaitingForTripleTriadMatch83 = 83,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting for a Triple Triad match.
|
/// Unable to execute command while participating in a cross-world party or alliance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForTripleTriadMatch83 = 83,
|
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
||||||
|
|
||||||
/// <summary>
|
// Unknown85 = 85,
|
||||||
/// Unable to execute command while participating in a cross-world party or alliance.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
ParticipatingInCrossWorldPartyOrAlliance = 84,
|
/// Unable to execute command while playing duty record.
|
||||||
|
/// </summary>
|
||||||
// Unknown85 = 85,
|
DutyRecorderPlayback = 86,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while playing duty record.
|
/// Unable to execute command while casting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DutyRecorderPlayback = 86,
|
Casting87 = 87,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while casting.
|
/// Unable to execute command in this state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Casting87 = 87,
|
InThisState88 = 88,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command in this state.
|
/// Unable to execute command in this state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InThisState88 = 88,
|
InThisState89 = 89,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command in this state.
|
/// Unable to execute command while role-playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InThisState89 = 89,
|
RolePlaying = 90,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while role-playing.
|
/// Unable to execute command while bound by duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RolePlaying = 90,
|
BoundToDuty97 = 91,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while bound by duty.
|
/// Unable to execute command while readying to visit another World.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BoundToDuty97 = 91,
|
ReadyingVisitOtherWorld = 92,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while readying to visit another World.
|
/// Unable to execute command while waiting to visit another World.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ReadyingVisitOtherWorld = 92,
|
WaitingToVisitOtherWorld = 93,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while waiting to visit another World.
|
/// Unable to execute command while using a parasol.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingToVisitOtherWorld = 93,
|
UsingParasol = 94,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while using a parasol.
|
/// Unable to execute command while bound by duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UsingParasol = 94,
|
BoundByDuty95 = 95,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while bound by duty.
|
/// Cannot execute at this time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BoundByDuty95 = 95,
|
Unknown96 = 96,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cannot execute at this time.
|
/// Unable to execute command while wearing a guise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unknown96 = 96,
|
Disguised = 97,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unable to execute command while wearing a guise.
|
/// Unable to execute command while recruiting for a non-cross-world party.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disguised = 97,
|
RecruitingWorldOnly = 98,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while recruiting for a non-cross-world party.
|
|
||||||
/// </summary>
|
|
||||||
RecruitingWorldOnly = 98,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,131 +6,130 @@ using Dalamud.Game.ClientState.Resolvers;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Fates
|
namespace Dalamud.Game.ClientState.Fates;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an FFXIV Fate.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe partial class Fate : IEquatable<Fate>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents an FFXIV Fate.
|
/// Initializes a new instance of the <see cref="Fate"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe partial class Fate : IEquatable<Fate>
|
/// <param name="address">The address of this fate in memory.</param>
|
||||||
|
internal Fate(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = address;
|
||||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this fate in memory.</param>
|
|
||||||
internal Fate(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of this Fate in memory.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address { get; }
|
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
|
|
||||||
|
|
||||||
public static bool operator ==(Fate fate1, Fate fate2)
|
|
||||||
{
|
|
||||||
if (fate1 is null || fate2 is null)
|
|
||||||
return Equals(fate1, fate2);
|
|
||||||
|
|
||||||
return fate1.Equals(fate2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fate">The fate to check.</param>
|
|
||||||
/// <returns>True or false.</returns>
|
|
||||||
public static bool IsValid(Fate fate)
|
|
||||||
{
|
|
||||||
var clientState = Service<ClientState>.GetNullable();
|
|
||||||
|
|
||||||
if (fate == null || clientState == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (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<Fate>.Equals(Fate other) => this.FateId == other?.FateId;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents an FFXIV Fate.
|
/// Gets the address of this Fate in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe partial class Fate
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
|
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
|
||||||
|
|
||||||
|
public static bool operator ==(Fate fate1, Fate fate2)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (fate1 is null || fate2 is null)
|
||||||
/// Gets the Fate ID of this <see cref="Fate" />.
|
return Equals(fate1, fate2);
|
||||||
/// </summary>
|
|
||||||
public ushort FateId => this.Struct->FateId;
|
|
||||||
|
|
||||||
/// <summary>
|
return fate1.Equals(fate2);
|
||||||
/// Gets game data linked to this Fate.
|
|
||||||
/// </summary>
|
|
||||||
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time this <see cref="Fate"/> started.
|
|
||||||
/// </summary>
|
|
||||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets how long this <see cref="Fate"/> will run.
|
|
||||||
/// </summary>
|
|
||||||
public short Duration => this.Struct->Duration;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
|
|
||||||
/// </summary>
|
|
||||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the displayname of this <see cref="Fate" />.
|
|
||||||
/// </summary>
|
|
||||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
|
|
||||||
/// </summary>
|
|
||||||
public FateState State => (FateState)this.Struct->State;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the progress amount of this <see cref="Fate"/>.
|
|
||||||
/// </summary>
|
|
||||||
public byte Progress => this.Struct->Progress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the level of this <see cref="Fate"/>.
|
|
||||||
/// </summary>
|
|
||||||
public byte Level => this.Struct->Level;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the position of this <see cref="Fate"/>.
|
|
||||||
/// </summary>
|
|
||||||
public Vector3 Position => this.Struct->Location;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this Fate is still valid in memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fate">The fate to check.</param>
|
||||||
|
/// <returns>True or false.</returns>
|
||||||
|
public static bool IsValid(Fate fate)
|
||||||
|
{
|
||||||
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
|
if (fate == null || clientState == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (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<Fate>.Equals(Fate other) => this.FateId == other?.FateId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode() => this.FateId.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an FFXIV Fate.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe partial class Fate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Fate ID of this <see cref="Fate" />.
|
||||||
|
/// </summary>
|
||||||
|
public ushort FateId => this.Struct->FateId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets game data linked to this Fate.
|
||||||
|
/// </summary>
|
||||||
|
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time this <see cref="Fate"/> started.
|
||||||
|
/// </summary>
|
||||||
|
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how long this <see cref="Fate"/> will run.
|
||||||
|
/// </summary>
|
||||||
|
public short Duration => this.Struct->Duration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
|
||||||
|
/// </summary>
|
||||||
|
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the displayname of this <see cref="Fate" />.
|
||||||
|
/// </summary>
|
||||||
|
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
|
||||||
|
/// </summary>
|
||||||
|
public FateState State => (FateState)this.Struct->State;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the progress amount of this <see cref="Fate"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte Progress => this.Struct->Progress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the level of this <see cref="Fate"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte Level => this.Struct->Level;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the position of this <see cref="Fate"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Vector3 Position => this.Struct->Location;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
namespace Dalamud.Game.ClientState.Fates
|
namespace Dalamud.Game.ClientState.Fates;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This represents the state of a single Fate.
|
||||||
|
/// </summary>
|
||||||
|
public enum FateState : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This represents the state of a single Fate.
|
/// The Fate is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum FateState : byte
|
Running = 0x02,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The Fate is active.
|
|
||||||
/// </summary>
|
|
||||||
Running = 0x02,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate has ended.
|
/// The Fate has ended.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ended = 0x04,
|
Ended = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The player failed the Fate.
|
/// The player failed the Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Failed = 0x05,
|
Failed = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate is preparing to run.
|
/// The Fate is preparing to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Preparation = 0x07,
|
Preparation = 0x07,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Fate is preparing to end.
|
/// The Fate is preparing to end.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WaitingForEnd = 0x08,
|
WaitingForEnd = 0x08,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,137 +6,136 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Fates
|
namespace Dalamud.Game.ClientState.Fates;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the currently available Fate events.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed partial class FateTable : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly ClientStateAddressResolver address;
|
||||||
/// This collection represents the currently available Fate events.
|
|
||||||
/// </summary>
|
[ServiceManager.ServiceConstructor]
|
||||||
[PluginInterface]
|
private FateTable(ClientState clientState)
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed partial class FateTable : IServiceType
|
|
||||||
{
|
{
|
||||||
private readonly ClientStateAddressResolver address;
|
this.address = clientState.AddressResolver;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
||||||
private FateTable(ClientState clientState)
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the Fate table.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address => this.address.FateTablePtr;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of currently active Fates.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
this.address = clientState.AddressResolver;
|
|
||||||
|
|
||||||
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the Fate table.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address => this.address.FateTablePtr;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of currently active Fates.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe int Length
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var fateTable = this.FateTableAddress;
|
|
||||||
if (fateTable == IntPtr.Zero)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Sonar used this to check if the table was safe to read
|
|
||||||
if (Struct->FateDirector == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (Struct->Fates.First == null || Struct->Fates.Last == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return (int)Struct->Fates.Size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the Fate table.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe IntPtr FateTableAddress
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.address.FateTablePtr == IntPtr.Zero)
|
|
||||||
return IntPtr.Zero;
|
|
||||||
|
|
||||||
return *(IntPtr*)this.address.FateTablePtr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an actor at the specified spawn index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Spawn index.</param>
|
|
||||||
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
|
|
||||||
public Fate? this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var address = this.GetFateAddress(index);
|
|
||||||
return this.CreateFateReference(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the Fate at the specified index of the fate table.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the Fate.</param>
|
|
||||||
/// <returns>The memory address of the Fate.</returns>
|
|
||||||
public unsafe IntPtr GetFateAddress(int index)
|
|
||||||
{
|
|
||||||
if (index >= this.Length)
|
|
||||||
return IntPtr.Zero;
|
|
||||||
|
|
||||||
var fateTable = this.FateTableAddress;
|
var fateTable = this.FateTableAddress;
|
||||||
if (fateTable == IntPtr.Zero)
|
if (fateTable == IntPtr.Zero)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return (IntPtr)this.Struct->Fates.Get((ulong)index).Value;
|
// Sonar used this to check if the table was safe to read
|
||||||
}
|
if (Struct->FateDirector == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/// <summary>
|
if (Struct->Fates.First == null || Struct->Fates.Last == null)
|
||||||
/// Create a reference to a FFXIV actor.
|
return 0;
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset">The offset of the actor in memory.</param>
|
|
||||||
/// <returns><see cref="Fate"/> object containing requested data.</returns>
|
|
||||||
public Fate? CreateFateReference(IntPtr offset)
|
|
||||||
{
|
|
||||||
var clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
return (int)Struct->Fates.Size();
|
||||||
return null;
|
|
||||||
|
|
||||||
if (offset == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new Fate(offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently available Fate events.
|
/// Gets the address of the Fate table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class FateTable : IReadOnlyCollection<Fate>
|
internal unsafe IntPtr FateTableAddress
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
get
|
||||||
int IReadOnlyCollection<Fate>.Count => this.Length;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IEnumerator<Fate> GetEnumerator()
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
if (this.address.FateTablePtr == IntPtr.Zero)
|
||||||
{
|
return IntPtr.Zero;
|
||||||
yield return this[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
return *(IntPtr*)this.address.FateTablePtr;
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an actor at the specified spawn index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Spawn index.</param>
|
||||||
|
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
|
||||||
|
public Fate? this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var address = this.GetFateAddress(index);
|
||||||
|
return this.CreateFateReference(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the Fate at the specified index of the fate table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the Fate.</param>
|
||||||
|
/// <returns>The memory address of the Fate.</returns>
|
||||||
|
public unsafe IntPtr GetFateAddress(int index)
|
||||||
|
{
|
||||||
|
if (index >= this.Length)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
|
||||||
|
var fateTable = this.FateTableAddress;
|
||||||
|
if (fateTable == IntPtr.Zero)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
|
||||||
|
return (IntPtr)this.Struct->Fates.Get((ulong)index).Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a reference to a FFXIV actor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the actor in memory.</param>
|
||||||
|
/// <returns><see cref="Fate"/> object containing requested data.</returns>
|
||||||
|
public Fate? CreateFateReference(IntPtr offset)
|
||||||
|
{
|
||||||
|
var clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
|
if (clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (offset == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Fate(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the currently available Fate events.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class FateTable : IReadOnlyCollection<Fate>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<Fate> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,95 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.GamePad
|
namespace Dalamud.Game.ClientState.GamePad;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bitmask of the Button ushort used by the game.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum GamepadButtons : ushort
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bitmask of the Button ushort used by the game.
|
/// No buttons pressed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
None = 0,
|
||||||
public enum GamepadButtons : ushort
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No buttons pressed.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad up.
|
/// Digipad up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadUp = 0x0001,
|
DpadUp = 0x0001,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad down.
|
/// Digipad down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadDown = 0x0002,
|
DpadDown = 0x0002,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad left.
|
/// Digipad left.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadLeft = 0x0004,
|
DpadLeft = 0x0004,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digipad right.
|
/// Digipad right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DpadRight = 0x0008,
|
DpadRight = 0x0008,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// North action button. Triangle on PS, Y on Xbox.
|
/// North action button. Triangle on PS, Y on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
North = 0x0010,
|
North = 0x0010,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// South action button. Cross on PS, A on Xbox.
|
/// South action button. Cross on PS, A on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
South = 0x0020,
|
South = 0x0020,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// West action button. Square on PS, X on Xbos.
|
/// West action button. Square on PS, X on Xbos.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
West = 0x0040,
|
West = 0x0040,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// East action button. Circle on PS, B on Xbox.
|
/// East action button. Circle on PS, B on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
East = 0x0080,
|
East = 0x0080,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First button on left shoulder side.
|
/// First button on left shoulder side.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L1 = 0x0100,
|
L1 = 0x0100,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L2 = 0x0200,
|
L2 = 0x0200,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Press on left analogue stick.
|
/// Press on left analogue stick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
L3 = 0x0400,
|
L3 = 0x0400,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First button on right shoulder.
|
/// First button on right shoulder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R1 = 0x0800,
|
R1 = 0x0800,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Second button on right shoulder. Analog input lost in this bitmask.
|
/// Second button on right shoulder. Analog input lost in this bitmask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R2 = 0x1000,
|
R2 = 0x1000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Press on right analogue stick.
|
/// Press on right analogue stick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
R3 = 0x2000,
|
R3 = 0x2000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Start = 0x8000,
|
Start = 0x8000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Select = 0x4000,
|
Select = 0x4000,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,75 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.GamePad
|
namespace Dalamud.Game.ClientState.GamePad;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Struct which gets populated by polling the gamepads.
|
||||||
|
///
|
||||||
|
/// Has an array of gamepads, among many other things (here not mapped).
|
||||||
|
/// All we really care about is the final data which the game uses to determine input.
|
||||||
|
///
|
||||||
|
/// The size is definitely bigger than only the following fields but I do not know how big.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct GamepadInput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct which gets populated by polling the gamepads.
|
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||||
///
|
|
||||||
/// Has an array of gamepads, among many other things (here not mapped).
|
|
||||||
/// All we really care about is the final data which the game uses to determine input.
|
|
||||||
///
|
|
||||||
/// The size is definitely bigger than only the following fields but I do not know how big.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[FieldOffset(0x88)]
|
||||||
public struct GamepadInput
|
public int LeftStickX;
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
|
||||||
/// </summary>
|
|
||||||
[FieldOffset(0x88)]
|
|
||||||
public int LeftStickX;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x8C)]
|
[FieldOffset(0x8C)]
|
||||||
public int LeftStickY;
|
public int LeftStickY;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x90)]
|
[FieldOffset(0x90)]
|
||||||
public int RightStickX;
|
public int RightStickX;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(0x94)]
|
[FieldOffset(0x94)]
|
||||||
public int RightStickY;
|
public int RightStickY;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0x98)]
|
[FieldOffset(0x98)]
|
||||||
public ushort ButtonsRaw;
|
public ushort ButtonsRaw;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0x9C)]
|
[FieldOffset(0x9C)]
|
||||||
public ushort ButtonsPressed;
|
public ushort ButtonsPressed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0xA0)]
|
[FieldOffset(0xA0)]
|
||||||
public ushort ButtonsReleased;
|
public ushort ButtonsReleased;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is a bitfield.
|
/// This is a bitfield.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[FieldOffset(0xA4)]
|
[FieldOffset(0xA4)]
|
||||||
public ushort ButtonsRepeat;
|
public ushort ButtonsRepeat;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,249 +6,248 @@ using Dalamud.IoC.Internal;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.GamePad
|
namespace Dalamud.Game.ClientState.GamePad;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exposes the game gamepad state to dalamud.
|
||||||
|
///
|
||||||
|
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public unsafe class GamepadState : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly Hook<ControllerPoll> gamepadPoll;
|
||||||
/// Exposes the game gamepad state to dalamud.
|
|
||||||
///
|
private bool isDisposed;
|
||||||
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
|
||||||
/// </summary>
|
private int leftStickX;
|
||||||
[PluginInterface]
|
private int leftStickY;
|
||||||
[InterfaceVersion("1.0")]
|
private int rightStickX;
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
private int rightStickY;
|
||||||
public unsafe class GamepadState : IDisposable, IServiceType
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private GamepadState(ClientState clientState)
|
||||||
{
|
{
|
||||||
private readonly Hook<ControllerPoll> gamepadPoll;
|
var resolver = clientState.AddressResolver;
|
||||||
|
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||||
|
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||||
|
}
|
||||||
|
|
||||||
private bool isDisposed;
|
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||||
|
|
||||||
private int leftStickX;
|
/// <summary>
|
||||||
private int leftStickY;
|
/// Gets the pointer to the current instance of the GamepadInput struct.
|
||||||
private int rightStickX;
|
/// </summary>
|
||||||
private int rightStickY;
|
public IntPtr GamepadInputAddress { get; private set; }
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
/// <summary>
|
||||||
private GamepadState(ClientState clientState)
|
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||||
|
/// </summary>
|
||||||
|
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
|
///
|
||||||
|
/// Exposed internally for Debug Data window.
|
||||||
|
/// </summary>
|
||||||
|
internal ushort ButtonsPressed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
|
///
|
||||||
|
/// Exposed internally for Debug Data window.
|
||||||
|
/// </summary>
|
||||||
|
internal ushort ButtonsRaw { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
|
///
|
||||||
|
/// Exposed internally for Debug Data window.
|
||||||
|
/// </summary>
|
||||||
|
internal ushort ButtonsReleased { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
|
///
|
||||||
|
/// Exposed internally for Debug Data window.
|
||||||
|
/// </summary>
|
||||||
|
internal ushort ButtonsRepeat { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether detour should block gamepad input for game.
|
||||||
|
///
|
||||||
|
/// Ideally, we would use
|
||||||
|
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
||||||
|
/// but this has a race condition during load with the detour which sets up ImGui
|
||||||
|
/// and throws if our detour gets called before the other.
|
||||||
|
/// </summary>
|
||||||
|
internal bool NavEnableGamepad { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether <paramref name="button"/> has been pressed.
|
||||||
|
///
|
||||||
|
/// Only true on first frame of the press.
|
||||||
|
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="button">The button to check for.</param>
|
||||||
|
/// <returns>1 if pressed, 0 otherwise.</returns>
|
||||||
|
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether <paramref name="button"/> is being pressed.
|
||||||
|
///
|
||||||
|
/// True in intervals if button is held down.
|
||||||
|
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="button">The button to check for.</param>
|
||||||
|
/// <returns>1 if still pressed during interval, 0 otherwise or in between intervals.</returns>
|
||||||
|
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether <paramref name="button"/> has been released.
|
||||||
|
///
|
||||||
|
/// Only true the frame after release.
|
||||||
|
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="button">The button to check for.</param>
|
||||||
|
/// <returns>1 if released, 0 otherwise.</returns>
|
||||||
|
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw state of <paramref name="button"/>.
|
||||||
|
///
|
||||||
|
/// Is set the entire time a button is pressed down.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="button">The button to check for.</param>
|
||||||
|
/// <returns>1 the whole time button is pressed, 0 otherwise.</returns>
|
||||||
|
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes this instance, alongside its hooks.
|
||||||
|
/// </summary>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
this.Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ServiceManager.CallWhenServicesReady]
|
||||||
|
private void ContinueConstruction()
|
||||||
|
{
|
||||||
|
this.gamepadPoll.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GamepadPollDetour(IntPtr gamepadInput)
|
||||||
|
{
|
||||||
|
var original = this.gamepadPoll.Original(gamepadInput);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var resolver = clientState.AddressResolver;
|
this.GamepadInputAddress = gamepadInput;
|
||||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
var input = (GamepadInput*)gamepadInput;
|
||||||
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
this.leftStickX = input->LeftStickX;
|
||||||
}
|
this.leftStickY = input->LeftStickY;
|
||||||
|
this.rightStickX = input->RightStickX;
|
||||||
|
this.rightStickY = input->RightStickY;
|
||||||
|
this.ButtonsRaw = input->ButtonsRaw;
|
||||||
|
this.ButtonsPressed = input->ButtonsPressed;
|
||||||
|
this.ButtonsReleased = input->ButtonsReleased;
|
||||||
|
this.ButtonsRepeat = input->ButtonsRepeat;
|
||||||
|
|
||||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
if (this.NavEnableGamepad)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr GamepadInputAddress { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
|
||||||
///
|
|
||||||
/// Exposed internally for Debug Data window.
|
|
||||||
/// </summary>
|
|
||||||
internal ushort ButtonsPressed { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
|
||||||
///
|
|
||||||
/// Exposed internally for Debug Data window.
|
|
||||||
/// </summary>
|
|
||||||
internal ushort ButtonsRaw { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
|
||||||
///
|
|
||||||
/// Exposed internally for Debug Data window.
|
|
||||||
/// </summary>
|
|
||||||
internal ushort ButtonsReleased { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
|
||||||
///
|
|
||||||
/// Exposed internally for Debug Data window.
|
|
||||||
/// </summary>
|
|
||||||
internal ushort ButtonsRepeat { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether detour should block gamepad input for game.
|
|
||||||
///
|
|
||||||
/// Ideally, we would use
|
|
||||||
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
|
||||||
/// but this has a race condition during load with the detour which sets up ImGui
|
|
||||||
/// and throws if our detour gets called before the other.
|
|
||||||
/// </summary>
|
|
||||||
internal bool NavEnableGamepad { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether <paramref name="button"/> has been pressed.
|
|
||||||
///
|
|
||||||
/// Only true on first frame of the press.
|
|
||||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The button to check for.</param>
|
|
||||||
/// <returns>1 if pressed, 0 otherwise.</returns>
|
|
||||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether <paramref name="button"/> is being pressed.
|
|
||||||
///
|
|
||||||
/// True in intervals if button is held down.
|
|
||||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The button to check for.</param>
|
|
||||||
/// <returns>1 if still pressed during interval, 0 otherwise or in between intervals.</returns>
|
|
||||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether <paramref name="button"/> has been released.
|
|
||||||
///
|
|
||||||
/// Only true the frame after release.
|
|
||||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The button to check for.</param>
|
|
||||||
/// <returns>1 if released, 0 otherwise.</returns>
|
|
||||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the raw state of <paramref name="button"/>.
|
|
||||||
///
|
|
||||||
/// Is set the entire time a button is pressed down.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The button to check for.</param>
|
|
||||||
/// <returns>1 the whole time button is pressed, 0 otherwise.</returns>
|
|
||||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes this instance, alongside its hooks.
|
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
this.Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction()
|
|
||||||
{
|
|
||||||
this.gamepadPoll.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GamepadPollDetour(IntPtr gamepadInput)
|
|
||||||
{
|
|
||||||
var original = this.gamepadPoll.Original(gamepadInput);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
this.GamepadInputAddress = gamepadInput;
|
input->LeftStickX = 0;
|
||||||
var input = (GamepadInput*)gamepadInput;
|
input->LeftStickY = 0;
|
||||||
this.leftStickX = input->LeftStickX;
|
input->RightStickX = 0;
|
||||||
this.leftStickY = input->LeftStickY;
|
input->RightStickY = 0;
|
||||||
this.rightStickX = input->RightStickX;
|
|
||||||
this.rightStickY = input->RightStickY;
|
|
||||||
this.ButtonsRaw = input->ButtonsRaw;
|
|
||||||
this.ButtonsPressed = input->ButtonsPressed;
|
|
||||||
this.ButtonsReleased = input->ButtonsReleased;
|
|
||||||
this.ButtonsRepeat = input->ButtonsRepeat;
|
|
||||||
|
|
||||||
if (this.NavEnableGamepad)
|
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
|
||||||
{
|
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
|
||||||
input->LeftStickX = 0;
|
// It does block, however, all input to the game.
|
||||||
input->LeftStickY = 0;
|
// Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
|
||||||
input->RightStickX = 0;
|
// and the digipad (in some situations, but thankfully not in menus) functional.
|
||||||
input->RightStickY = 0;
|
// We can either:
|
||||||
|
// (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
|
||||||
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
|
// (b) ignore it as so far it seems only a 'visual' error
|
||||||
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
|
// (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
|
||||||
// It does block, however, all input to the game.
|
// Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
|
||||||
// Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
|
// because of the other blocked input)
|
||||||
// and the digipad (in some situations, but thankfully not in menus) functional.
|
// `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
|
||||||
// We can either:
|
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
||||||
// (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
|
// This is debatable.
|
||||||
// (b) ignore it as so far it seems only a 'visual' error
|
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
||||||
// (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
|
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
||||||
// Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
|
& ~GamepadButtons.R2
|
||||||
// because of the other blocked input)
|
& ~GamepadButtons.DpadDown
|
||||||
// `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
|
& ~GamepadButtons.DpadLeft
|
||||||
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
& ~GamepadButtons.DpadUp
|
||||||
// This is debatable.
|
& ~GamepadButtons.DpadRight);
|
||||||
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
input->ButtonsRaw &= deletionMask;
|
||||||
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
input->ButtonsPressed = 0;
|
||||||
& ~GamepadButtons.R2
|
input->ButtonsReleased = 0;
|
||||||
& ~GamepadButtons.DpadDown
|
input->ButtonsRepeat = 0;
|
||||||
& ~GamepadButtons.DpadLeft
|
return 0;
|
||||||
& ~GamepadButtons.DpadUp
|
|
||||||
& ~GamepadButtons.DpadRight);
|
|
||||||
input->ButtonsRaw &= deletionMask;
|
|
||||||
input->ButtonsPressed = 0;
|
|
||||||
input->ButtonsReleased = 0;
|
|
||||||
input->ButtonsRepeat = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
|
|
||||||
// original, zero or do the work adjusting the bits.
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
|
|
||||||
|
|
||||||
// NOTE (Chiv) Explicitly deactivate on error
|
|
||||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (this.isDisposed) return;
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
this.gamepadPoll?.Disable();
|
|
||||||
this.gamepadPoll?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isDisposed = true;
|
// NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
|
||||||
|
// original, zero or do the work adjusting the bits.
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
|
||||||
|
|
||||||
|
// NOTE (Chiv) Explicitly deactivate on error
|
||||||
|
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
||||||
|
return original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.isDisposed) return;
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
this.gamepadPoll?.Disable();
|
||||||
|
this.gamepadPoll?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDisposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MNK Beast Chakra types.
|
||||||
|
/// </summary>
|
||||||
|
public enum BeastChakra : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MNK Beast Chakra types.
|
/// No card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum BeastChakra : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No card.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Coeurl chakra.
|
/// The Coeurl chakra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
COEURL = 1,
|
COEURL = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Opo-Opo chakra.
|
/// The Opo-Opo chakra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OPOOPO = 2,
|
OPOOPO = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Raptor chakra.
|
/// The Raptor chakra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RAPTOR = 3,
|
RAPTOR = 3,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,52 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AST Arcanum (card) types.
|
||||||
|
/// </summary>
|
||||||
|
public enum CardType : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AST Arcanum (card) types.
|
/// No card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum CardType : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No card.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Balance card.
|
/// The Balance card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BALANCE = 1,
|
BALANCE = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Bole card.
|
/// The Bole card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BOLE = 2,
|
BOLE = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Arrow card.
|
/// The Arrow card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ARROW = 3,
|
ARROW = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Spear card.
|
/// The Spear card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SPEAR = 4,
|
SPEAR = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Ewer card.
|
/// The Ewer card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EWER = 5,
|
EWER = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Spire card.
|
/// The Spire card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SPIRE = 6,
|
SPIRE = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lord of Crowns card.
|
/// The Lord of Crowns card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LORD = 0x70,
|
LORD = 0x70,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lady of Crowns card.
|
/// The Lady of Crowns card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LADY = 0x80,
|
LADY = 0x80,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SCH Dismissed fairy types.
|
||||||
|
/// </summary>
|
||||||
|
public enum DismissedFairy : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SCH Dismissed fairy types.
|
/// Dismissed fairy is Eos.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum DismissedFairy : byte
|
EOS = 6,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Dismissed fairy is Eos.
|
|
||||||
/// </summary>
|
|
||||||
EOS = 6,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dismissed fairy is Selene.
|
/// Dismissed fairy is Selene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SELENE = 7,
|
SELENE = 7,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SAM Kaeshi types.
|
||||||
|
/// </summary>
|
||||||
|
public enum Kaeshi : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SAM Kaeshi types.
|
/// No Kaeshi is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Kaeshi : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No Kaeshi is active.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kaeshi: Higanbana type.
|
/// Kaeshi: Higanbana type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HIGANBANA = 1,
|
HIGANBANA = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kaeshi: Goken type.
|
/// Kaeshi: Goken type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GOKEN = 2,
|
GOKEN = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kaeshi: Setsugekka type.
|
/// Kaeshi: Setsugekka type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SETSUGEKKA = 3,
|
SETSUGEKKA = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kaeshi: Namikiri type.
|
/// Kaeshi: Namikiri type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NAMIKIRI = 4,
|
NAMIKIRI = 4,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// NIN Mudra types.
|
||||||
|
/// </summary>
|
||||||
|
public enum Mudras : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NIN Mudra types.
|
/// Ten mudra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Mudras : byte
|
TEN = 1,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ten mudra.
|
|
||||||
/// </summary>
|
|
||||||
TEN = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chi mudra.
|
/// Chi mudra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CHI = 2,
|
CHI = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Jin mudra.
|
/// Jin mudra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JIN = 3,
|
JIN = 3,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,25 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MNK Nadi types.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum Nadi : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MNK Nadi types.
|
/// No card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
NONE = 0,
|
||||||
public enum Nadi : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No card.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Lunar nadi.
|
/// The Lunar nadi.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LUNAR = 2,
|
LUNAR = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Solar nadi.
|
/// The Solar nadi.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SOLAR = 4,
|
SOLAR = 4,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,47 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SMN summoned pet glam types.
|
||||||
|
/// </summary>
|
||||||
|
public enum PetGlam : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SMN summoned pet glam types.
|
/// No pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum PetGlam : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No pet glam.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emerald carbuncle pet glam.
|
/// Emerald carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EMERALD = 1,
|
EMERALD = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Topaz carbuncle pet glam.
|
/// Topaz carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TOPAZ = 2,
|
TOPAZ = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ruby carbuncle pet glam.
|
/// Ruby carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RUBY = 3,
|
RUBY = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normal carbuncle pet glam.
|
/// Normal carbuncle pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CARBUNCLE = 4,
|
CARBUNCLE = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ifrit Egi pet glam.
|
/// Ifrit Egi pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IFRIT = 5,
|
IFRIT = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Titan Egi pet glam.
|
/// Titan Egi pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TITAN = 6,
|
TITAN = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Garuda Egi pet glam.
|
/// Garuda Egi pet glam.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GARUDA = 7,
|
GARUDA = 7,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AST Divination seal types.
|
||||||
|
/// </summary>
|
||||||
|
public enum SealType : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AST Divination seal types.
|
/// No seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SealType : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No seal.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sun seal.
|
/// Sun seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SUN = 1,
|
SUN = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moon seal.
|
/// Moon seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MOON = 2,
|
MOON = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Celestial seal.
|
/// Celestial seal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CELESTIAL = 3,
|
CELESTIAL = 3,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Samurai Sen types.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum Sen : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Samurai Sen types.
|
/// No Sen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
NONE = 0,
|
||||||
public enum Sen : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No Sen.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setsu Sen type.
|
/// Setsu Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SETSU = 1 << 0,
|
SETSU = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Getsu Sen type.
|
/// Getsu Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GETSU = 1 << 1,
|
GETSU = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ka Sen type.
|
/// Ka Sen type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
KA = 1 << 2,
|
KA = 1 << 2,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BRD Song types.
|
||||||
|
/// </summary>
|
||||||
|
public enum Song : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BRD Song types.
|
/// No song is active type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Song : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No song is active type.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mage's Ballad type.
|
/// Mage's Ballad type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MAGE = 1,
|
MAGE = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Army's Paeon type.
|
/// Army's Paeon type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ARMY = 2,
|
ARMY = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Wanderer's Minuet type.
|
/// The Wanderer's Minuet type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WANDERER = 3,
|
WANDERER = 3,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Enums
|
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SMN summoned pet types.
|
||||||
|
/// </summary>
|
||||||
|
public enum SummonPet : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SMN summoned pet types.
|
/// No pet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SummonPet : byte
|
NONE = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No pet.
|
|
||||||
/// </summary>
|
|
||||||
NONE = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The summoned pet Carbuncle.
|
/// The summoned pet Carbuncle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CARBUNCLE = 23,
|
CARBUNCLE = 23,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,45 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge
|
namespace Dalamud.Game.ClientState.JobGauge;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class converts in-memory Job gauge data to structs.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public class JobGauges : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||||
/// This class converts in-memory Job gauge data to structs.
|
|
||||||
/// </summary>
|
[ServiceManager.ServiceConstructor]
|
||||||
[PluginInterface]
|
private JobGauges(ClientState clientState)
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public class JobGauges : IServiceType
|
|
||||||
{
|
{
|
||||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
this.Address = clientState.AddressResolver.JobGaugeData;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
||||||
private JobGauges(ClientState clientState)
|
}
|
||||||
|
|
||||||
|
/// <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))
|
||||||
{
|
{
|
||||||
this.Address = clientState.AddressResolver.JobGaugeData;
|
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
|
||||||
|
|
||||||
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return (T)gauge;
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,44 +3,43 @@ using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory AST job gauge.
|
/// Initializes a new instance of the <see cref="ASTGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class ASTGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.AstrologianGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal ASTGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 & 0xF);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the currently drawn crown <see cref="CardType"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Currently drawn crown <see cref="CardType"/>.</returns>
|
|
||||||
public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="SealType"/>s currently active.
|
|
||||||
/// </summary>
|
|
||||||
public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray();
|
|
||||||
|
|
||||||
/// <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) => this.Seals.Contains(seal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently drawn <see cref="CardType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
|
||||||
|
public CardType DrawnCard => (CardType)(this.Struct->Card & 0xF);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently drawn crown <see cref="CardType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Currently drawn crown <see cref="CardType"/>.</returns>
|
||||||
|
public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="SealType"/>s currently active.
|
||||||
|
/// </summary>
|
||||||
|
public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray();
|
||||||
|
|
||||||
|
/// <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) => this.Seals.Contains(seal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,72 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory BLM job gauge.
|
/// Initializes a new instance of the <see cref="BLMGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BLMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BlackMageGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal BLMGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 or not 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 or not 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 or not Enochian is active.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsEnochianActive => this.Struct->EnochianActive;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether Paradox is active.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsParadoxActive => this.Struct->ParadoxActive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 or not 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 or not 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 or not Enochian is active.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsEnochianActive => this.Struct->EnochianActive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Paradox is active.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsParadoxActive => this.Struct->ParadoxActive;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,94 +3,93 @@ using System;
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory BRD job gauge.
|
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BardGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal BRDGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
}
|
||||||
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
/// Gets the current song timer in milliseconds.
|
||||||
internal BRDGauge(IntPtr address)
|
/// </summary>
|
||||||
: base(address)
|
public ushort 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
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
|
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet))
|
||||||
|
return Song.WANDERER;
|
||||||
|
|
||||||
|
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon))
|
||||||
|
return Song.ARMY;
|
||||||
|
|
||||||
|
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad))
|
||||||
|
return Song.MAGE;
|
||||||
|
|
||||||
|
return Song.NONE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current song timer in milliseconds.
|
/// Gets the type of song that was last played.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort SongTimer => this.Struct->SongTimer;
|
public Song LastSong
|
||||||
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// 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
|
|
||||||
{
|
{
|
||||||
get
|
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed))
|
||||||
{
|
return Song.WANDERER;
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet))
|
|
||||||
return Song.WANDERER;
|
|
||||||
|
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon))
|
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed))
|
||||||
return Song.ARMY;
|
return Song.ARMY;
|
||||||
|
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad))
|
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed))
|
||||||
return Song.MAGE;
|
return Song.MAGE;
|
||||||
|
|
||||||
return Song.NONE;
|
return Song.NONE;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of song that was last played.
|
/// Gets the song Coda that are currently active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Song LastSong
|
/// <remarks>
|
||||||
|
/// This will always return an array of size 3, inactive Coda are represented by <see cref="Song.NONE"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public Song[] Coda
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
get
|
return new[]
|
||||||
{
|
{
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed))
|
this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE,
|
||||||
return Song.WANDERER;
|
this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE,
|
||||||
|
this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE,
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed))
|
};
|
||||||
return Song.ARMY;
|
|
||||||
|
|
||||||
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed))
|
|
||||||
return Song.MAGE;
|
|
||||||
|
|
||||||
return Song.NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the song Coda that are currently active.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This will always return an array of size 3, inactive Coda are represented by <see cref="Song.NONE"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public Song[] Coda
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE,
|
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE,
|
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,59 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory DNC job gauge.
|
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class DNCGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DancerGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal DNCGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 all the steps in the current dance.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe uint[] Steps
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var arr = new uint[4];
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
arr[i] = this.Struct->DanceSteps[i] + 15999u - 1;
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the next step in the current dance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The next dance step action ID.</returns>
|
|
||||||
public uint NextStep => 15999u + 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 all the steps in the current dance.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe uint[] Steps
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var arr = new uint[4];
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
arr[i] = this.Struct->DanceSteps[i] + 15999u - 1;
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next step in the current dance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The next dance step action ID.</returns>
|
||||||
|
public uint NextStep => 15999u + 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,38 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory DRG job gauge.
|
/// Initializes a new instance of the <see cref="DRGGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class DRGGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DragoonGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal DRGGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 Life of the Dragon in milliseconds.
|
|
||||||
/// </summary>
|
|
||||||
public short LOTDTimer => this.Struct->LotdTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether Life of the Dragon is active.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLOTDActive => this.Struct->LotdState == 2;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the count of eyes opened during Blood of the Dragon.
|
|
||||||
/// </summary>
|
|
||||||
public byte EyeCount => this.Struct->EyeCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Firstminds' Focus available.
|
|
||||||
/// </summary>
|
|
||||||
public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time remaining for Life of the Dragon in milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
public short LOTDTimer => this.Struct->LotdTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Life of the Dragon is active.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLOTDActive => this.Struct->LotdState == 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the count of eyes opened during Blood of the Dragon.
|
||||||
|
/// </summary>
|
||||||
|
public byte EyeCount => this.Struct->EyeCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Firstminds' Focus available.
|
||||||
|
/// </summary>
|
||||||
|
public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,39 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory DRK job gauge.
|
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal DRKGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory GNB job gauge.
|
/// Initializes a new instance of the <see cref="GNBGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class GNBGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.GunbreakerGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal GNBGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base job gauge class.
|
||||||
|
/// </summary>
|
||||||
|
public abstract unsafe class JobGaugeBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base job gauge class.
|
/// Initializes a new instance of the <see cref="JobGaugeBase"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract unsafe class JobGaugeBase
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal JobGaugeBase(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = address;
|
||||||
/// 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; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of this job gauge in memory.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// Base job gauge class.
|
/// Initializes a new instance of the <see cref="JobGaugeBase{T}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The underlying FFXIVClientStructs type.</typeparam>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
public unsafe class JobGaugeBase<T> : JobGaugeBase where T : unmanaged
|
internal JobGaugeBase(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an unsafe struct pointer of this job gauge.
|
||||||
|
/// </summary>
|
||||||
|
private protected T* Struct => (T*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,55 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory MCH job gauge.
|
/// Initializes a new instance of the <see cref="MCHGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class MCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MachinistGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal MCHGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,43 +3,42 @@ using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory MNK job gauge.
|
/// Initializes a new instance of the <see cref="MNKGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MonkGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal MNKGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 amount of Chakra available.
|
|
||||||
/// </summary>
|
|
||||||
public byte Chakra => this.Struct->Chakra;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the types of Beast Chakra available.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This will always return an array of size 3, inactive Beast Chakra are represented by <see cref="BeastChakra.NONE"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the types of Nadi available.
|
|
||||||
/// </summary>
|
|
||||||
public Nadi Nadi => (Nadi)this.Struct->Nadi;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time remaining that Blitz is active.
|
|
||||||
/// </summary>
|
|
||||||
public ushort BlitzTimeRemaining => this.Struct->BlitzTimeRemaining;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Chakra available.
|
||||||
|
/// </summary>
|
||||||
|
public byte Chakra => this.Struct->Chakra;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the types of Beast Chakra available.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will always return an array of size 3, inactive Beast Chakra are represented by <see cref="BeastChakra.NONE"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the types of Nadi available.
|
||||||
|
/// </summary>
|
||||||
|
public Nadi Nadi => (Nadi)this.Struct->Nadi;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time remaining that Blitz is active.
|
||||||
|
/// </summary>
|
||||||
|
public ushort BlitzTimeRemaining => this.Struct->BlitzTimeRemaining;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory NIN job gauge.
|
/// Initializes a new instance of the <see cref="NINGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class NINGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.NinjaGauge>
|
/// <param name="address">The address of the gauge.</param>
|
||||||
|
internal NINGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory PLD job gauge.
|
/// Initializes a new instance of the <see cref="PLDGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class PLDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.PaladinGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal PLDGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current level of the Oath gauge.
|
||||||
|
/// </summary>
|
||||||
|
public byte OathGauge => this.Struct->OathGauge;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory RDM job gauge.
|
/// Initializes a new instance of the <see cref="RDMGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class RDMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.RedMageGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal RDMGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of mana stacks.
|
|
||||||
/// </summary>
|
|
||||||
public byte ManaStacks => this.Struct->ManaStacks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of mana stacks.
|
||||||
|
/// </summary>
|
||||||
|
public byte ManaStacks => this.Struct->ManaStacks;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,43 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory RPR job gauge.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class RPRGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ReaperGauge>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory RPR job gauge.
|
/// Initializes a new instance of the <see cref="RPRGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class RPRGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ReaperGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal RPRGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RPRGauge"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
|
||||||
internal RPRGauge(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Soul available.
|
|
||||||
/// </summary>
|
|
||||||
public byte Soul => this.Struct->Soul;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Shroud available.
|
|
||||||
/// </summary>
|
|
||||||
public byte Shroud => this.Struct->Shroud;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time remaining that Enshrouded is active.
|
|
||||||
/// </summary>
|
|
||||||
public ushort EnshroudedTimeRemaining => this.Struct->EnshroudedTimeRemaining;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Lemure Shroud available.
|
|
||||||
/// </summary>
|
|
||||||
public byte LemureShroud => this.Struct->LemureShroud;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Void Shroud available.
|
|
||||||
/// </summary>
|
|
||||||
public byte VoidShroud => this.Struct->VoidShroud;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Soul available.
|
||||||
|
/// </summary>
|
||||||
|
public byte Soul => this.Struct->Soul;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Shroud available.
|
||||||
|
/// </summary>
|
||||||
|
public byte Shroud => this.Struct->Shroud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time remaining that Enshrouded is active.
|
||||||
|
/// </summary>
|
||||||
|
public ushort EnshroudedTimeRemaining => this.Struct->EnshroudedTimeRemaining;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Lemure Shroud available.
|
||||||
|
/// </summary>
|
||||||
|
public byte LemureShroud => this.Struct->LemureShroud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Void Shroud available.
|
||||||
|
/// </summary>
|
||||||
|
public byte VoidShroud => this.Struct->VoidShroud;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,58 +2,57 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory SAM job gauge.
|
/// Initializes a new instance of the <see cref="SAMGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SamuraiGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal SAMGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 currently active Kaeshi ability.
|
|
||||||
/// </summary>
|
|
||||||
public Kaeshi Kaeshi => (Kaeshi)this.Struct->Kaeshi;
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently active Kaeshi ability.
|
||||||
|
/// </summary>
|
||||||
|
public Kaeshi Kaeshi => (Kaeshi)this.Struct->Kaeshi;
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,39 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory SCH job gauge.
|
/// Initializes a new instance of the <see cref="SCHGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class SCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ScholarGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal SCHGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 remaining time Seraph is active in milliseconds.
|
|
||||||
/// </summary>
|
|
||||||
public short SeraphTimer => this.Struct->SeraphTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last dismissed fairy.
|
|
||||||
/// </summary>
|
|
||||||
public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 remaining time Seraph is active in milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
public short SeraphTimer => this.Struct->SeraphTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last dismissed fairy.
|
||||||
|
/// </summary>
|
||||||
|
public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,39 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory SGE job gauge.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class SGEGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SageGauge>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory SGE job gauge.
|
/// Initializes a new instance of the <see cref="SGEGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class SGEGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SageGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal SGEGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SGEGauge"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the job gauge.</param>
|
|
||||||
internal SGEGauge(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of milliseconds elapsed until the next Addersgall is available.
|
|
||||||
/// This counts from 0 to 20_000.
|
|
||||||
/// </summary>
|
|
||||||
public short AddersgallTimer => this.Struct->AddersgallTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Addersgall available.
|
|
||||||
/// </summary>
|
|
||||||
public byte Addersgall => this.Struct->Addersgall;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Addersting available.
|
|
||||||
/// </summary>
|
|
||||||
public byte Addersting => this.Struct->Addersting;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether Eukrasia is activated.
|
|
||||||
/// </summary>
|
|
||||||
public bool Eukrasia => this.Struct->Eukrasia == 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of milliseconds elapsed until the next Addersgall is available.
|
||||||
|
/// This counts from 0 to 20_000.
|
||||||
|
/// </summary>
|
||||||
|
public short AddersgallTimer => this.Struct->AddersgallTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Addersgall available.
|
||||||
|
/// </summary>
|
||||||
|
public byte Addersgall => this.Struct->Addersgall;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Addersting available.
|
||||||
|
/// </summary>
|
||||||
|
public byte Addersting => this.Struct->Addersting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Eukrasia is activated.
|
||||||
|
/// </summary>
|
||||||
|
public bool Eukrasia => this.Struct->Eukrasia == 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,112 +3,111 @@ using System;
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory SMN job gauge.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory SMN job gauge.
|
/// Initializes a new instance of the <see cref="SMNGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal SMNGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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 ushort SummonTimerRemaining => this.Struct->SummonTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time remaining for the current attunement.
|
|
||||||
/// </summary>
|
|
||||||
public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the summon that will return after the current summon expires.
|
|
||||||
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.Pet"/> sheet.
|
|
||||||
/// </summary>
|
|
||||||
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
|
|
||||||
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.PetMirage"/> sheet.
|
|
||||||
/// </summary>
|
|
||||||
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of aspected Attunment remaining.
|
|
||||||
/// </summary>
|
|
||||||
public byte Attunement => this.Struct->Attunement;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current aether flags.
|
|
||||||
/// Use the summon accessors instead.
|
|
||||||
/// </summary>
|
|
||||||
public AetherFlags AetherFlags => this.Struct->AetherFlags;
|
|
||||||
|
|
||||||
/// <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.HasFlag(AetherFlags.PhoenixReady);
|
|
||||||
|
|
||||||
/// <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.HasFlag(AetherFlags.PhoenixReady);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Ifrit is ready to be summoned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsIfritReady => this.AetherFlags.HasFlag(AetherFlags.IfritReady);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Titan is ready to be summoned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsTitanReady => this.AetherFlags.HasFlag(AetherFlags.TitanReady);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Garuda is ready to be summoned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsGarudaReady => this.AetherFlags.HasFlag(AetherFlags.GarudaReady);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Ifrit is currently attuned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Titan is currently attuned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether if Garuda is currently attuned.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
|
||||||
public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
|
||||||
|
|
||||||
/// <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.AetherflowStacks > 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of Aetherflow available.
|
|
||||||
/// </summary>
|
|
||||||
public byte AetherflowStacks => (byte)(this.AetherFlags & AetherFlags.Aetherflow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time remaining for the current summon.
|
||||||
|
/// </summary>
|
||||||
|
public ushort SummonTimerRemaining => this.Struct->SummonTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the time remaining for the current attunement.
|
||||||
|
/// </summary>
|
||||||
|
public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the summon that will return after the current summon expires.
|
||||||
|
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.Pet"/> sheet.
|
||||||
|
/// </summary>
|
||||||
|
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
|
||||||
|
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.PetMirage"/> sheet.
|
||||||
|
/// </summary>
|
||||||
|
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of aspected Attunment remaining.
|
||||||
|
/// </summary>
|
||||||
|
public byte Attunement => this.Struct->Attunement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current aether flags.
|
||||||
|
/// Use the summon accessors instead.
|
||||||
|
/// </summary>
|
||||||
|
public AetherFlags AetherFlags => this.Struct->AetherFlags;
|
||||||
|
|
||||||
|
/// <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.HasFlag(AetherFlags.PhoenixReady);
|
||||||
|
|
||||||
|
/// <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.HasFlag(AetherFlags.PhoenixReady);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Ifrit is ready to be summoned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsIfritReady => this.AetherFlags.HasFlag(AetherFlags.IfritReady);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Titan is ready to be summoned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsTitanReady => this.AetherFlags.HasFlag(AetherFlags.TitanReady);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Garuda is ready to be summoned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsGarudaReady => this.AetherFlags.HasFlag(AetherFlags.GarudaReady);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Ifrit is currently attuned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Titan is currently attuned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether if Garuda is currently attuned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||||
|
public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned);
|
||||||
|
|
||||||
|
/// <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.AetherflowStacks > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Aetherflow available.
|
||||||
|
/// </summary>
|
||||||
|
public byte AetherflowStacks => (byte)(this.AetherFlags & AetherFlags.Aetherflow);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory WAR job gauge.
|
/// Initializes a new instance of the <see cref="WARGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class WARGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WarriorGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal WARGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of wrath in the Beast gauge.
|
||||||
|
/// </summary>
|
||||||
|
public byte BeastGauge => this.Struct->BeastGauge;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types
|
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>
|
/// <summary>
|
||||||
/// In-memory WHM job gauge.
|
/// Initializes a new instance of the <see cref="WHMGauge"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class WHMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WhiteMageGauge>
|
/// <param name="address">Address of the job gauge.</param>
|
||||||
|
internal WHMGauge(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,153 +6,152 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Keys
|
namespace Dalamud.Game.ClientState.Keys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting
|
||||||
|
/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without
|
||||||
|
/// setting the statue manually.
|
||||||
|
/// index & 0 = key pressed.
|
||||||
|
/// index & 1 = key down (ephemeral).
|
||||||
|
/// index & 2 = key up (ephemeral).
|
||||||
|
/// index & 3 = short key press (ephemeral).
|
||||||
|
/// </remarks>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public class KeyState : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
// The array is accessed in a way that this limit doesn't appear to exist
|
||||||
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
|
// but there is other state data past this point, and keys beyond here aren't
|
||||||
/// </summary>
|
// generally valid for most things anyway
|
||||||
/// <remarks>
|
private const int MaxKeyCode = 0xF0;
|
||||||
/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting
|
private readonly IntPtr bufferBase;
|
||||||
/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without
|
private readonly IntPtr indexBase;
|
||||||
/// setting the statue manually.
|
private VirtualKey[] validVirtualKeyCache = null;
|
||||||
/// index & 0 = key pressed.
|
|
||||||
/// index & 1 = key down (ephemeral).
|
[ServiceManager.ServiceConstructor]
|
||||||
/// index & 2 = key up (ephemeral).
|
private KeyState(SigScanner sigScanner, ClientState clientState)
|
||||||
/// index & 3 = short key press (ephemeral).
|
|
||||||
/// </remarks>
|
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public class KeyState : IServiceType
|
|
||||||
{
|
{
|
||||||
// The array is accessed in a way that this limit doesn't appear to exist
|
var moduleBaseAddress = sigScanner.Module.BaseAddress;
|
||||||
// but there is other state data past this point, and keys beyond here aren't
|
var addressResolver = clientState.AddressResolver;
|
||||||
// generally valid for most things anyway
|
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||||
private const int MaxKeyCode = 0xF0;
|
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
||||||
private readonly IntPtr bufferBase;
|
|
||||||
private readonly IntPtr indexBase;
|
|
||||||
private VirtualKey[] validVirtualKeyCache = null;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
||||||
private KeyState(SigScanner sigScanner, ClientState clientState)
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or set the key-pressed state for a given vkCode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">The virtual key to change.</param>
|
||||||
|
/// <returns>Whether the specified key is currently pressed.</returns>
|
||||||
|
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||||
|
public unsafe bool this[int vkCode]
|
||||||
|
{
|
||||||
|
get => this.GetRawValue(vkCode) != 0;
|
||||||
|
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="this[int]"/>
|
||||||
|
public bool this[VirtualKey vkCode]
|
||||||
|
{
|
||||||
|
get => this[(int)vkCode];
|
||||||
|
set => this[(int)vkCode] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value in the index array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">The virtual key to change.</param>
|
||||||
|
/// <returns>The raw value stored in the index array.</returns>
|
||||||
|
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||||
|
public int GetRawValue(int vkCode)
|
||||||
|
=> this.GetRefValue(vkCode);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GetRawValue(int)"/>
|
||||||
|
public int GetRawValue(VirtualKey vkCode)
|
||||||
|
=> this.GetRawValue((int)vkCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value in the index array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">The virtual key to change.</param>
|
||||||
|
/// <param name="value">The raw value to set in the index array.</param>
|
||||||
|
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
||||||
|
public void SetRawValue(int vkCode, int value)
|
||||||
|
{
|
||||||
|
if (value != 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team.");
|
||||||
|
|
||||||
|
this.GetRefValue(vkCode) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SetRawValue(int, int)"/>
|
||||||
|
public void SetRawValue(VirtualKey vkCode, int value)
|
||||||
|
=> this.SetRawValue((int)vkCode, value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">Virtual key code.</param>
|
||||||
|
/// <returns>If the code is valid.</returns>
|
||||||
|
public bool IsVirtualKeyValid(int vkCode)
|
||||||
|
=> this.ConvertVirtualKey(vkCode) != 0;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
|
||||||
|
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
||||||
|
=> this.IsVirtualKeyValid((int)vkCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an array of virtual keys the game considers valid input.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An array of valid virtual keys.</returns>
|
||||||
|
public VirtualKey[] GetValidVirtualKeys()
|
||||||
|
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the pressed state for all keys.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAll()
|
||||||
|
{
|
||||||
|
foreach (var vk in this.GetValidVirtualKeys())
|
||||||
{
|
{
|
||||||
var moduleBaseAddress = sigScanner.Module.BaseAddress;
|
this[vk] = false;
|
||||||
var addressResolver = clientState.AddressResolver;
|
|
||||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
|
||||||
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
|
||||||
|
|
||||||
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set the key-pressed state for a given vkCode.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <returns>Whether the specified key is currently pressed.</returns>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
|
||||||
public unsafe bool this[int vkCode]
|
|
||||||
{
|
|
||||||
get => this.GetRawValue(vkCode) != 0;
|
|
||||||
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="this[int]"/>
|
|
||||||
public bool this[VirtualKey vkCode]
|
|
||||||
{
|
|
||||||
get => this[(int)vkCode];
|
|
||||||
set => this[(int)vkCode] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value in the index array.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <returns>The raw value stored in the index array.</returns>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
public int GetRawValue(int vkCode)
|
|
||||||
=> this.GetRefValue(vkCode);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="GetRawValue(int)"/>
|
|
||||||
public int GetRawValue(VirtualKey vkCode)
|
|
||||||
=> this.GetRawValue((int)vkCode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the value in the index array.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <param name="value">The raw value to set in the index array.</param>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
|
||||||
public void SetRawValue(int vkCode, int value)
|
|
||||||
{
|
|
||||||
if (value != 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team.");
|
|
||||||
|
|
||||||
this.GetRefValue(vkCode) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SetRawValue(int, int)"/>
|
|
||||||
public void SetRawValue(VirtualKey vkCode, int value)
|
|
||||||
=> this.SetRawValue((int)vkCode, value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">Virtual key code.</param>
|
|
||||||
/// <returns>If the code is valid.</returns>
|
|
||||||
public bool IsVirtualKeyValid(int vkCode)
|
|
||||||
=> this.ConvertVirtualKey(vkCode) != 0;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
|
|
||||||
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
|
||||||
=> this.IsVirtualKeyValid((int)vkCode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an array of virtual keys the game considers valid input.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An array of valid virtual keys.</returns>
|
|
||||||
public VirtualKey[] GetValidVirtualKeys()
|
|
||||||
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the pressed state for all keys.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearAll()
|
|
||||||
{
|
|
||||||
foreach (var vk in this.GetValidVirtualKeys())
|
|
||||||
{
|
|
||||||
this[vk] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a virtual key into the equivalent value that the game uses.
|
|
||||||
/// Valid values are non-zero.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">Virtual key.</param>
|
|
||||||
/// <returns>Converted value.</returns>
|
|
||||||
private unsafe byte ConvertVirtualKey(int vkCode)
|
|
||||||
{
|
|
||||||
if (vkCode <= 0 || vkCode >= MaxKeyCode)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return *(byte*)(this.indexBase + vkCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the raw value from the key state array.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">Virtual key code.</param>
|
|
||||||
/// <returns>A reference to the indexed array.</returns>
|
|
||||||
private unsafe ref int GetRefValue(int vkCode)
|
|
||||||
{
|
|
||||||
vkCode = this.ConvertVirtualKey(vkCode);
|
|
||||||
|
|
||||||
if (vkCode == 0)
|
|
||||||
throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help.");
|
|
||||||
|
|
||||||
return ref *(int*)(this.bufferBase + (4 * vkCode));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a virtual key into the equivalent value that the game uses.
|
||||||
|
/// Valid values are non-zero.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">Virtual key.</param>
|
||||||
|
/// <returns>Converted value.</returns>
|
||||||
|
private unsafe byte ConvertVirtualKey(int vkCode)
|
||||||
|
{
|
||||||
|
if (vkCode <= 0 || vkCode >= MaxKeyCode)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return *(byte*)(this.indexBase + vkCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw value from the key state array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vkCode">Virtual key code.</param>
|
||||||
|
/// <returns>A reference to the indexed array.</returns>
|
||||||
|
private unsafe ref int GetRefValue(int vkCode)
|
||||||
|
{
|
||||||
|
vkCode = this.ConvertVirtualKey(vkCode);
|
||||||
|
|
||||||
|
if (vkCode == 0)
|
||||||
|
throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help.");
|
||||||
|
|
||||||
|
return ref *(int*)(this.bufferBase + (4 * vkCode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,25 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Keys
|
namespace Dalamud.Game.ClientState.Keys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute describing a VirtualKey.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
internal sealed class VirtualKeyAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attribute describing a VirtualKey.
|
/// Initializes a new instance of the <see cref="VirtualKeyAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
/// <param name="fancyName">Fancy name of this key.</param>
|
||||||
internal sealed class VirtualKeyAttribute : Attribute
|
public VirtualKeyAttribute(string fancyName)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.FancyName = fancyName;
|
||||||
/// Initializes a new instance of the <see cref="VirtualKeyAttribute"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fancyName">Fancy name of this key.</param>
|
|
||||||
public VirtualKeyAttribute(string fancyName)
|
|
||||||
{
|
|
||||||
this.FancyName = fancyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the fancy name of this virtual key.
|
|
||||||
/// </summary>
|
|
||||||
public string FancyName { get; init; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the fancy name of this virtual key.
|
||||||
|
/// </summary>
|
||||||
|
public string FancyName { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Keys
|
namespace Dalamud.Game.ClientState.Keys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="VirtualKey"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class VirtualKeyExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension methods for <see cref="VirtualKey"/>.
|
/// Get the fancy name associated with this key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class VirtualKeyExtensions
|
/// <param name="key">The they key to act on.</param>
|
||||||
|
/// <returns>The key's fancy name.</returns>
|
||||||
|
public static string GetFancyName(this VirtualKey key)
|
||||||
{
|
{
|
||||||
/// <summary>
|
return key.GetAttribute<VirtualKeyAttribute>().FancyName;
|
||||||
/// Get the fancy name associated with this key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The they key to act on.</param>
|
|
||||||
/// <returns>The key's fancy name.</returns>
|
|
||||||
public static string GetFancyName(this VirtualKey key)
|
|
||||||
{
|
|
||||||
return key.GetAttribute<VirtualKeyAttribute>().FancyName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
namespace Dalamud.Game.ClientState.Objects.Enums
|
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An Enum describing possible BattleNpc kinds.
|
||||||
|
/// </summary>
|
||||||
|
public enum BattleNpcSubKind : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An Enum describing possible BattleNpc kinds.
|
/// Invalid BattleNpc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum BattleNpcSubKind : byte
|
None = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Invalid BattleNpc.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a Pet.
|
/// BattleNpc representing a Pet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Pet = 2,
|
Pet = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a Chocobo.
|
/// BattleNpc representing a Chocobo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Chocobo = 3,
|
Chocobo = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BattleNpc representing a standard enemy.
|
/// BattleNpc representing a standard enemy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Enemy = 5,
|
Enemy = 5,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,139 +1,138 @@
|
||||||
namespace Dalamud.Game.ClientState.Objects.Enums
|
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This enum describes the indices of the Customize array.
|
||||||
|
/// </summary>
|
||||||
|
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
||||||
|
public enum CustomizeIndex
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This enum describes the indices of the Customize array.
|
/// The race of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
|
Race = 0x00,
|
||||||
public enum CustomizeIndex
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The race of the character.
|
|
||||||
/// </summary>
|
|
||||||
Race = 0x00,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The gender of the character.
|
/// The gender of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Gender = 0x01,
|
Gender = 0x01,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tribe of the character.
|
/// The tribe of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Tribe = 0x04,
|
Tribe = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The height of the character.
|
/// The height of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Height = 0x03,
|
Height = 0x03,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The model type of the character.
|
/// The model type of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
|
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face type of the character.
|
/// The face type of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceType = 0x05,
|
FaceType = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hair of the character.
|
/// The hair of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairStyle = 0x06,
|
HairStyle = 0x06,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the character has hair highlights.
|
/// Whether or not the character has hair highlights.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HasHighlights = 0x07, // negative to enable, positive to disable
|
HasHighlights = 0x07, // negative to enable, positive to disable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The skin color of the character.
|
/// The skin color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SkinColor = 0x08,
|
SkinColor = 0x08,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eye color of the character.
|
/// The eye color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeColor = 0x09, // color of character's right eye
|
EyeColor = 0x09, // color of character's right eye
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hair color of the character.
|
/// The hair color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairColor = 0x0A, // main color
|
HairColor = 0x0A, // main color
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The highlights hair color of the character.
|
/// The highlights hair color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HairColor2 = 0x0B, // highlights color
|
HairColor2 = 0x0B, // highlights color
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face features of the character.
|
/// The face features of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The color of the face features of the character.
|
/// The color of the face features of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FaceFeaturesColor = 0x0D,
|
FaceFeaturesColor = 0x0D,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eyebrows of the character.
|
/// The eyebrows of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Eyebrows = 0x0E,
|
Eyebrows = 0x0E,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The 2nd eye color of the character.
|
/// The 2nd eye color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeColor2 = 0x0F, // color of character's left eye
|
EyeColor2 = 0x0F, // color of character's left eye
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The eye shape of the character.
|
/// The eye shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EyeShape = 0x10,
|
EyeShape = 0x10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The nose shape of the character.
|
/// The nose shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NoseShape = 0x11,
|
NoseShape = 0x11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The jaw shape of the character.
|
/// The jaw shape of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JawShape = 0x12,
|
JawShape = 0x12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lip style of the character.
|
/// The lip style of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lip color of the character.
|
/// The lip color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LipColor = 0x14,
|
LipColor = 0x14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The race feature size of the character.
|
/// The race feature size of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RaceFeatureSize = 0x15,
|
RaceFeatureSize = 0x15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The race feature type of the character.
|
/// The race feature type of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
|
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bust size of the character.
|
/// The bust size of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face paint of the character.
|
/// The face paint of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Facepaint = 0x18,
|
Facepaint = 0x18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The face paint color of the character.
|
/// The face paint color of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FacepaintColor = 0x19,
|
FacepaintColor = 0x19,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,82 @@
|
||||||
namespace Dalamud.Game.ClientState.Objects.Enums
|
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum describing possible entity kinds.
|
||||||
|
/// </summary>
|
||||||
|
public enum ObjectKind : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum describing possible entity kinds.
|
/// Invalid character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ObjectKind : byte
|
None = 0x00,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Invalid character.
|
|
||||||
/// </summary>
|
|
||||||
None = 0x00,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing player characters.
|
/// Objects representing player characters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Player = 0x01,
|
Player = 0x01,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing battle NPCs.
|
/// Objects representing battle NPCs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BattleNpc = 0x02,
|
BattleNpc = 0x02,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing event NPCs.
|
/// Objects representing event NPCs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EventNpc = 0x03,
|
EventNpc = 0x03,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing treasures.
|
/// Objects representing treasures.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Treasure = 0x04,
|
Treasure = 0x04,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing aetherytes.
|
/// Objects representing aetherytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Aetheryte = 0x05,
|
Aetheryte = 0x05,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing gathering points.
|
/// Objects representing gathering points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GatheringPoint = 0x06,
|
GatheringPoint = 0x06,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing event objects.
|
/// Objects representing event objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EventObj = 0x07,
|
EventObj = 0x07,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing mounts.
|
/// Objects representing mounts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MountType = 0x08,
|
MountType = 0x08,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing minions.
|
/// Objects representing minions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Companion = 0x09, // Minion
|
Companion = 0x09, // Minion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing retainers.
|
/// Objects representing retainers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Retainer = 0x0A,
|
Retainer = 0x0A,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing area objects.
|
/// Objects representing area objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Area = 0x0B,
|
Area = 0x0B,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing housing objects.
|
/// Objects representing housing objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Housing = 0x0C,
|
Housing = 0x0C,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing cutscene objects.
|
/// Objects representing cutscene objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Cutscene = 0x0D,
|
Cutscene = 0x0D,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Objects representing card stand objects.
|
/// Objects representing card stand objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CardStand = 0x0E,
|
CardStand = 0x0E,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,55 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Enums
|
namespace Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum describing possible status flags.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum StatusFlags : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum describing possible status flags.
|
/// No status flags set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
None = 0,
|
||||||
public enum StatusFlags : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No status flags set.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hostile character.
|
/// Hostile character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Hostile = 1,
|
Hostile = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character in combat.
|
/// Character in combat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InCombat = 2,
|
InCombat = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character weapon is out.
|
/// Character weapon is out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WeaponOut = 4,
|
WeaponOut = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character offhand is out.
|
/// Character offhand is out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OffhandOut = 8,
|
OffhandOut = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is a party member.
|
/// Character is a party member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PartyMember = 16,
|
PartyMember = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is a alliance member.
|
/// Character is a alliance member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AllianceMember = 32,
|
AllianceMember = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is in friend list.
|
/// Character is in friend list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Friend = 64,
|
Friend = 64,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Character is casting.
|
/// Character is casting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IsCasting = 128,
|
IsCasting = 128,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,138 +9,137 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects
|
namespace Dalamud.Game.ClientState.Objects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the currently spawned FFXIV game objects.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed partial class ObjectTable : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const int ObjectTableLength = 596;
|
||||||
/// This collection represents the currently spawned FFXIV game objects.
|
|
||||||
/// </summary>
|
private readonly ClientStateAddressResolver address;
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
[ServiceManager.ServiceConstructor]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
private ObjectTable(ClientState clientState)
|
||||||
public sealed partial class ObjectTable : IServiceType
|
|
||||||
{
|
{
|
||||||
private const int ObjectTableLength = 596;
|
this.address = clientState.AddressResolver;
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
/// <summary>
|
||||||
private ObjectTable(ClientState clientState)
|
/// 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>
|
||||||
|
public GameObject? this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
this.address = clientState.AddressResolver;
|
var address = this.GetObjectAddress(index);
|
||||||
|
return this.CreateObjectReference(address);
|
||||||
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>
|
|
||||||
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>
|
|
||||||
public GameObject? SearchById(uint objectId)
|
|
||||||
{
|
|
||||||
if (objectId is GameObject.InvalidGameObjectId or 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
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>
|
|
||||||
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
|
||||||
{
|
|
||||||
var clientState = Service<ClientState>.GetNullable();
|
|
||||||
|
|
||||||
if (clientState == null || 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),
|
|
||||||
ObjectKind.BattleNpc => new BattleNpc(address),
|
|
||||||
ObjectKind.EventObj => new EventObj(address),
|
|
||||||
ObjectKind.Companion => new Npc(address),
|
|
||||||
_ => new GameObject(address),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently spawned FFXIV game objects.
|
/// Search for a game object by their Object ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
|
/// <param name="objectId">Object ID to find.</param>
|
||||||
|
/// <returns>A game object or null.</returns>
|
||||||
|
public GameObject? SearchById(uint objectId)
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
if (objectId is GameObject.InvalidGameObjectId or 0)
|
||||||
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
return null;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
foreach (var obj in this)
|
||||||
public IEnumerator<GameObject> GetEnumerator()
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < ObjectTableLength; i++)
|
if (obj == null)
|
||||||
{
|
continue;
|
||||||
var obj = this[i];
|
|
||||||
|
|
||||||
if (obj == null)
|
if (obj.ObjectId == objectId)
|
||||||
continue;
|
return obj;
|
||||||
|
|
||||||
yield return obj;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
return null;
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
||||||
|
{
|
||||||
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
|
if (clientState == null || 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),
|
||||||
|
ObjectKind.BattleNpc => new BattleNpc(address),
|
||||||
|
ObjectKind.EventObj => new EventObj(address),
|
||||||
|
ObjectKind.Companion => new Npc(address),
|
||||||
|
_ => new GameObject(address),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,28 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a battle NPC.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class BattleNpc : BattleChara
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a battle NPC.
|
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
|
||||||
|
/// Set up a new BattleNpc with the provided memory representation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BattleNpc : BattleChara
|
/// <param name="address">The address of this actor in memory.</param>
|
||||||
|
internal BattleNpc(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
|
|
||||||
/// Set up a new BattleNpc with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
|
||||||
internal BattleNpc(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
|
||||||
/// </summary>
|
|
||||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override uint TargetObjectId => this.Struct->Character.TargetObjectID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
||||||
|
/// </summary>
|
||||||
|
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override uint TargetObjectId => this.Struct->Character.TargetObjectID;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an EventObj.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class EventObj : GameObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents an EventObj.
|
/// Initializes a new instance of the <see cref="EventObj"/> class.
|
||||||
|
/// Set up a new EventObj with the provided memory representation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class EventObj : GameObject
|
/// <param name="address">The address of this event object in memory.</param>
|
||||||
|
internal EventObj(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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>
|
|
||||||
internal EventObj(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a NPC.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class Npc : Character
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a NPC.
|
/// Initializes a new instance of the <see cref="Npc"/> class.
|
||||||
|
/// Set up a new NPC with the provided memory representation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class Npc : Character
|
/// <param name="address">The address of this actor in memory.</param>
|
||||||
|
internal Npc(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Npc"/> class.
|
|
||||||
/// Set up a new NPC with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
|
||||||
internal Npc(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,36 +3,35 @@ using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.SubKinds
|
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a player character.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class PlayerCharacter : BattleChara
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a player character.
|
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
|
||||||
|
/// This represents a player character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class PlayerCharacter : BattleChara
|
/// <param name="address">The address of this actor in memory.</param>
|
||||||
|
internal PlayerCharacter(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
|
|
||||||
/// This represents a player character.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this actor in memory.</param>
|
|
||||||
internal PlayerCharacter(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the target actor ID of the PlayerCharacter.
|
|
||||||
/// </summary>
|
|
||||||
public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the target actor ID of the PlayerCharacter.
|
||||||
|
/// </summary>
|
||||||
|
public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,165 +4,164 @@ using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects
|
namespace Dalamud.Game.ClientState.Objects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get and set various kinds of targets for the player.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed unsafe class TargetManager : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
[ServiceManager.ServiceDependency]
|
||||||
/// Get and set various kinds of targets for the player.
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
/// </summary>
|
|
||||||
[PluginInterface]
|
[ServiceManager.ServiceDependency]
|
||||||
[InterfaceVersion("1.0")]
|
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed unsafe class TargetManager : IServiceType
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private TargetManager()
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
this.address = this.clientState.AddressResolver;
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private TargetManager()
|
|
||||||
{
|
|
||||||
this.address = this.clientState.AddressResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the target manager.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address => this.address.TargetManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current target.
|
|
||||||
/// </summary>
|
|
||||||
public GameObject? Target
|
|
||||||
{
|
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
|
|
||||||
set => this.SetTarget(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the mouseover target.
|
|
||||||
/// </summary>
|
|
||||||
public GameObject? MouseOverTarget
|
|
||||||
{
|
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
|
||||||
set => this.SetMouseOverTarget(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the focus target.
|
|
||||||
/// </summary>
|
|
||||||
public GameObject? FocusTarget
|
|
||||||
{
|
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
|
||||||
set => this.SetFocusTarget(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the previous target.
|
|
||||||
/// </summary>
|
|
||||||
public GameObject? PreviousTarget
|
|
||||||
{
|
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
|
||||||
set => this.SetPreviousTarget(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the soft target.
|
|
||||||
/// </summary>
|
|
||||||
public GameObject? SoftTarget
|
|
||||||
{
|
|
||||||
get => this.objectTable.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.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the focus target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the target manager.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address => this.address.TargetManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current target.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? Target
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
|
||||||
|
set => this.SetTarget(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mouseover target.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? MouseOverTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||||
|
set => this.SetMouseOverTarget(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the focus target.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? FocusTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||||
|
set => this.SetFocusTarget(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the previous target.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? PreviousTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||||
|
set => this.SetPreviousTarget(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the soft target.
|
||||||
|
/// </summary>
|
||||||
|
public GameObject? SoftTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.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.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the focus target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">Actor to target.</param>
|
||||||
|
public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,66 +2,65 @@ using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents the battle characters.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class BattleChara : Character
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the battle characters.
|
/// Initializes a new instance of the <see cref="BattleChara"/> class.
|
||||||
|
/// This represents a battle character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BattleChara : Character
|
/// <param name="address">The address of this character in memory.</param>
|
||||||
|
internal BattleChara(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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>
|
|
||||||
internal BattleChara(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current status effects.
|
|
||||||
/// </summary>
|
|
||||||
public StatusList StatusList => new(&this.Struct->StatusManager);
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current status effects.
|
||||||
|
/// </summary>
|
||||||
|
public StatusList StatusList => new(&this.Struct->StatusManager);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,107 +6,106 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents the base for non-static entities.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class Character : GameObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the base for non-static entities.
|
/// Initializes a new instance of the <see cref="Character"/> class.
|
||||||
|
/// This represents a non-static entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class Character : GameObject
|
/// <param name="address">The address of this character in memory.</param>
|
||||||
|
internal Character(IntPtr address)
|
||||||
|
: base(address)
|
||||||
{
|
{
|
||||||
/// <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>
|
|
||||||
internal Character(IntPtr address)
|
|
||||||
: base(address)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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<ClassJob> ClassJob => new(this.Struct->ClassJob);
|
|
||||||
|
|
||||||
/// <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->TargetObjectID;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name ID of the character.
|
|
||||||
/// </summary>
|
|
||||||
public uint NameId => this.Struct->NameID;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current online status of the character.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->OnlineStatus);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the status flags.
|
|
||||||
/// </summary>
|
|
||||||
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the underlying structure.
|
|
||||||
/// </summary>
|
|
||||||
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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<ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||||
|
|
||||||
|
/// <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->TargetObjectID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name ID of the character.
|
||||||
|
/// </summary>
|
||||||
|
public uint NameId => this.Struct->NameID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current online status of the character.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->OnlineStatus);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the status flags.
|
||||||
|
/// </summary>
|
||||||
|
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the underlying structure.
|
||||||
|
/// </summary>
|
||||||
|
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,171 +5,170 @@ using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a GameObject in FFXIV.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe partial class GameObject : IEquatable<GameObject>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a GameObject in FFXIV.
|
/// IDs of non-networked GameObjects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe partial class GameObject : IEquatable<GameObject>
|
public const uint InvalidGameObjectId = 0xE0000000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameObject"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address of this game object in memory.</param>
|
||||||
|
internal GameObject(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = address;
|
||||||
/// IDs of non-networked GameObjects.
|
|
||||||
/// </summary>
|
|
||||||
public const uint InvalidGameObjectId = 0xE0000000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameObject"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this game object in memory.</param>
|
|
||||||
internal GameObject(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
var clientState = Service<ClientState>.GetNullable();
|
|
||||||
|
|
||||||
if (actor is null || clientState == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (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>
|
/// <summary>
|
||||||
/// This class represents a basic actor (GameObject) in FFXIV.
|
/// Gets the address of the game object in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe partial class GameObject
|
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)
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Using == results in a stack overflow.
|
||||||
/// Gets the name of this <see cref="GameObject" />.
|
if (gameObject1 is null || gameObject2 is null)
|
||||||
/// </summary>
|
return Equals(gameObject1, gameObject2);
|
||||||
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64);
|
|
||||||
|
|
||||||
/// <summary>
|
return gameObject1.Equals(gameObject2);
|
||||||
/// 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>
|
|
||||||
// TODO: Fix for non-networked GameObjects
|
|
||||||
public virtual GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the underlying structure.
|
|
||||||
/// </summary>
|
|
||||||
protected internal FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
|
if (actor is null || clientState == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (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, 64);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
// TODO: Fix for non-networked GameObjects
|
||||||
|
public virtual GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the underlying structure.
|
||||||
|
/// </summary>
|
||||||
|
protected internal FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,180 +7,179 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Party
|
namespace Dalamud.Game.ClientState.Party;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the actors present in your party or alliance.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed unsafe partial class PartyList : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const int GroupLength = 8;
|
||||||
/// This collection represents the actors present in your party or alliance.
|
private const int AllianceLength = 20;
|
||||||
/// </summary>
|
|
||||||
[PluginInterface]
|
[ServiceManager.ServiceDependency]
|
||||||
[InterfaceVersion("1.0")]
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed unsafe partial class PartyList : IServiceType
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private PartyList()
|
||||||
{
|
{
|
||||||
private const int GroupLength = 8;
|
this.address = this.clientState.AddressResolver;
|
||||||
private const int AllianceLength = 20;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private PartyList()
|
|
||||||
{
|
|
||||||
this.address = this.clientState.AddressResolver;
|
|
||||||
|
|
||||||
Log.Verbose($"Group manager address 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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ID of the party.
|
|
||||||
/// </summary>
|
|
||||||
public long PartyId => this.GroupManagerStruct->PartyId;
|
|
||||||
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
|
||||||
{
|
|
||||||
if (this.clientState.LocalContentId == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new PartyMember(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
|
||||||
{
|
|
||||||
if (this.clientState.LocalContentId == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new PartyMember(address);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the party members present in your party or alliance.
|
/// Gets the amount of party members the local player has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
|
public int Length => this.GroupManagerStruct->MemberCount;
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public IEnumerator<PartyMember> GetEnumerator()
|
/// 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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the party.
|
||||||
|
/// </summary>
|
||||||
|
public long PartyId => this.GroupManagerStruct->PartyId;
|
||||||
|
|
||||||
|
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>
|
||||||
|
public PartyMember? this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||||
for (var i = 0; i < this.Length; i++)
|
if (index < 0 || index >= this.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (this.Length > GroupLength)
|
||||||
{
|
{
|
||||||
var member = this[i];
|
var addr = this.GetAllianceMemberAddress(index);
|
||||||
|
return this.CreateAllianceMemberReference(addr);
|
||||||
if (member == null)
|
}
|
||||||
break;
|
else
|
||||||
|
{
|
||||||
yield return member;
|
var addr = this.GetPartyMemberAddress(index);
|
||||||
|
return this.CreatePartyMemberReference(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
/// 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>
|
||||||
|
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
||||||
|
{
|
||||||
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (address == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PartyMember(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||||
|
{
|
||||||
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (address == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PartyMember(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,105 +8,104 @@ using Dalamud.Game.ClientState.Statuses;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Party
|
namespace Dalamud.Game.ClientState.Party;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a party member in the group manager.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class PartyMember
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a party member in the group manager.
|
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class PartyMember
|
/// <param name="address">Address of the party member.</param>
|
||||||
|
internal PartyMember(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = address;
|
||||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the party member.</param>
|
|
||||||
internal PartyMember(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
public GameObject? GameObject => Service<ObjectTable>.Get().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);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the World this party member resides in.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld);
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public GameObject? GameObject => Service<ObjectTable>.Get().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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the World this party member resides in.
|
||||||
|
/// </summary>
|
||||||
|
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld);
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
||||||
|
/// <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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,37 @@
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Resolvers
|
namespace Dalamud.Game.ClientState.Resolvers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This object resolves a rowID within an Excel sheet.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||||
|
public class ExcelResolver<T> where T : ExcelRow
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This object resolves a rowID within an Excel sheet.
|
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
/// <param name="id">The ID of the classJob.</param>
|
||||||
public class ExcelResolver<T> where T : ExcelRow
|
internal ExcelResolver(uint id)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Id = id;
|
||||||
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The ID of the classJob.</param>
|
|
||||||
internal ExcelResolver(uint id)
|
|
||||||
{
|
|
||||||
this.Id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ID to be resolved.
|
|
||||||
/// </summary>
|
|
||||||
public uint Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets GameData linked to this excel row.
|
|
||||||
/// </summary>
|
|
||||||
public T? GameData => Service<DataManager>.Get().GetExcelSheet<T>()?.GetRow(this.Id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets GameData linked to this excel row with the specified language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="language">The language.</param>
|
|
||||||
/// <returns>The ExcelRow in the specified language.</returns>
|
|
||||||
public T? GetWithLanguage(ClientLanguage language) => Service<DataManager>.Get().GetExcelSheet<T>(language)?.GetRow(this.Id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID to be resolved.
|
||||||
|
/// </summary>
|
||||||
|
public uint Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets GameData linked to this excel row.
|
||||||
|
/// </summary>
|
||||||
|
public T? GameData => Service<DataManager>.Get().GetExcelSheet<T>()?.GetRow(this.Id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets GameData linked to this excel row with the specified language.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="language">The language.</param>
|
||||||
|
/// <returns>The ExcelRow in the specified language.</returns>
|
||||||
|
public T? GetWithLanguage(ClientLanguage language) => Service<DataManager>.Get().GetExcelSheet<T>(language)?.GetRow(this.Id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,65 +4,64 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Statuses
|
namespace Dalamud.Game.ClientState.Statuses;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a status effect an actor is afflicted by.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class Status
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a status effect an actor is afflicted by.
|
/// Initializes a new instance of the <see cref="Status"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class Status
|
/// <param name="address">Status address.</param>
|
||||||
|
internal Status(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = address;
|
||||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Status address.</param>
|
|
||||||
internal Status(IntPtr address)
|
|
||||||
{
|
|
||||||
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).GameData;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parameter value of the status.
|
|
||||||
/// </summary>
|
|
||||||
public ushort 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>
|
|
||||||
public GameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.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).GameData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parameter value of the status.
|
||||||
|
/// </summary>
|
||||||
|
public ushort 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>
|
||||||
|
public GameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
||||||
|
|
||||||
|
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,159 +3,158 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Statuses
|
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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the status effects an actor is afflicted by.
|
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe partial class StatusList
|
/// <param name="address">Address of the status list.</param>
|
||||||
|
internal StatusList(IntPtr address)
|
||||||
{
|
{
|
||||||
private const int StatusListLength = 30;
|
this.Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the status list.</param>
|
/// <param name="pointer">Pointer to the status list.</param>
|
||||||
internal StatusList(IntPtr address)
|
internal unsafe StatusList(void* pointer)
|
||||||
|
: this((IntPtr)pointer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the status list in memory.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of status effect slots the actor has.
|
||||||
|
/// </summary>
|
||||||
|
public int Length => StatusListLength;
|
||||||
|
|
||||||
|
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>
|
||||||
|
public Status? this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
this.Address = address;
|
if (index < 0 || index > StatusListLength)
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pointer">Pointer to the status list.</param>
|
|
||||||
internal unsafe StatusList(void* pointer)
|
|
||||||
: this((IntPtr)pointer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the status list in memory.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of status effect slots the actor has.
|
|
||||||
/// </summary>
|
|
||||||
public int Length => StatusListLength;
|
|
||||||
|
|
||||||
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>
|
|
||||||
public Status? this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (index < 0 || index > StatusListLength)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var addr = this.GetStatusAddress(index);
|
|
||||||
return CreateStatusReference(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a reference to an FFXIV actor status list.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of the status list in memory.</param>
|
|
||||||
/// <returns>The status object containing the requested data.</returns>
|
|
||||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
|
||||||
{
|
|
||||||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
|
||||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
|
||||||
// here or somewhere else.
|
|
||||||
var clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
var addr = this.GetStatusAddress(index);
|
||||||
return null;
|
return CreateStatusReference(addr);
|
||||||
|
|
||||||
return new StatusList(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
public static Status? CreateStatusReference(IntPtr address)
|
|
||||||
{
|
|
||||||
var clientState = Service<ClientState>.Get();
|
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new Status(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
/// <summary>
|
||||||
/// This collection represents the status effects an actor is afflicted by.
|
/// Create a reference to an FFXIV actor status list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
/// <param name="address">The address of the status list in memory.</param>
|
||||||
|
/// <returns>The status object containing the requested data.</returns>
|
||||||
|
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||||
|
// here or somewhere else.
|
||||||
|
var clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (clientState.LocalContentId == 0)
|
||||||
int ICollection.Count => this.Length;
|
return null;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (address == IntPtr.Zero)
|
||||||
bool ICollection.IsSynchronized => false;
|
return null;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
return new StatusList(address);
|
||||||
object ICollection.SyncRoot => this;
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public IEnumerator<Status> GetEnumerator()
|
/// 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>
|
||||||
|
public static Status? CreateStatusReference(IntPtr address)
|
||||||
|
{
|
||||||
|
var clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
|
if (clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (address == IntPtr.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Status(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// 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++)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < StatusListLength; i++)
|
var status = this[i];
|
||||||
{
|
|
||||||
var status = this[i];
|
|
||||||
|
|
||||||
if (status == null || status.StatusId == 0)
|
if (status == null || status.StatusId == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
yield return status;
|
yield return status;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void ICollection.CopyTo(Array array, int index)
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.Length; i++)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
array.SetValue(this[i], index);
|
||||||
{
|
index++;
|
||||||
array.SetValue(this[i], index);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,35 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs
|
namespace Dalamud.Game.ClientState.Structs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Native memory representation of a FFXIV status effect.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Native memory representation of a FFXIV status effect.
|
/// The effect ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
public short EffectId;
|
||||||
public struct StatusEffect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The effect ID.
|
|
||||||
/// </summary>
|
|
||||||
public short EffectId;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many stacks are present.
|
/// How many stacks are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte StackCount;
|
public byte StackCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional parameters.
|
/// Additional parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte Param;
|
public byte Param;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duration remaining.
|
/// The duration remaining.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Duration;
|
public float Duration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the actor that caused this effect.
|
/// The ID of the actor that caused this effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int OwnerId;
|
public int OwnerId;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,47 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Dalamud.Game.Command
|
namespace Dalamud.Game.Command;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class describes a registered command.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CommandInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class describes a registered command.
|
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
||||||
|
/// Create a new CommandInfo with the provided handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CommandInfo
|
/// <param name="handler">The method to call when the command is run.</param>
|
||||||
|
public CommandInfo(HandlerDelegate handler)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Handler = handler;
|
||||||
/// Initializes a new instance of the <see cref="CommandInfo"/> class.
|
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||||
/// Create a new CommandInfo with the provided handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler">The method to call when the command is run.</param>
|
|
||||||
public CommandInfo(HandlerDelegate handler)
|
|
||||||
{
|
|
||||||
this.Handler = handler;
|
|
||||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The function to be executed when the command is dispatched.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command itself.</param>
|
|
||||||
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
|
|
||||||
public delegate void HandlerDelegate(string command, string arguments);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
|
||||||
/// </summary>
|
|
||||||
public HandlerDelegate Handler { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the help message for this command.
|
|
||||||
/// </summary>
|
|
||||||
public string HelpMessage { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether if this command should be shown in the help output.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowInHelp { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the assembly responsible for this command.
|
|
||||||
/// </summary>
|
|
||||||
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The function to be executed when the command is dispatched.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">The command itself.</param>
|
||||||
|
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
|
||||||
|
public delegate void HandlerDelegate(string command, string arguments);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
||||||
|
/// </summary>
|
||||||
|
public HandlerDelegate Handler { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the help message for this command.
|
||||||
|
/// </summary>
|
||||||
|
public string HelpMessage { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether if this command should be shown in the help output.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowInHelp { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the assembly responsible for this command.
|
||||||
|
/// </summary>
|
||||||
|
internal string LoaderAssemblyName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,173 +10,172 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Command
|
namespace Dalamud.Game.Command;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class manages registered in-game slash commands.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed class CommandManager : IServiceType, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
||||||
/// This class manages registered in-game slash commands.
|
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||||
/// </summary>
|
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||||
[PluginInterface]
|
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||||
[InterfaceVersion("1.0")]
|
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
||||||
public sealed class CommandManager : IServiceType, IDisposable
|
private readonly Regex currentLangCommandRegex;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private CommandManager(DalamudStartInfo startInfo)
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
this.currentLangCommandRegex = startInfo.Language switch
|
||||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
|
||||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
|
||||||
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
|
||||||
private readonly Regex commandRegexFr = new(@"^La commande texte “(?<command>.+)” n'existe pas\.$", RegexOptions.Compiled);
|
|
||||||
private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
|
||||||
private readonly Regex currentLangCommandRegex;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private CommandManager(DalamudStartInfo startInfo)
|
|
||||||
{
|
{
|
||||||
this.currentLangCommandRegex = startInfo.Language switch
|
ClientLanguage.Japanese => this.commandRegexJp,
|
||||||
{
|
ClientLanguage.English => this.commandRegexEn,
|
||||||
ClientLanguage.Japanese => this.commandRegexJp,
|
ClientLanguage.German => this.commandRegexDe,
|
||||||
ClientLanguage.English => this.commandRegexEn,
|
ClientLanguage.French => this.commandRegexFr,
|
||||||
ClientLanguage.German => this.commandRegexDe,
|
_ => this.currentLangCommandRegex,
|
||||||
ClientLanguage.French => this.commandRegexFr,
|
};
|
||||||
_ => this.currentLangCommandRegex,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only list of all registered commands.
|
/// Gets a read-only list of all registered commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process a command in full.
|
/// Process a command in full.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">The full command string.</param>
|
/// <param name="content">The full command string.</param>
|
||||||
/// <returns>True if the command was found and dispatched.</returns>
|
/// <returns>True if the command was found and dispatched.</returns>
|
||||||
public bool ProcessCommand(string content)
|
public bool ProcessCommand(string content)
|
||||||
|
{
|
||||||
|
string command;
|
||||||
|
string argument;
|
||||||
|
|
||||||
|
var separatorPosition = content.IndexOf(' ');
|
||||||
|
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
||||||
{
|
{
|
||||||
string command;
|
// If no space was found or ends with the space. Process them as a no argument
|
||||||
string argument;
|
if (separatorPosition + 1 >= content.Length)
|
||||||
|
|
||||||
var separatorPosition = content.IndexOf(' ');
|
|
||||||
if (separatorPosition == -1 || separatorPosition + 1 >= content.Length)
|
|
||||||
{
|
{
|
||||||
// If no space was found or ends with the space. Process them as a no argument
|
// Remove the trailing space
|
||||||
if (separatorPosition + 1 >= content.Length)
|
command = content.Substring(0, separatorPosition);
|
||||||
{
|
|
||||||
// Remove the trailing space
|
|
||||||
command = content.Substring(0, separatorPosition);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
command = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
argument = string.Empty;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// e.g.)
|
command = content;
|
||||||
// /testcommand arg1
|
|
||||||
// => Total of 17 chars
|
|
||||||
// => command: 0-12 (12 chars)
|
|
||||||
// => argument: 13-17 (4 chars)
|
|
||||||
// => content.IndexOf(' ') == 12
|
|
||||||
command = content.Substring(0, separatorPosition);
|
|
||||||
|
|
||||||
var argStart = separatorPosition + 1;
|
|
||||||
argument = content[argStart..];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
argument = string.Empty;
|
||||||
return false;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// e.g.)
|
||||||
|
// /testcommand arg1
|
||||||
|
// => Total of 17 chars
|
||||||
|
// => command: 0-12 (12 chars)
|
||||||
|
// => argument: 13-17 (4 chars)
|
||||||
|
// => content.IndexOf(' ') == 12
|
||||||
|
command = content.Substring(0, separatorPosition);
|
||||||
|
|
||||||
this.DispatchCommand(command, argument, handler);
|
var argStart = separatorPosition + 1;
|
||||||
|
argument = content[argStart..];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.DispatchCommand(command, argument, handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispatch the handling of a command.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">The command to dispatch.</param>
|
||||||
|
/// <param name="argument">The provided arguments.</param>
|
||||||
|
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
||||||
|
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
info.Handler(command, argument);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">The command to register.</param>
|
||||||
|
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
||||||
|
/// <returns>If adding was successful.</returns>
|
||||||
|
public bool AddHandler(string command, CommandInfo info)
|
||||||
|
{
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.commandMap.Add(command, info);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
/// <summary>
|
|
||||||
/// Dispatch the handling of a command.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command to dispatch.</param>
|
|
||||||
/// <param name="argument">The provided arguments.</param>
|
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
|
||||||
public void DispatchCommand(string command, string argument, CommandInfo info)
|
|
||||||
{
|
{
|
||||||
try
|
Log.Error("Command {CommandName} is already registered.", command);
|
||||||
{
|
return false;
|
||||||
info.Handler(command, argument);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
|
/// Remove a command from the command handlers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="command">The command to register.</param>
|
/// <param name="command">The command to remove.</param>
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
/// <returns>If the removal was successful.</returns>
|
||||||
/// <returns>If adding was successful.</returns>
|
public bool RemoveHandler(string command)
|
||||||
public bool AddHandler(string command, CommandInfo info)
|
{
|
||||||
|
return this.commandMap.Remove(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
|
{
|
||||||
|
if (type == XivChatType.ErrorMessage && senderId == 0)
|
||||||
{
|
{
|
||||||
if (info == null)
|
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
||||||
throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
if (cmdMatch.Success)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
this.commandMap.Add(command, info);
|
// Yes, it's a chat command.
|
||||||
return true;
|
var command = cmdMatch.Value;
|
||||||
|
if (this.ProcessCommand(command)) isHandled = true;
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Command {CommandName} is already registered.", command);
|
// Always match for china, since they patch in language files without changing the ClientLanguage.
|
||||||
return false;
|
cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a command from the command handlers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command to remove.</param>
|
|
||||||
/// <returns>If the removal was successful.</returns>
|
|
||||||
public bool RemoveHandler(string command)
|
|
||||||
{
|
|
||||||
return this.commandMap.Remove(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
|
||||||
{
|
|
||||||
if (type == XivChatType.ErrorMessage && senderId == 0)
|
|
||||||
{
|
|
||||||
var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
|
|
||||||
if (cmdMatch.Success)
|
if (cmdMatch.Success)
|
||||||
{
|
{
|
||||||
// Yes, it's a chat command.
|
// Yes, it's a Chinese fallback chat command.
|
||||||
var command = cmdMatch.Value;
|
var command = cmdMatch.Value;
|
||||||
if (this.ProcessCommand(command)) isHandled = true;
|
if (this.ProcessCommand(command)) isHandled = true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Always match for china, since they patch in language files without changing the ClientLanguage.
|
|
||||||
cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
|
|
||||||
if (cmdMatch.Success)
|
|
||||||
{
|
|
||||||
// Yes, it's a Chinese fallback chat command.
|
|
||||||
var command = cmdMatch.Value;
|
|
||||||
if (this.ProcessCommand(command)) isHandled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,49 +1,48 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="Framework"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed unsafe class FrameworkAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The address resolver for the <see cref="Framework"/> class.
|
/// Gets the base address of the Framework object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe class FrameworkAddressResolver : BaseAddressResolver
|
[Obsolete("Please use FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance() instead.")]
|
||||||
|
public IntPtr BaseAddress => new(FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address for the function that is called once the Framework is destroyed.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr DestroyAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address for the function that is called once the Framework is free'd.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr FreeAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the function that is called every tick.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr TickAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.SetupFramework(sig);
|
||||||
/// Gets the base address of the Framework object.
|
}
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Please use FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance() instead.")]
|
|
||||||
public IntPtr BaseAddress => new(FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance());
|
|
||||||
|
|
||||||
/// <summary>
|
private void SetupFramework(SigScanner scanner)
|
||||||
/// Gets the address for the function that is called once the Framework is destroyed.
|
{
|
||||||
/// </summary>
|
this.DestroyAddress =
|
||||||
public IntPtr DestroyAddress { get; private set; }
|
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF");
|
||||||
|
|
||||||
/// <summary>
|
this.FreeAddress =
|
||||||
/// Gets the address for the function that is called once the Framework is free'd.
|
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ??");
|
||||||
/// </summary>
|
|
||||||
public IntPtr FreeAddress { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
this.TickAddress =
|
||||||
/// Gets the function that is called every tick.
|
scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??");
|
||||||
/// </summary>
|
|
||||||
public IntPtr TickAddress { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
this.SetupFramework(sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupFramework(SigScanner scanner)
|
|
||||||
{
|
|
||||||
this.DestroyAddress =
|
|
||||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF");
|
|
||||||
|
|
||||||
this.FreeAddress =
|
|
||||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ??");
|
|
||||||
|
|
||||||
this.TickAddress =
|
|
||||||
scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,405 +5,404 @@ using System.Text;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
||||||
|
/// day, major and minor. All components may be unspecified, which is represented
|
||||||
|
/// internally as a -1. By definition, an unspecified component matches anything
|
||||||
|
/// (both unspecified and specified), and an unspecified component is "less than" any
|
||||||
|
/// specified component. It will also equal the string "any" if all components are
|
||||||
|
/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game
|
||||||
|
/// installation directory.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
|
||||||
{
|
{
|
||||||
|
private static readonly GameVersion AnyVersion = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
/// day, major and minor. All components may be unspecified, which is represented
|
|
||||||
/// internally as a -1. By definition, an unspecified component matches anything
|
|
||||||
/// (both unspecified and specified), and an unspecified component is "less than" any
|
|
||||||
/// specified component. It will also equal the string "any" if all components are
|
|
||||||
/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game
|
|
||||||
/// installation directory.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
/// <param name="version">Version string to parse.</param>
|
||||||
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
|
[JsonConstructor]
|
||||||
|
public GameVersion(string version)
|
||||||
{
|
{
|
||||||
private static readonly GameVersion AnyVersion = new();
|
var ver = Parse(version);
|
||||||
|
this.Year = ver.Year;
|
||||||
|
this.Month = ver.Month;
|
||||||
|
this.Day = ver.Day;
|
||||||
|
this.Major = ver.Major;
|
||||||
|
this.Minor = ver.Minor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="version">Version string to parse.</param>
|
/// <param name="year">The year.</param>
|
||||||
[JsonConstructor]
|
/// <param name="month">The month.</param>
|
||||||
public GameVersion(string version)
|
/// <param name="day">The day.</param>
|
||||||
|
/// <param name="major">The major version.</param>
|
||||||
|
/// <param name="minor">The minor version.</param>
|
||||||
|
public GameVersion(int year, int month, int day, int major, int minor)
|
||||||
|
{
|
||||||
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
|
||||||
|
if ((this.Month = month) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
|
|
||||||
|
if ((this.Day = day) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(day));
|
||||||
|
|
||||||
|
if ((this.Major = major) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(major));
|
||||||
|
|
||||||
|
if ((this.Minor = minor) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(minor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The year.</param>
|
||||||
|
/// <param name="month">The month.</param>
|
||||||
|
/// <param name="day">The day.</param>
|
||||||
|
/// <param name="major">The major version.</param>
|
||||||
|
public GameVersion(int year, int month, int day, int major)
|
||||||
|
{
|
||||||
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
|
||||||
|
if ((this.Month = month) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
|
|
||||||
|
if ((this.Day = day) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(day));
|
||||||
|
|
||||||
|
if ((this.Major = major) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(major));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The year.</param>
|
||||||
|
/// <param name="month">The month.</param>
|
||||||
|
/// <param name="day">The day.</param>
|
||||||
|
public GameVersion(int year, int month, int day)
|
||||||
|
{
|
||||||
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
|
||||||
|
if ((this.Month = month) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
|
|
||||||
|
if ((this.Day = day) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(day));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The year.</param>
|
||||||
|
/// <param name="month">The month.</param>
|
||||||
|
public GameVersion(int year, int month)
|
||||||
|
{
|
||||||
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
|
||||||
|
if ((this.Month = month) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(month));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The year.</param>
|
||||||
|
public GameVersion(int year)
|
||||||
|
{
|
||||||
|
if ((this.Year = year) < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(year));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public GameVersion()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default "any" game version.
|
||||||
|
/// </summary>
|
||||||
|
public static GameVersion Any => AnyVersion;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the year component.
|
||||||
|
/// </summary>
|
||||||
|
public int Year { get; } = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the month component.
|
||||||
|
/// </summary>
|
||||||
|
public int Month { get; } = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the day component.
|
||||||
|
/// </summary>
|
||||||
|
public int Day { get; } = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the major version component.
|
||||||
|
/// </summary>
|
||||||
|
public int Major { get; } = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the minor version component.
|
||||||
|
/// </summary>
|
||||||
|
public int Minor { get; } = -1;
|
||||||
|
|
||||||
|
public static implicit operator GameVersion(string ver)
|
||||||
|
{
|
||||||
|
return Parse(ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(GameVersion v1, GameVersion v2)
|
||||||
|
{
|
||||||
|
if (v1 is null)
|
||||||
{
|
{
|
||||||
var ver = Parse(version);
|
return v2 is null;
|
||||||
this.Year = ver.Year;
|
|
||||||
this.Month = ver.Month;
|
|
||||||
this.Day = ver.Day;
|
|
||||||
this.Major = ver.Major;
|
|
||||||
this.Minor = ver.Minor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return v1.Equals(v2);
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year.</param>
|
public static bool operator !=(GameVersion v1, GameVersion v2)
|
||||||
/// <param name="month">The month.</param>
|
{
|
||||||
/// <param name="day">The day.</param>
|
return !(v1 == v2);
|
||||||
/// <param name="major">The major version.</param>
|
}
|
||||||
/// <param name="minor">The minor version.</param>
|
|
||||||
public GameVersion(int year, int month, int day, int major, int minor)
|
public static bool operator <(GameVersion v1, GameVersion v2)
|
||||||
|
{
|
||||||
|
if (v1 is null)
|
||||||
|
throw new ArgumentNullException(nameof(v1));
|
||||||
|
|
||||||
|
return v1.CompareTo(v2) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <=(GameVersion v1, GameVersion v2)
|
||||||
|
{
|
||||||
|
if (v1 is null)
|
||||||
|
throw new ArgumentNullException(nameof(v1));
|
||||||
|
|
||||||
|
return v1.CompareTo(v2) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >(GameVersion v1, GameVersion v2)
|
||||||
|
{
|
||||||
|
return v2 < v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >=(GameVersion v1, GameVersion v2)
|
||||||
|
{
|
||||||
|
return v2 <= v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameVersion operator +(GameVersion v1, TimeSpan v2)
|
||||||
|
{
|
||||||
|
if (v1 == null)
|
||||||
|
throw new ArgumentNullException(nameof(v1));
|
||||||
|
|
||||||
|
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||||
|
return v1;
|
||||||
|
|
||||||
|
var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2;
|
||||||
|
|
||||||
|
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameVersion operator -(GameVersion v1, TimeSpan v2)
|
||||||
|
{
|
||||||
|
if (v1 == null)
|
||||||
|
throw new ArgumentNullException(nameof(v1));
|
||||||
|
|
||||||
|
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
||||||
|
return v1;
|
||||||
|
|
||||||
|
var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2;
|
||||||
|
|
||||||
|
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Input to parse.</param>
|
||||||
|
/// <returns>GameVersion object.</returns>
|
||||||
|
public static GameVersion Parse(string input)
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
throw new ArgumentNullException(nameof(input));
|
||||||
|
|
||||||
|
if (input.ToLower(CultureInfo.InvariantCulture) == "any")
|
||||||
|
return new GameVersion();
|
||||||
|
|
||||||
|
var parts = input.Split('.');
|
||||||
|
var tplParts = parts.Select(p =>
|
||||||
{
|
{
|
||||||
if ((this.Year = year) < 0)
|
var result = int.TryParse(p, out var value);
|
||||||
throw new ArgumentOutOfRangeException(nameof(year));
|
return (result, value);
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
if ((this.Month = month) < 0)
|
if (tplParts.Any(t => !t.result))
|
||||||
throw new ArgumentOutOfRangeException(nameof(month));
|
throw new FormatException("Bad formatting");
|
||||||
|
|
||||||
if ((this.Day = day) < 0)
|
var intParts = tplParts.Select(t => t.value).ToArray();
|
||||||
throw new ArgumentOutOfRangeException(nameof(day));
|
var len = intParts.Length;
|
||||||
|
|
||||||
if ((this.Major = major) < 0)
|
if (len == 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(major));
|
return new GameVersion(intParts[0]);
|
||||||
|
else if (len == 2)
|
||||||
|
return new GameVersion(intParts[0], intParts[1]);
|
||||||
|
else if (len == 3)
|
||||||
|
return new GameVersion(intParts[0], intParts[1], intParts[2]);
|
||||||
|
else if (len == 4)
|
||||||
|
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]);
|
||||||
|
else if (len == 5)
|
||||||
|
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]);
|
||||||
|
else
|
||||||
|
throw new ArgumentException("Too many parts");
|
||||||
|
}
|
||||||
|
|
||||||
if ((this.Minor = minor) < 0)
|
/// <summary>
|
||||||
throw new ArgumentOutOfRangeException(nameof(minor));
|
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Input to parse.</param>
|
||||||
|
/// <param name="result">GameVersion object.</param>
|
||||||
|
/// <returns>Success or failure.</returns>
|
||||||
|
public static bool TryParse(string input, out GameVersion result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Parse(input);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year.</param>
|
|
||||||
/// <param name="month">The month.</param>
|
|
||||||
/// <param name="day">The day.</param>
|
|
||||||
/// <param name="major">The major version.</param>
|
|
||||||
public GameVersion(int year, int month, int day, int major)
|
|
||||||
{
|
{
|
||||||
if ((this.Year = year) < 0)
|
result = null;
|
||||||
throw new ArgumentOutOfRangeException(nameof(year));
|
return false;
|
||||||
|
|
||||||
if ((this.Month = month) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(month));
|
|
||||||
|
|
||||||
if ((this.Day = day) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(day));
|
|
||||||
|
|
||||||
if ((this.Major = major) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(major));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year.</param>
|
|
||||||
/// <param name="month">The month.</param>
|
|
||||||
/// <param name="day">The day.</param>
|
|
||||||
public GameVersion(int year, int month, int day)
|
|
||||||
{
|
|
||||||
if ((this.Year = year) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(year));
|
|
||||||
|
|
||||||
if ((this.Month = month) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(month));
|
|
||||||
|
|
||||||
if ((this.Day = day) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(day));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year.</param>
|
|
||||||
/// <param name="month">The month.</param>
|
|
||||||
public GameVersion(int year, int month)
|
|
||||||
{
|
|
||||||
if ((this.Year = year) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(year));
|
|
||||||
|
|
||||||
if ((this.Month = month) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(month));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year.</param>
|
|
||||||
public GameVersion(int year)
|
|
||||||
{
|
|
||||||
if ((this.Year = year) < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(year));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameVersion"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public GameVersion()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the default "any" game version.
|
|
||||||
/// </summary>
|
|
||||||
public static GameVersion Any => AnyVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the year component.
|
|
||||||
/// </summary>
|
|
||||||
public int Year { get; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the month component.
|
|
||||||
/// </summary>
|
|
||||||
public int Month { get; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the day component.
|
|
||||||
/// </summary>
|
|
||||||
public int Day { get; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the major version component.
|
|
||||||
/// </summary>
|
|
||||||
public int Major { get; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the minor version component.
|
|
||||||
/// </summary>
|
|
||||||
public int Minor { get; } = -1;
|
|
||||||
|
|
||||||
public static implicit operator GameVersion(string ver)
|
|
||||||
{
|
|
||||||
return Parse(ver);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
if (v1 is null)
|
|
||||||
{
|
|
||||||
return v2 is null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return v1.Equals(v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
return !(v1 == v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator <(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
if (v1 is null)
|
|
||||||
throw new ArgumentNullException(nameof(v1));
|
|
||||||
|
|
||||||
return v1.CompareTo(v2) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator <=(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
if (v1 is null)
|
|
||||||
throw new ArgumentNullException(nameof(v1));
|
|
||||||
|
|
||||||
return v1.CompareTo(v2) <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator >(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
return v2 < v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator >=(GameVersion v1, GameVersion v2)
|
|
||||||
{
|
|
||||||
return v2 <= v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GameVersion operator +(GameVersion v1, TimeSpan v2)
|
|
||||||
{
|
|
||||||
if (v1 == null)
|
|
||||||
throw new ArgumentNullException(nameof(v1));
|
|
||||||
|
|
||||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
|
||||||
return v1;
|
|
||||||
|
|
||||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2;
|
|
||||||
|
|
||||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GameVersion operator -(GameVersion v1, TimeSpan v2)
|
|
||||||
{
|
|
||||||
if (v1 == null)
|
|
||||||
throw new ArgumentNullException(nameof(v1));
|
|
||||||
|
|
||||||
if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1)
|
|
||||||
return v1;
|
|
||||||
|
|
||||||
var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2;
|
|
||||||
|
|
||||||
return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse a version string. YYYY.MM.DD.majr.minr or "any".
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">Input to parse.</param>
|
|
||||||
/// <returns>GameVersion object.</returns>
|
|
||||||
public static GameVersion Parse(string input)
|
|
||||||
{
|
|
||||||
if (input == null)
|
|
||||||
throw new ArgumentNullException(nameof(input));
|
|
||||||
|
|
||||||
if (input.ToLower(CultureInfo.InvariantCulture) == "any")
|
|
||||||
return new GameVersion();
|
|
||||||
|
|
||||||
var parts = input.Split('.');
|
|
||||||
var tplParts = parts.Select(p =>
|
|
||||||
{
|
|
||||||
var result = int.TryParse(p, out var value);
|
|
||||||
return (result, value);
|
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
if (tplParts.Any(t => !t.result))
|
|
||||||
throw new FormatException("Bad formatting");
|
|
||||||
|
|
||||||
var intParts = tplParts.Select(t => t.value).ToArray();
|
|
||||||
var len = intParts.Length;
|
|
||||||
|
|
||||||
if (len == 1)
|
|
||||||
return new GameVersion(intParts[0]);
|
|
||||||
else if (len == 2)
|
|
||||||
return new GameVersion(intParts[0], intParts[1]);
|
|
||||||
else if (len == 3)
|
|
||||||
return new GameVersion(intParts[0], intParts[1], intParts[2]);
|
|
||||||
else if (len == 4)
|
|
||||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]);
|
|
||||||
else if (len == 5)
|
|
||||||
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]);
|
|
||||||
else
|
|
||||||
throw new ArgumentException("Too many parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">Input to parse.</param>
|
|
||||||
/// <param name="result">GameVersion object.</param>
|
|
||||||
/// <returns>Success or failure.</returns>
|
|
||||||
public static bool TryParse(string input, out GameVersion result)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = Parse(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int CompareTo(object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (obj is GameVersion value)
|
|
||||||
{
|
|
||||||
return this.CompareTo(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Argument must be a GameVersion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int CompareTo(GameVersion value)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (this == value)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (this == AnyVersion)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (value == AnyVersion)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (this.Year != value.Year)
|
|
||||||
return this.Year > value.Year ? 1 : -1;
|
|
||||||
|
|
||||||
if (this.Month != value.Month)
|
|
||||||
return this.Month > value.Month ? 1 : -1;
|
|
||||||
|
|
||||||
if (this.Day != value.Day)
|
|
||||||
return this.Day > value.Day ? 1 : -1;
|
|
||||||
|
|
||||||
if (this.Major != value.Major)
|
|
||||||
return this.Major > value.Major ? 1 : -1;
|
|
||||||
|
|
||||||
if (this.Minor != value.Minor)
|
|
||||||
return this.Minor > value.Minor ? 1 : -1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (obj is not GameVersion value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return this.Equals(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool Equals(GameVersion value)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
(this.Year == value.Year) &&
|
|
||||||
(this.Month == value.Month) &&
|
|
||||||
(this.Day == value.Day) &&
|
|
||||||
(this.Major == value.Major) &&
|
|
||||||
(this.Minor == value.Minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var accumulator = 0;
|
|
||||||
|
|
||||||
// This might be horribly wrong, but it isn't used heavily.
|
|
||||||
accumulator |= this.Year.GetHashCode();
|
|
||||||
accumulator |= this.Month.GetHashCode();
|
|
||||||
accumulator |= this.Day.GetHashCode();
|
|
||||||
accumulator |= this.Major.GetHashCode();
|
|
||||||
accumulator |= this.Minor.GetHashCode();
|
|
||||||
|
|
||||||
return accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (this.Year == -1 &&
|
|
||||||
this.Month == -1 &&
|
|
||||||
this.Day == -1 &&
|
|
||||||
this.Major == -1 &&
|
|
||||||
this.Minor == -1)
|
|
||||||
return "any";
|
|
||||||
|
|
||||||
return new StringBuilder()
|
|
||||||
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
|
|
||||||
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
|
|
||||||
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
|
|
||||||
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
|
|
||||||
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
|
|
||||||
.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CompareTo(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (obj is GameVersion value)
|
||||||
|
{
|
||||||
|
return this.CompareTo(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Argument must be a GameVersion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CompareTo(GameVersion value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (this == value)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (this == AnyVersion)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (value == AnyVersion)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (this.Year != value.Year)
|
||||||
|
return this.Year > value.Year ? 1 : -1;
|
||||||
|
|
||||||
|
if (this.Month != value.Month)
|
||||||
|
return this.Month > value.Month ? 1 : -1;
|
||||||
|
|
||||||
|
if (this.Day != value.Day)
|
||||||
|
return this.Day > value.Day ? 1 : -1;
|
||||||
|
|
||||||
|
if (this.Major != value.Major)
|
||||||
|
return this.Major > value.Major ? 1 : -1;
|
||||||
|
|
||||||
|
if (this.Minor != value.Minor)
|
||||||
|
return this.Minor > value.Minor ? 1 : -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is not GameVersion value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.Equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(GameVersion value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
(this.Year == value.Year) &&
|
||||||
|
(this.Month == value.Month) &&
|
||||||
|
(this.Day == value.Day) &&
|
||||||
|
(this.Major == value.Major) &&
|
||||||
|
(this.Minor == value.Minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var accumulator = 0;
|
||||||
|
|
||||||
|
// This might be horribly wrong, but it isn't used heavily.
|
||||||
|
accumulator |= this.Year.GetHashCode();
|
||||||
|
accumulator |= this.Month.GetHashCode();
|
||||||
|
accumulator |= this.Day.GetHashCode();
|
||||||
|
accumulator |= this.Major.GetHashCode();
|
||||||
|
accumulator |= this.Minor.GetHashCode();
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (this.Year == -1 &&
|
||||||
|
this.Month == -1 &&
|
||||||
|
this.Day == -1 &&
|
||||||
|
this.Major == -1 &&
|
||||||
|
this.Minor == -1)
|
||||||
|
return "any";
|
||||||
|
|
||||||
|
return new StringBuilder()
|
||||||
|
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
|
||||||
|
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
|
||||||
|
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
|
||||||
|
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
|
||||||
|
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
|
||||||
|
.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,79 +2,78 @@ using System;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GameVersionConverter : JsonConverter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
/// Writes the JSON representation of the object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GameVersionConverter : JsonConverter
|
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
||||||
|
/// <param name="value">The value.</param>
|
||||||
|
/// <param name="serializer">The calling serializer.</param>
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (value == null)
|
||||||
/// Writes the JSON representation of the object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
|
||||||
/// <param name="value">The value.</param>
|
|
||||||
/// <param name="serializer">The calling serializer.</param>
|
|
||||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
|
||||||
{
|
{
|
||||||
if (value == null)
|
writer.WriteNull();
|
||||||
{
|
|
||||||
writer.WriteNull();
|
|
||||||
}
|
|
||||||
else if (value is GameVersion)
|
|
||||||
{
|
|
||||||
writer.WriteValue(value.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new JsonSerializationException("Expected GameVersion object value");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (value is GameVersion)
|
||||||
/// <summary>
|
|
||||||
/// Reads the JSON representation of the object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
|
||||||
/// <param name="objectType">Type of the object.</param>
|
|
||||||
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
|
||||||
/// <param name="serializer">The calling serializer.</param>
|
|
||||||
/// <returns>The object value.</returns>
|
|
||||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonToken.Null)
|
writer.WriteValue(value.ToString());
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (reader.TokenType == JsonToken.String)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new GameVersion((string)reader.Value!);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this instance can convert the specified object type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="objectType">Type of the object.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
|
||||||
/// </returns>
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
{
|
{
|
||||||
return objectType == typeof(GameVersion);
|
throw new JsonSerializationException("Expected GameVersion object value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the JSON representation of the object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
||||||
|
/// <param name="objectType">Type of the object.</param>
|
||||||
|
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
||||||
|
/// <param name="serializer">The calling serializer.</param>
|
||||||
|
/// <returns>The object value.</returns>
|
||||||
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonToken.Null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonToken.String)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new GameVersion((string)reader.Value!);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this instance can convert the specified object type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectType">Type of the object.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(GameVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,446 +14,445 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class handles interacting with the native chat UI.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed class ChatGui : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly ChatGuiAddressResolver address;
|
||||||
/// This class handles interacting with the native chat UI.
|
|
||||||
/// </summary>
|
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||||
[PluginInterface]
|
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||||
public sealed class ChatGui : IDisposable, IServiceType
|
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||||
|
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
|
||||||
|
|
||||||
|
private IntPtr baseAddress = IntPtr.Zero;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private ChatGui(SigScanner sigScanner)
|
||||||
{
|
{
|
||||||
private readonly ChatGuiAddressResolver address;
|
this.address = new ChatGuiAddressResolver();
|
||||||
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||||
|
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
/// <summary>
|
||||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||||
|
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
/// <summary>
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||||
|
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
/// <summary>
|
||||||
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
|
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||||
|
|
||||||
private IntPtr baseAddress = IntPtr.Zero;
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of chat.</param>
|
||||||
|
/// <param name="senderId">The sender ID.</param>
|
||||||
|
/// <param name="sender">The sender name.</param>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private ChatGui(SigScanner sigScanner)
|
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||||
|
/// </summary>
|
||||||
|
public event OnMessageDelegate ChatMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||||
|
/// </summary>
|
||||||
|
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||||
|
/// </summary>
|
||||||
|
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||||
|
/// </summary>
|
||||||
|
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the last linked item.
|
||||||
|
/// </summary>
|
||||||
|
public int LastLinkedItemId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the flags of the last linked item.
|
||||||
|
/// </summary>
|
||||||
|
public byte LastLinkedItemFlags { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
this.printMessageHook.Dispose();
|
||||||
|
this.populateItemLinkHook.Dispose();
|
||||||
|
this.interactableLinkClickedHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||||
|
/// later to be processed when UpdateQueue() is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chat">A message to send.</param>
|
||||||
|
public void PrintChat(XivChatEntry chat)
|
||||||
|
{
|
||||||
|
this.chatQueue.Enqueue(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||||
|
/// later to be processed when UpdateQueue() is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message to send.</param>
|
||||||
|
public void Print(string message)
|
||||||
|
{
|
||||||
|
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||||
|
this.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
this.address = new ChatGuiAddressResolver();
|
Message = message,
|
||||||
this.address.Setup(sigScanner);
|
Type = this.configuration.GeneralChatType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
/// <summary>
|
||||||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
/// later to be processed when UpdateQueue() is called.
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="message">A message to send.</param>
|
||||||
/// <summary>
|
public void Print(SeString message)
|
||||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
{
|
||||||
/// </summary>
|
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||||
/// <param name="type">The type of chat.</param>
|
this.PrintChat(new XivChatEntry
|
||||||
/// <param name="senderId">The sender ID.</param>
|
|
||||||
/// <param name="sender">The sender name.</param>
|
|
||||||
/// <param name="message">The message sent.</param>
|
|
||||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
|
||||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of chat.</param>
|
|
||||||
/// <param name="senderId">The sender ID.</param>
|
|
||||||
/// <param name="sender">The sender name.</param>
|
|
||||||
/// <param name="message">The message sent.</param>
|
|
||||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
|
||||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of chat.</param>
|
|
||||||
/// <param name="senderId">The sender ID.</param>
|
|
||||||
/// <param name="sender">The sender name.</param>
|
|
||||||
/// <param name="message">The message sent.</param>
|
|
||||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of chat.</param>
|
|
||||||
/// <param name="senderId">The sender ID.</param>
|
|
||||||
/// <param name="sender">The sender name.</param>
|
|
||||||
/// <param name="message">The message sent.</param>
|
|
||||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
|
||||||
/// </summary>
|
|
||||||
public event OnMessageDelegate ChatMessage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
|
||||||
/// </summary>
|
|
||||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
|
||||||
/// </summary>
|
|
||||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
|
||||||
/// </summary>
|
|
||||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ID of the last linked item.
|
|
||||||
/// </summary>
|
|
||||||
public int LastLinkedItemId { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the flags of the last linked item.
|
|
||||||
/// </summary>
|
|
||||||
public byte LastLinkedItemFlags { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose of managed and unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
{
|
||||||
this.printMessageHook.Dispose();
|
Message = message,
|
||||||
this.populateItemLinkHook.Dispose();
|
Type = this.configuration.GeneralChatType,
|
||||||
this.interactableLinkClickedHook.Dispose();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||||
/// later to be processed when UpdateQueue() is called.
|
/// the queue, later to be processed when UpdateQueue() is called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="chat">A message to send.</param>
|
/// <param name="message">A message to send.</param>
|
||||||
public void PrintChat(XivChatEntry chat)
|
public void PrintError(string message)
|
||||||
|
{
|
||||||
|
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||||
|
this.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
this.chatQueue.Enqueue(chat);
|
Message = message,
|
||||||
}
|
Type = XivChatType.Urgent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||||
/// later to be processed when UpdateQueue() is called.
|
/// the queue, later to be processed when UpdateQueue() is called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">A message to send.</param>
|
/// <param name="message">A message to send.</param>
|
||||||
public void Print(string message)
|
public void PrintError(SeString message)
|
||||||
|
{
|
||||||
|
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||||
|
this.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
Message = message,
|
||||||
this.PrintChat(new XivChatEntry
|
Type = XivChatType.Urgent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process a chat queue.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateQueue()
|
||||||
|
{
|
||||||
|
while (this.chatQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var chat = this.chatQueue.Dequeue();
|
||||||
|
|
||||||
|
if (this.baseAddress == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Message = message,
|
continue;
|
||||||
Type = this.configuration.GeneralChatType,
|
}
|
||||||
});
|
|
||||||
|
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||||
|
using var senderOwned = this.libcFunction.NewString(senderRaw);
|
||||||
|
|
||||||
|
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||||
|
using var messageOwned = this.libcFunction.NewString(messageRaw);
|
||||||
|
|
||||||
|
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
/// Create a link handler.
|
||||||
/// later to be processed when UpdateQueue() is called.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||||
/// <param name="message">A message to send.</param>
|
/// <param name="commandId">The ID of the command to run.</param>
|
||||||
public void Print(SeString message)
|
/// <param name="commandAction">The command action itself.</param>
|
||||||
|
/// <returns>A payload for handling.</returns>
|
||||||
|
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||||
|
{
|
||||||
|
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||||
|
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove all handlers owned by a plugin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
||||||
|
internal void RemoveChatLinkHandler(string pluginName)
|
||||||
|
{
|
||||||
|
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
||||||
{
|
{
|
||||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
this.dalamudLinkHandlers.Remove(handler);
|
||||||
this.PrintChat(new XivChatEntry
|
|
||||||
{
|
|
||||||
Message = message,
|
|
||||||
Type = this.configuration.GeneralChatType,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
/// Remove a registered link handler.
|
||||||
/// the queue, later to be processed when UpdateQueue() is called.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||||
/// <param name="message">A message to send.</param>
|
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||||
public void PrintError(string message)
|
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||||
|
{
|
||||||
|
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||||
{
|
{
|
||||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||||
this.PrintChat(new XivChatEntry
|
|
||||||
{
|
|
||||||
Message = message,
|
|
||||||
Type = XivChatType.Urgent,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
[ServiceManager.CallWhenServicesReady]
|
||||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
|
||||||
/// the queue, later to be processed when UpdateQueue() is called.
|
{
|
||||||
/// </summary>
|
this.printMessageHook.Enable();
|
||||||
/// <param name="message">A message to send.</param>
|
this.populateItemLinkHook.Enable();
|
||||||
public void PrintError(SeString message)
|
this.interactableLinkClickedHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||||
this.PrintChat(new XivChatEntry
|
|
||||||
{
|
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||||
Message = message,
|
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||||
Type = XivChatType.Urgent,
|
|
||||||
});
|
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// Process a chat queue.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateQueue()
|
|
||||||
{
|
{
|
||||||
while (this.chatQueue.Count > 0)
|
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||||
{
|
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||||
var chat = this.chatQueue.Dequeue();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.baseAddress == IntPtr.Zero)
|
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||||
|
{
|
||||||
|
var retVal = IntPtr.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sender = StdString.ReadFromPointer(pSenderName);
|
||||||
|
var parsedSender = SeString.Parse(sender.RawData);
|
||||||
|
var originalSenderData = (byte[])sender.RawData.Clone();
|
||||||
|
var oldEditedSender = parsedSender.Encode();
|
||||||
|
var senderPtr = pSenderName;
|
||||||
|
OwnedStdString allocatedString = null;
|
||||||
|
|
||||||
|
var message = StdString.ReadFromPointer(pMessage);
|
||||||
|
var parsedMessage = SeString.Parse(message.RawData);
|
||||||
|
var originalMessageData = (byte[])message.RawData.Clone();
|
||||||
|
var oldEdited = parsedMessage.Encode();
|
||||||
|
var messagePtr = pMessage;
|
||||||
|
OwnedStdString allocatedStringSender = null;
|
||||||
|
|
||||||
|
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||||
|
|
||||||
|
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||||
|
|
||||||
|
// Call events
|
||||||
|
var isHandled = false;
|
||||||
|
|
||||||
|
var invocationList = this.CheckMessageHandled.GetInvocationList();
|
||||||
|
foreach (var @delegate in invocationList)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
continue;
|
var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate;
|
||||||
|
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
|
||||||
using var senderOwned = this.libcFunction.NewString(senderRaw);
|
|
||||||
|
|
||||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
|
||||||
using var messageOwned = this.libcFunction.NewString(messageRaw);
|
|
||||||
|
|
||||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
if (!isHandled)
|
||||||
/// Create a link handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
|
||||||
/// <param name="commandId">The ID of the command to run.</param>
|
|
||||||
/// <param name="commandAction">The command action itself.</param>
|
|
||||||
/// <returns>A payload for handling.</returns>
|
|
||||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
|
||||||
{
|
|
||||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
|
||||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove all handlers owned by a plugin.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
|
||||||
internal void RemoveChatLinkHandler(string pluginName)
|
|
||||||
{
|
|
||||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
|
||||||
{
|
{
|
||||||
this.dalamudLinkHandlers.Remove(handler);
|
invocationList = this.ChatMessage.GetInvocationList();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a registered link handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
|
||||||
/// <param name="commandId">The ID of the command to be removed.</param>
|
|
||||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
|
||||||
{
|
|
||||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
|
||||||
{
|
|
||||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
|
|
||||||
{
|
|
||||||
this.printMessageHook.Enable();
|
|
||||||
this.populateItemLinkHook.Enable();
|
|
||||||
this.interactableLinkClickedHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
|
||||||
|
|
||||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
|
||||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
|
||||||
|
|
||||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
|
||||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
|
||||||
{
|
|
||||||
var retVal = IntPtr.Zero;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var sender = StdString.ReadFromPointer(pSenderName);
|
|
||||||
var parsedSender = SeString.Parse(sender.RawData);
|
|
||||||
var originalSenderData = (byte[])sender.RawData.Clone();
|
|
||||||
var oldEditedSender = parsedSender.Encode();
|
|
||||||
var senderPtr = pSenderName;
|
|
||||||
OwnedStdString allocatedString = null;
|
|
||||||
|
|
||||||
var message = StdString.ReadFromPointer(pMessage);
|
|
||||||
var parsedMessage = SeString.Parse(message.RawData);
|
|
||||||
var originalMessageData = (byte[])message.RawData.Clone();
|
|
||||||
var oldEdited = parsedMessage.Encode();
|
|
||||||
var messagePtr = pMessage;
|
|
||||||
OwnedStdString allocatedStringSender = null;
|
|
||||||
|
|
||||||
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
|
||||||
|
|
||||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
|
||||||
|
|
||||||
// Call events
|
|
||||||
var isHandled = false;
|
|
||||||
|
|
||||||
var invocationList = this.CheckMessageHandled.GetInvocationList();
|
|
||||||
foreach (var @delegate in invocationList)
|
foreach (var @delegate in invocationList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate;
|
var messageHandledDelegate = @delegate as OnMessageDelegate;
|
||||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isHandled)
|
var newEdited = parsedMessage.Encode();
|
||||||
{
|
if (!Util.FastByteArrayCompare(oldEdited, newEdited))
|
||||||
invocationList = this.ChatMessage.GetInvocationList();
|
{
|
||||||
foreach (var @delegate in invocationList)
|
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||||
{
|
message.RawData = newEdited;
|
||||||
try
|
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||||
{
|
}
|
||||||
var messageHandledDelegate = @delegate as OnMessageDelegate;
|
|
||||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newEdited = parsedMessage.Encode();
|
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
||||||
if (!Util.FastByteArrayCompare(oldEdited, newEdited))
|
{
|
||||||
{
|
allocatedString = this.libcFunction.NewString(message.RawData);
|
||||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||||
message.RawData = newEdited;
|
messagePtr = allocatedString.Address;
|
||||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
var newEditedSender = parsedSender.Encode();
|
||||||
{
|
if (!Util.FastByteArrayCompare(oldEditedSender, newEditedSender))
|
||||||
allocatedString = this.libcFunction.NewString(message.RawData);
|
{
|
||||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||||
messagePtr = allocatedString.Address;
|
sender.RawData = newEditedSender;
|
||||||
}
|
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||||
|
}
|
||||||
|
|
||||||
var newEditedSender = parsedSender.Encode();
|
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||||
if (!Util.FastByteArrayCompare(oldEditedSender, newEditedSender))
|
{
|
||||||
{
|
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
|
||||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
Log.Debug(
|
||||||
sender.RawData = newEditedSender;
|
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
senderPtr = allocatedStringSender.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
// Print the original chat if it's handled.
|
||||||
{
|
if (isHandled)
|
||||||
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
|
{
|
||||||
Log.Debug(
|
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
}
|
||||||
senderPtr = allocatedStringSender.Address;
|
else
|
||||||
}
|
{
|
||||||
|
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||||
|
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
// Print the original chat if it's handled.
|
if (this.baseAddress == IntPtr.Zero)
|
||||||
if (isHandled)
|
this.baseAddress = manager;
|
||||||
|
|
||||||
|
allocatedString?.Dispose();
|
||||||
|
allocatedStringSender?.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||||
|
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||||
|
|
||||||
|
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||||
|
{
|
||||||
|
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||||
|
|
||||||
|
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||||
|
var messageSize = 0;
|
||||||
|
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||||
|
var payloadBytes = new byte[messageSize];
|
||||||
|
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||||
|
var seStr = SeString.Parse(payloadBytes);
|
||||||
|
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||||
|
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||||
|
if (payloads.Count == 0) return;
|
||||||
|
var linkPayload = payloads[0];
|
||||||
|
if (linkPayload is DalamudLinkPayload link)
|
||||||
|
{
|
||||||
|
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||||
{
|
{
|
||||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||||
|
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.baseAddress == IntPtr.Zero)
|
|
||||||
this.baseAddress = manager;
|
|
||||||
|
|
||||||
allocatedString?.Dispose();
|
|
||||||
allocatedStringSender?.Dispose();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
|
||||||
{
|
{
|
||||||
try
|
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||||
{
|
|
||||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
|
||||||
|
|
||||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
|
||||||
{
|
|
||||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
|
||||||
|
|
||||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
|
||||||
var messageSize = 0;
|
|
||||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
|
||||||
var payloadBytes = new byte[messageSize];
|
|
||||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
|
||||||
var seStr = SeString.Parse(payloadBytes);
|
|
||||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
|
||||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
|
||||||
if (payloads.Count == 0) return;
|
|
||||||
var linkPayload = payloads[0];
|
|
||||||
if (linkPayload is DalamudLinkPayload link)
|
|
||||||
{
|
|
||||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
|
||||||
{
|
|
||||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
|
||||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,104 +1,103 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
/// Gets the address of the native PrintMessage method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
public IntPtr PrintMessage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native PopulateItemLinkObject method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native InteractableLinkClicked method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr InteractableLinkClicked { get; private set; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
--- for reference: 4.57 ---
|
||||||
|
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||||
|
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||||
|
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||||
|
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||||
|
.text:00000001405CD210
|
||||||
|
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||||
|
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||||
|
.text:00000001405CD210 var_210 = word ptr -210h
|
||||||
|
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||||
|
.text:00000001405CD210 var_200 = word ptr -200h
|
||||||
|
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||||
|
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||||
|
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||||
|
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||||
|
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||||
|
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||||
|
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||||
|
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||||
|
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||||
|
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||||
|
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||||
|
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||||
|
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||||
|
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||||
|
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||||
|
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||||
|
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||||
|
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||||
|
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||||
|
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||||
|
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||||
|
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||||
|
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||||
|
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||||
|
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||||
|
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||||
|
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||||
|
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||||
|
.text:00000001405CD210
|
||||||
|
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||||
|
.text:00000001405CD210 push rbp
|
||||||
|
.text:00000001405CD212 push rdi
|
||||||
|
.text:00000001405CD213 push r14
|
||||||
|
.text:00000001405CD215 push r15
|
||||||
|
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||||
|
.text:00000001405CD21F sub rsp, 228h
|
||||||
|
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||||
|
.text:00000001405CD22D xor rax, rsp
|
||||||
|
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||||
|
.text:00000001405CD237 xor r10b, r10b
|
||||||
|
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||||
|
.text:00000001405CD23F xor eax, eax
|
||||||
|
.text:00000001405CD241 mov r11, r9
|
||||||
|
.text:00000001405CD244 mov r14, r8
|
||||||
|
.text:00000001405CD247 mov r9d, eax
|
||||||
|
.text:00000001405CD24A movzx r15d, dx
|
||||||
|
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||||
|
.text:00000001405CD255 mov rdi, rcx
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
/// <summary>
|
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||||
/// Gets the address of the native PrintMessage method.
|
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
||||||
/// </summary>
|
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||||
public IntPtr PrintMessage { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||||
/// Gets the address of the native PopulateItemLinkObject method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||||
/// Gets the address of the native InteractableLinkClicked method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr InteractableLinkClicked { get; private set; }
|
|
||||||
|
|
||||||
/*
|
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||||
--- for reference: 4.57 ---
|
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
|
||||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
|
||||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
|
||||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
|
||||||
.text:00000001405CD210
|
|
||||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
|
||||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
|
||||||
.text:00000001405CD210 var_210 = word ptr -210h
|
|
||||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
|
||||||
.text:00000001405CD210 var_200 = word ptr -200h
|
|
||||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
|
||||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
|
||||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
|
||||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
|
||||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
|
||||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
|
||||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
|
||||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
|
||||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
|
||||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
|
||||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
|
||||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
|
||||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
|
||||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
|
||||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
|
||||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
|
||||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
|
||||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
|
||||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
|
||||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
|
||||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
|
||||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
|
||||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
|
||||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
|
||||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
|
||||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
|
||||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
|
||||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
|
||||||
.text:00000001405CD210
|
|
||||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
|
||||||
.text:00000001405CD210 push rbp
|
|
||||||
.text:00000001405CD212 push rdi
|
|
||||||
.text:00000001405CD213 push r14
|
|
||||||
.text:00000001405CD215 push r15
|
|
||||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
|
||||||
.text:00000001405CD21F sub rsp, 228h
|
|
||||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
|
||||||
.text:00000001405CD22D xor rax, rsp
|
|
||||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
|
||||||
.text:00000001405CD237 xor r10b, r10b
|
|
||||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
|
||||||
.text:00000001405CD23F xor eax, eax
|
|
||||||
.text:00000001405CD241 mov r11, r9
|
|
||||||
.text:00000001405CD244 mov r14, r8
|
|
||||||
.text:00000001405CD247 mov r9d, eax
|
|
||||||
.text:00000001405CD24A movzx r15d, dx
|
|
||||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
|
||||||
.text:00000001405CD255 mov rdi, rcx
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
|
||||||
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
|
||||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
|
||||||
|
|
||||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
|
||||||
|
|
||||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
|
||||||
|
|
||||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
|
||||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
|
||||||
|
|
||||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,314 +10,313 @@ using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.Dtr
|
namespace Dalamud.Game.Gui.Dtr;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class used to interface with the server info bar.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed unsafe class DtrBar : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const uint BaseNodeId = 1000;
|
||||||
/// Class used to interface with the server info bar.
|
|
||||||
/// </summary>
|
[ServiceManager.ServiceDependency]
|
||||||
[PluginInterface]
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.ServiceDependency]
|
||||||
public sealed unsafe class DtrBar : IDisposable, IServiceType
|
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
private List<DtrBarEntry> entries = new();
|
||||||
|
private uint runningNodeIds = BaseNodeId;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private DtrBar()
|
||||||
{
|
{
|
||||||
private const uint BaseNodeId = 1000;
|
this.framework.Update += this.Update;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
this.configuration.DtrOrder ??= new List<string>();
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
this.configuration.DtrIgnore ??= new List<string>();
|
||||||
|
this.configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
/// <summary>
|
||||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
/// Get a DTR bar entry.
|
||||||
|
/// This allows you to add your own text, and users to sort it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">A user-friendly name for sorting.</param>
|
||||||
|
/// <param name="text">The text the entry shows.</param>
|
||||||
|
/// <returns>The entry object used to update, hide and remove the entry.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
|
||||||
|
public DtrBarEntry Get(string title, SeString? text = null)
|
||||||
|
{
|
||||||
|
if (this.entries.Any(x => x.Title == title))
|
||||||
|
throw new ArgumentException("An entry with the same title already exists.");
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
var node = this.MakeNode(++this.runningNodeIds);
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
var entry = new DtrBarEntry(title, node);
|
||||||
|
entry.Text = text;
|
||||||
|
|
||||||
private List<DtrBarEntry> entries = new();
|
// Add the entry to the end of the order list, if it's not there already.
|
||||||
private uint runningNodeIds = BaseNodeId;
|
if (!this.configuration.DtrOrder!.Contains(title))
|
||||||
|
this.configuration.DtrOrder!.Add(title);
|
||||||
|
this.entries.Add(entry);
|
||||||
|
this.ApplySort();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
return entry;
|
||||||
private DtrBar()
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
foreach (var entry in this.entries)
|
||||||
|
this.RemoveNode(entry.TextNode);
|
||||||
|
|
||||||
|
this.entries.Clear();
|
||||||
|
this.framework.Update -= this.Update;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove nodes marked as "should be removed" from the bar.
|
||||||
|
/// </summary>
|
||||||
|
internal void HandleRemovedNodes()
|
||||||
|
{
|
||||||
|
foreach (var data in this.entries.Where(d => d.ShouldBeRemoved))
|
||||||
{
|
{
|
||||||
this.framework.Update += this.Update;
|
this.RemoveNode(data.TextNode);
|
||||||
|
|
||||||
this.configuration.DtrOrder ??= new List<string>();
|
|
||||||
this.configuration.DtrIgnore ??= new List<string>();
|
|
||||||
this.configuration.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.entries.RemoveAll(d => d.ShouldBeRemoved);
|
||||||
/// Get a DTR bar entry.
|
}
|
||||||
/// This allows you to add your own text, and users to sort it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">A user-friendly name for sorting.</param>
|
|
||||||
/// <param name="text">The text the entry shows.</param>
|
|
||||||
/// <returns>The entry object used to update, hide and remove the entry.</returns>
|
|
||||||
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
|
|
||||||
public DtrBarEntry Get(string title, SeString? text = null)
|
|
||||||
{
|
|
||||||
if (this.entries.Any(x => x.Title == title))
|
|
||||||
throw new ArgumentException("An entry with the same title already exists.");
|
|
||||||
|
|
||||||
var node = this.MakeNode(++this.runningNodeIds);
|
/// <summary>
|
||||||
var entry = new DtrBarEntry(title, node);
|
/// Check whether an entry with the specified title exists.
|
||||||
entry.Text = text;
|
/// </summary>
|
||||||
|
/// <param name="title">The title to check for.</param>
|
||||||
// Add the entry to the end of the order list, if it's not there already.
|
/// <returns>Whether or not an entry with that title is registered.</returns>
|
||||||
if (!this.configuration.DtrOrder!.Contains(title))
|
internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title);
|
||||||
this.configuration.DtrOrder!.Add(title);
|
|
||||||
this.entries.Add(entry);
|
|
||||||
this.ApplySort();
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
foreach (var entry in this.entries)
|
|
||||||
this.RemoveNode(entry.TextNode);
|
|
||||||
|
|
||||||
this.entries.Clear();
|
|
||||||
this.framework.Update -= this.Update;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove nodes marked as "should be removed" from the bar.
|
|
||||||
/// </summary>
|
|
||||||
internal void HandleRemovedNodes()
|
|
||||||
{
|
|
||||||
foreach (var data in this.entries.Where(d => d.ShouldBeRemoved))
|
|
||||||
{
|
|
||||||
this.RemoveNode(data.TextNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entries.RemoveAll(d => d.ShouldBeRemoved);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check whether an entry with the specified title exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">The title to check for.</param>
|
|
||||||
/// <returns>Whether or not an entry with that title is registered.</returns>
|
|
||||||
internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dirty the DTR bar entry with the specified title.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">Title of the entry to dirty.</param>
|
|
||||||
/// <returns>Whether the entry was found.</returns>
|
|
||||||
internal bool MakeDirty(string title)
|
|
||||||
{
|
|
||||||
var entry = this.entries.FirstOrDefault(x => x.Title == title);
|
|
||||||
if (entry == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
entry.Dirty = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reapply the DTR entry ordering from <see cref="DalamudConfiguration"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal void ApplySort()
|
|
||||||
{
|
|
||||||
// Sort the current entry list, based on the order in the configuration.
|
|
||||||
var positions = this.configuration
|
|
||||||
.DtrOrder!
|
|
||||||
.Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry)))
|
|
||||||
.ToDictionary(x => x.entry, x => x.index);
|
|
||||||
|
|
||||||
this.entries.Sort((x, y) =>
|
|
||||||
{
|
|
||||||
var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue;
|
|
||||||
var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue;
|
|
||||||
return xPos.CompareTo(yPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer();
|
|
||||||
|
|
||||||
private void Update(Framework unused)
|
|
||||||
{
|
|
||||||
this.HandleRemovedNodes();
|
|
||||||
|
|
||||||
var dtr = this.GetDtr();
|
|
||||||
if (dtr == null) return;
|
|
||||||
|
|
||||||
// The collision node on the DTR element is always the width of its content
|
|
||||||
if (dtr->UldManager.NodeList == null) return;
|
|
||||||
|
|
||||||
// If we have an unmodified DTR but still have entries, we need to
|
|
||||||
// work to reset our state.
|
|
||||||
if (!this.CheckForDalamudNodes())
|
|
||||||
this.RecreateNodes();
|
|
||||||
|
|
||||||
var collisionNode = dtr->UldManager.NodeList[1];
|
|
||||||
if (collisionNode == null) return;
|
|
||||||
|
|
||||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
|
||||||
// collisionNode->X + collisionNode->Width.
|
|
||||||
var runningXPos = this.configuration.DtrSwapDirection
|
|
||||||
? collisionNode->X + collisionNode->Width
|
|
||||||
: collisionNode->X;
|
|
||||||
|
|
||||||
for (var i = 0; i < this.entries.Count; i++)
|
|
||||||
{
|
|
||||||
var data = this.entries[i];
|
|
||||||
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
|
||||||
|
|
||||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
|
||||||
{
|
|
||||||
var node = data.TextNode;
|
|
||||||
node->SetText(data.Text?.Encode());
|
|
||||||
ushort w = 0, h = 0;
|
|
||||||
|
|
||||||
if (isHide)
|
|
||||||
{
|
|
||||||
node->AtkResNode.ToggleVisibility(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->AtkResNode.ToggleVisibility(true);
|
|
||||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
|
||||||
node->AtkResNode.SetWidth(w);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.Added)
|
|
||||||
{
|
|
||||||
data.Added = this.AddNode(data.TextNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isHide)
|
|
||||||
{
|
|
||||||
var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing;
|
|
||||||
|
|
||||||
if (this.configuration.DtrSwapDirection)
|
|
||||||
{
|
|
||||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
|
||||||
runningXPos += elementWidth;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
runningXPos -= elementWidth;
|
|
||||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entries[i] = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if there are any Dalamud nodes in the DTR.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
|
||||||
private bool CheckForDalamudNodes()
|
|
||||||
{
|
|
||||||
var dtr = this.GetDtr();
|
|
||||||
if (dtr == null || dtr->RootNode == null) return false;
|
|
||||||
|
|
||||||
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
|
||||||
{
|
|
||||||
if (dtr->UldManager.NodeList[i]->NodeID > 1000)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dirty the DTR bar entry with the specified title.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">Title of the entry to dirty.</param>
|
||||||
|
/// <returns>Whether the entry was found.</returns>
|
||||||
|
internal bool MakeDirty(string title)
|
||||||
|
{
|
||||||
|
var entry = this.entries.FirstOrDefault(x => x.Title == title);
|
||||||
|
if (entry == null)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
private void RecreateNodes()
|
entry.Dirty = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reapply the DTR entry ordering from <see cref="DalamudConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal void ApplySort()
|
||||||
|
{
|
||||||
|
// Sort the current entry list, based on the order in the configuration.
|
||||||
|
var positions = this.configuration
|
||||||
|
.DtrOrder!
|
||||||
|
.Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry)))
|
||||||
|
.ToDictionary(x => x.entry, x => x.index);
|
||||||
|
|
||||||
|
this.entries.Sort((x, y) =>
|
||||||
{
|
{
|
||||||
this.runningNodeIds = BaseNodeId;
|
var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue;
|
||||||
foreach (var entry in this.entries)
|
var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue;
|
||||||
|
return xPos.CompareTo(yPos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer();
|
||||||
|
|
||||||
|
private void Update(Framework unused)
|
||||||
|
{
|
||||||
|
this.HandleRemovedNodes();
|
||||||
|
|
||||||
|
var dtr = this.GetDtr();
|
||||||
|
if (dtr == null) return;
|
||||||
|
|
||||||
|
// The collision node on the DTR element is always the width of its content
|
||||||
|
if (dtr->UldManager.NodeList == null) return;
|
||||||
|
|
||||||
|
// If we have an unmodified DTR but still have entries, we need to
|
||||||
|
// work to reset our state.
|
||||||
|
if (!this.CheckForDalamudNodes())
|
||||||
|
this.RecreateNodes();
|
||||||
|
|
||||||
|
var collisionNode = dtr->UldManager.NodeList[1];
|
||||||
|
if (collisionNode == null) return;
|
||||||
|
|
||||||
|
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||||
|
// collisionNode->X + collisionNode->Width.
|
||||||
|
var runningXPos = this.configuration.DtrSwapDirection
|
||||||
|
? collisionNode->X + collisionNode->Width
|
||||||
|
: collisionNode->X;
|
||||||
|
|
||||||
|
for (var i = 0; i < this.entries.Count; i++)
|
||||||
|
{
|
||||||
|
var data = this.entries[i];
|
||||||
|
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||||
|
|
||||||
|
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||||
{
|
{
|
||||||
entry.TextNode = this.MakeNode(++this.runningNodeIds);
|
var node = data.TextNode;
|
||||||
entry.Added = false;
|
node->SetText(data.Text?.Encode());
|
||||||
}
|
ushort w = 0, h = 0;
|
||||||
}
|
|
||||||
|
|
||||||
private bool AddNode(AtkTextNode* node)
|
if (isHide)
|
||||||
{
|
{
|
||||||
var dtr = this.GetDtr();
|
node->AtkResNode.ToggleVisibility(false);
|
||||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->AtkResNode.ToggleVisibility(true);
|
||||||
|
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||||
|
node->AtkResNode.SetWidth(w);
|
||||||
|
}
|
||||||
|
|
||||||
var lastChild = dtr->RootNode->ChildNode;
|
data.Dirty = false;
|
||||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
|
||||||
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
|
||||||
lastChild->PrevSiblingNode = (AtkResNode*)node;
|
|
||||||
node->AtkResNode.ParentNode = lastChild->ParentNode;
|
|
||||||
node->AtkResNode.NextSiblingNode = lastChild;
|
|
||||||
|
|
||||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1);
|
|
||||||
Log.Debug("Set last sibling of DTR and updated child count");
|
|
||||||
|
|
||||||
dtr->UldManager.UpdateDrawNodeList();
|
|
||||||
Log.Debug("Updated node draw list");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool RemoveNode(AtkTextNode* node)
|
|
||||||
{
|
|
||||||
var dtr = this.GetDtr();
|
|
||||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
|
||||||
|
|
||||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
|
||||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
|
||||||
|
|
||||||
// if (tmpNextNode != null)
|
|
||||||
tmpNextNode->PrevSiblingNode = tmpPrevNode;
|
|
||||||
if (tmpPrevNode != null)
|
|
||||||
tmpPrevNode->NextSiblingNode = tmpNextNode;
|
|
||||||
node->AtkResNode.Destroy(true);
|
|
||||||
|
|
||||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
|
||||||
Log.Debug("Set last sibling of DTR and updated child count");
|
|
||||||
dtr->UldManager.UpdateDrawNodeList();
|
|
||||||
Log.Debug("Updated node draw list");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AtkTextNode* MakeNode(uint nodeId)
|
|
||||||
{
|
|
||||||
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
|
||||||
if (newTextNode == null)
|
|
||||||
{
|
|
||||||
Log.Debug("Failed to allocate memory for text node");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
if (!data.Added)
|
||||||
newTextNode->Ctor();
|
{
|
||||||
|
data.Added = this.AddNode(data.TextNode);
|
||||||
|
}
|
||||||
|
|
||||||
newTextNode->AtkResNode.NodeID = nodeId;
|
if (!isHide)
|
||||||
newTextNode->AtkResNode.Type = NodeType.Text;
|
{
|
||||||
newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop);
|
var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing;
|
||||||
newTextNode->AtkResNode.DrawFlags = 12;
|
|
||||||
newTextNode->AtkResNode.SetWidth(22);
|
|
||||||
newTextNode->AtkResNode.SetHeight(22);
|
|
||||||
newTextNode->AtkResNode.SetPositionFloat(-200, 2);
|
|
||||||
|
|
||||||
newTextNode->LineSpacing = 12;
|
if (this.configuration.DtrSwapDirection)
|
||||||
newTextNode->AlignmentFontType = 5;
|
{
|
||||||
newTextNode->FontSize = 14;
|
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||||
newTextNode->TextFlags = (byte)TextFlags.Edge;
|
runningXPos += elementWidth;
|
||||||
newTextNode->TextFlags2 = 0;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runningXPos -= elementWidth;
|
||||||
|
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newTextNode->SetText(" ");
|
this.entries[i] = data;
|
||||||
|
|
||||||
newTextNode->TextColor.R = 255;
|
|
||||||
newTextNode->TextColor.G = 255;
|
|
||||||
newTextNode->TextColor.B = 255;
|
|
||||||
newTextNode->TextColor.A = 255;
|
|
||||||
|
|
||||||
newTextNode->EdgeColor.R = 142;
|
|
||||||
newTextNode->EdgeColor.G = 106;
|
|
||||||
newTextNode->EdgeColor.B = 12;
|
|
||||||
newTextNode->EdgeColor.A = 255;
|
|
||||||
|
|
||||||
return newTextNode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if there are any Dalamud nodes in the DTR.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
||||||
|
private bool CheckForDalamudNodes()
|
||||||
|
{
|
||||||
|
var dtr = this.GetDtr();
|
||||||
|
if (dtr == null || dtr->RootNode == null) return false;
|
||||||
|
|
||||||
|
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
||||||
|
{
|
||||||
|
if (dtr->UldManager.NodeList[i]->NodeID > 1000)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecreateNodes()
|
||||||
|
{
|
||||||
|
this.runningNodeIds = BaseNodeId;
|
||||||
|
foreach (var entry in this.entries)
|
||||||
|
{
|
||||||
|
entry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||||
|
entry.Added = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AddNode(AtkTextNode* node)
|
||||||
|
{
|
||||||
|
var dtr = this.GetDtr();
|
||||||
|
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||||
|
|
||||||
|
var lastChild = dtr->RootNode->ChildNode;
|
||||||
|
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||||
|
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
||||||
|
lastChild->PrevSiblingNode = (AtkResNode*)node;
|
||||||
|
node->AtkResNode.ParentNode = lastChild->ParentNode;
|
||||||
|
node->AtkResNode.NextSiblingNode = lastChild;
|
||||||
|
|
||||||
|
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1);
|
||||||
|
Log.Debug("Set last sibling of DTR and updated child count");
|
||||||
|
|
||||||
|
dtr->UldManager.UpdateDrawNodeList();
|
||||||
|
Log.Debug("Updated node draw list");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RemoveNode(AtkTextNode* node)
|
||||||
|
{
|
||||||
|
var dtr = this.GetDtr();
|
||||||
|
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||||
|
|
||||||
|
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||||
|
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||||
|
|
||||||
|
// if (tmpNextNode != null)
|
||||||
|
tmpNextNode->PrevSiblingNode = tmpPrevNode;
|
||||||
|
if (tmpPrevNode != null)
|
||||||
|
tmpPrevNode->NextSiblingNode = tmpNextNode;
|
||||||
|
node->AtkResNode.Destroy(true);
|
||||||
|
|
||||||
|
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
||||||
|
Log.Debug("Set last sibling of DTR and updated child count");
|
||||||
|
dtr->UldManager.UpdateDrawNodeList();
|
||||||
|
Log.Debug("Updated node draw list");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkTextNode* MakeNode(uint nodeId)
|
||||||
|
{
|
||||||
|
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
||||||
|
if (newTextNode == null)
|
||||||
|
{
|
||||||
|
Log.Debug("Failed to allocate memory for text node");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
||||||
|
newTextNode->Ctor();
|
||||||
|
|
||||||
|
newTextNode->AtkResNode.NodeID = nodeId;
|
||||||
|
newTextNode->AtkResNode.Type = NodeType.Text;
|
||||||
|
newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop);
|
||||||
|
newTextNode->AtkResNode.DrawFlags = 12;
|
||||||
|
newTextNode->AtkResNode.SetWidth(22);
|
||||||
|
newTextNode->AtkResNode.SetHeight(22);
|
||||||
|
newTextNode->AtkResNode.SetPositionFloat(-200, 2);
|
||||||
|
|
||||||
|
newTextNode->LineSpacing = 12;
|
||||||
|
newTextNode->AlignmentFontType = 5;
|
||||||
|
newTextNode->FontSize = 14;
|
||||||
|
newTextNode->TextFlags = (byte)TextFlags.Edge;
|
||||||
|
newTextNode->TextFlags2 = 0;
|
||||||
|
|
||||||
|
newTextNode->SetText(" ");
|
||||||
|
|
||||||
|
newTextNode->TextColor.R = 255;
|
||||||
|
newTextNode->TextColor.G = 255;
|
||||||
|
newTextNode->TextColor.B = 255;
|
||||||
|
newTextNode->TextColor.A = 255;
|
||||||
|
|
||||||
|
newTextNode->EdgeColor.R = 142;
|
||||||
|
newTextNode->EdgeColor.G = 106;
|
||||||
|
newTextNode->EdgeColor.B = 12;
|
||||||
|
newTextNode->EdgeColor.A = 255;
|
||||||
|
|
||||||
|
return newTextNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,91 +3,90 @@
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.Dtr
|
namespace Dalamud.Game.Gui.Dtr;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing an entry in the server info bar.
|
||||||
|
/// </summary>
|
||||||
|
public sealed unsafe class DtrBarEntry : IDisposable
|
||||||
{
|
{
|
||||||
|
private bool shownBacking = true;
|
||||||
|
private SeString? textBacking = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class representing an entry in the server info bar.
|
/// Initializes a new instance of the <see cref="DtrBarEntry"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe class DtrBarEntry : IDisposable
|
/// <param name="title">The title of the bar entry.</param>
|
||||||
|
/// <param name="textNode">The corresponding text node.</param>
|
||||||
|
internal DtrBarEntry(string title, AtkTextNode* textNode)
|
||||||
{
|
{
|
||||||
private bool shownBacking = true;
|
this.Title = title;
|
||||||
private SeString? textBacking = null;
|
this.TextNode = textNode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DtrBarEntry"/> class.
|
/// Gets the title of this entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="title">The title of the bar entry.</param>
|
public string Title { get; init; }
|
||||||
/// <param name="textNode">The corresponding text node.</param>
|
|
||||||
internal DtrBarEntry(string title, AtkTextNode* textNode)
|
/// <summary>
|
||||||
|
/// Gets or sets the text of this entry.
|
||||||
|
/// </summary>
|
||||||
|
public SeString? Text
|
||||||
|
{
|
||||||
|
get => this.textBacking;
|
||||||
|
set
|
||||||
{
|
{
|
||||||
this.Title = title;
|
this.textBacking = value;
|
||||||
this.TextNode = textNode;
|
this.Dirty = true;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the title of this entry.
|
|
||||||
/// </summary>
|
|
||||||
public string Title { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the text of this entry.
|
|
||||||
/// </summary>
|
|
||||||
public SeString? Text
|
|
||||||
{
|
|
||||||
get => this.textBacking;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.textBacking = value;
|
|
||||||
this.Dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this entry is visible.
|
|
||||||
/// </summary>
|
|
||||||
public bool Shown
|
|
||||||
{
|
|
||||||
get => this.shownBacking;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.shownBacking = value;
|
|
||||||
this.Dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the internal text node of this entry.
|
|
||||||
/// </summary>
|
|
||||||
internal AtkTextNode* TextNode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this entry should be removed.
|
|
||||||
/// </summary>
|
|
||||||
internal bool ShouldBeRemoved { get; private set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this entry is dirty.
|
|
||||||
/// </summary>
|
|
||||||
internal bool Dirty { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this entry has just been added.
|
|
||||||
/// </summary>
|
|
||||||
internal bool Added { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove this entry from the bar.
|
|
||||||
/// You will need to re-acquire it from DtrBar to reuse it.
|
|
||||||
/// </summary>
|
|
||||||
public void Remove()
|
|
||||||
{
|
|
||||||
this.ShouldBeRemoved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.Remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this entry is visible.
|
||||||
|
/// </summary>
|
||||||
|
public bool Shown
|
||||||
|
{
|
||||||
|
get => this.shownBacking;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.shownBacking = value;
|
||||||
|
this.Dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the internal text node of this entry.
|
||||||
|
/// </summary>
|
||||||
|
internal AtkTextNode* TextNode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this entry should be removed.
|
||||||
|
/// </summary>
|
||||||
|
internal bool ShouldBeRemoved { get; private set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this entry is dirty.
|
||||||
|
/// </summary>
|
||||||
|
internal bool Dirty { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this entry has just been added.
|
||||||
|
/// </summary>
|
||||||
|
internal bool Added { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove this entry from the bar.
|
||||||
|
/// You will need to re-acquire it from DtrBar to reuse it.
|
||||||
|
/// </summary>
|
||||||
|
public void Remove()
|
||||||
|
{
|
||||||
|
this.ShouldBeRemoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,303 +9,302 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.FlyText
|
namespace Dalamud.Game.Gui.FlyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
public sealed class FlyTextGui : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
/// <summary>
|
||||||
public sealed class FlyTextGui : IDisposable, IServiceType
|
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private FlyTextGui(SigScanner sigScanner)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.Address = new FlyTextGuiAddressResolver();
|
||||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
this.Address.Setup(sigScanner);
|
||||||
/// </summary>
|
|
||||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
|
||||||
|
|
||||||
/// <summary>
|
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||||
/// </summary>
|
}
|
||||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
/// <summary>
|
||||||
private FlyTextGui(SigScanner sigScanner)
|
/// The delegate defining the type for the FlyText event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||||
|
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||||
|
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||||
|
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||||
|
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||||
|
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||||
|
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||||
|
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||||
|
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||||
|
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||||
|
public delegate void OnFlyTextCreatedDelegate(
|
||||||
|
ref FlyTextKind kind,
|
||||||
|
ref int val1,
|
||||||
|
ref int val2,
|
||||||
|
ref SeString text1,
|
||||||
|
ref SeString text2,
|
||||||
|
ref uint color,
|
||||||
|
ref uint icon,
|
||||||
|
ref float yOffset,
|
||||||
|
ref bool handled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private delegate for the native CreateFlyText function's hook.
|
||||||
|
/// </summary>
|
||||||
|
private delegate IntPtr CreateFlyTextDelegate(
|
||||||
|
IntPtr addonFlyText,
|
||||||
|
FlyTextKind kind,
|
||||||
|
int val1,
|
||||||
|
int val2,
|
||||||
|
IntPtr text2,
|
||||||
|
uint color,
|
||||||
|
uint icon,
|
||||||
|
IntPtr text1,
|
||||||
|
float yOffset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private delegate for the native AddFlyText function pointer.
|
||||||
|
/// </summary>
|
||||||
|
private delegate void AddFlyTextDelegate(
|
||||||
|
IntPtr addonFlyText,
|
||||||
|
uint actorIndex,
|
||||||
|
uint messageMax,
|
||||||
|
IntPtr numbers,
|
||||||
|
uint offsetNum,
|
||||||
|
uint offsetNumMax,
|
||||||
|
IntPtr strings,
|
||||||
|
uint offsetStr,
|
||||||
|
uint offsetStrMax,
|
||||||
|
int unknown);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The FlyText event that can be subscribed to.
|
||||||
|
/// </summary>
|
||||||
|
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||||
|
|
||||||
|
private Dalamud Dalamud { get; }
|
||||||
|
|
||||||
|
private FlyTextGuiAddressResolver Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.createFlyTextHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a fly text in-game on the local player.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||||
|
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||||
|
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||||
|
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||||
|
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||||
|
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||||
|
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||||
|
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||||
|
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
||||||
|
{
|
||||||
|
// Known valid flytext region within the atk arrays
|
||||||
|
var numIndex = 28;
|
||||||
|
var strIndex = 25;
|
||||||
|
var numOffset = 147u;
|
||||||
|
var strOffset = 28u;
|
||||||
|
|
||||||
|
// Get the UI module and flytext addon pointers
|
||||||
|
var gameGui = Service<GameGui>.GetNullable();
|
||||||
|
if (gameGui == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||||
|
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||||
|
|
||||||
|
if (ui == null || flytext == IntPtr.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get the number and string arrays we need
|
||||||
|
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||||
|
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||||
|
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||||
|
|
||||||
|
// Write the values to the arrays using a known valid flytext region
|
||||||
|
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||||
|
numArray->IntArray[numOffset + 1] = (int)kind;
|
||||||
|
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
||||||
|
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
||||||
|
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
||||||
|
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
||||||
|
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
||||||
|
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
||||||
|
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
||||||
|
|
||||||
|
fixed (byte* pText1 = text1.Encode())
|
||||||
{
|
{
|
||||||
this.Address = new FlyTextGuiAddressResolver();
|
fixed (byte* pText2 = text2.Encode())
|
||||||
this.Address.Setup(sigScanner);
|
|
||||||
|
|
||||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
|
||||||
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The delegate defining the type for the FlyText event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
|
||||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
|
||||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
|
||||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
|
||||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
|
||||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
|
||||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
|
||||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
|
||||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
|
||||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
|
||||||
public delegate void OnFlyTextCreatedDelegate(
|
|
||||||
ref FlyTextKind kind,
|
|
||||||
ref int val1,
|
|
||||||
ref int val2,
|
|
||||||
ref SeString text1,
|
|
||||||
ref SeString text2,
|
|
||||||
ref uint color,
|
|
||||||
ref uint icon,
|
|
||||||
ref float yOffset,
|
|
||||||
ref bool handled);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private delegate for the native CreateFlyText function's hook.
|
|
||||||
/// </summary>
|
|
||||||
private delegate IntPtr CreateFlyTextDelegate(
|
|
||||||
IntPtr addonFlyText,
|
|
||||||
FlyTextKind kind,
|
|
||||||
int val1,
|
|
||||||
int val2,
|
|
||||||
IntPtr text2,
|
|
||||||
uint color,
|
|
||||||
uint icon,
|
|
||||||
IntPtr text1,
|
|
||||||
float yOffset);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private delegate for the native AddFlyText function pointer.
|
|
||||||
/// </summary>
|
|
||||||
private delegate void AddFlyTextDelegate(
|
|
||||||
IntPtr addonFlyText,
|
|
||||||
uint actorIndex,
|
|
||||||
uint messageMax,
|
|
||||||
IntPtr numbers,
|
|
||||||
uint offsetNum,
|
|
||||||
uint offsetNumMax,
|
|
||||||
IntPtr strings,
|
|
||||||
uint offsetStr,
|
|
||||||
uint offsetStrMax,
|
|
||||||
int unknown);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The FlyText event that can be subscribed to.
|
|
||||||
/// </summary>
|
|
||||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
|
||||||
|
|
||||||
private Dalamud Dalamud { get; }
|
|
||||||
|
|
||||||
private FlyTextGuiAddressResolver Address { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes of managed and unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.createFlyTextHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Displays a fly text in-game on the local player.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
|
||||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
|
||||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
|
||||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
|
||||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
|
||||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
|
||||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
|
||||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
|
||||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
|
||||||
{
|
|
||||||
// Known valid flytext region within the atk arrays
|
|
||||||
var numIndex = 28;
|
|
||||||
var strIndex = 25;
|
|
||||||
var numOffset = 147u;
|
|
||||||
var strOffset = 28u;
|
|
||||||
|
|
||||||
// Get the UI module and flytext addon pointers
|
|
||||||
var gameGui = Service<GameGui>.GetNullable();
|
|
||||||
if (gameGui == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
|
||||||
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
|
||||||
|
|
||||||
if (ui == null || flytext == IntPtr.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Get the number and string arrays we need
|
|
||||||
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
|
||||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
|
||||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
|
||||||
|
|
||||||
// Write the values to the arrays using a known valid flytext region
|
|
||||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
|
||||||
numArray->IntArray[numOffset + 1] = (int)kind;
|
|
||||||
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
|
||||||
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
|
||||||
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
|
||||||
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
|
||||||
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
|
||||||
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
|
||||||
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
|
||||||
|
|
||||||
fixed (byte* pText1 = text1.Encode())
|
|
||||||
{
|
{
|
||||||
fixed (byte* pText2 = text2.Encode())
|
strArray->StringArray[strOffset + 0] = pText1;
|
||||||
{
|
strArray->StringArray[strOffset + 1] = pText2;
|
||||||
strArray->StringArray[strOffset + 0] = pText1;
|
|
||||||
strArray->StringArray[strOffset + 1] = pText2;
|
|
||||||
|
|
||||||
this.addFlyTextNative(
|
this.addFlyTextNative(
|
||||||
flytext,
|
flytext,
|
||||||
actorIndex,
|
actorIndex,
|
||||||
1,
|
1,
|
||||||
(IntPtr)numArray,
|
(IntPtr)numArray,
|
||||||
numOffset,
|
numOffset,
|
||||||
9,
|
9,
|
||||||
(IntPtr)strArray,
|
(IntPtr)strArray,
|
||||||
strOffset,
|
strOffset,
|
||||||
2,
|
2,
|
||||||
0);
|
0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private static byte[] Terminate(byte[] source)
|
|
||||||
{
|
private static byte[] Terminate(byte[] source)
|
||||||
var terminated = new byte[source.Length + 1];
|
{
|
||||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
var terminated = new byte[source.Length + 1];
|
||||||
terminated[^1] = 0;
|
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||||
|
terminated[^1] = 0;
|
||||||
return terminated;
|
|
||||||
}
|
return terminated;
|
||||||
|
}
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(GameGui gameGui)
|
[ServiceManager.CallWhenServicesReady]
|
||||||
{
|
private void ContinueConstruction(GameGui gameGui)
|
||||||
this.createFlyTextHook.Enable();
|
{
|
||||||
}
|
this.createFlyTextHook.Enable();
|
||||||
|
}
|
||||||
private IntPtr CreateFlyTextDetour(
|
|
||||||
IntPtr addonFlyText,
|
private IntPtr CreateFlyTextDetour(
|
||||||
FlyTextKind kind,
|
IntPtr addonFlyText,
|
||||||
int val1,
|
FlyTextKind kind,
|
||||||
int val2,
|
int val1,
|
||||||
IntPtr text2,
|
int val2,
|
||||||
uint color,
|
IntPtr text2,
|
||||||
uint icon,
|
uint color,
|
||||||
IntPtr text1,
|
uint icon,
|
||||||
float yOffset)
|
IntPtr text1,
|
||||||
{
|
float yOffset)
|
||||||
var retVal = IntPtr.Zero;
|
{
|
||||||
try
|
var retVal = IntPtr.Zero;
|
||||||
{
|
try
|
||||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
{
|
||||||
|
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||||
var handled = false;
|
|
||||||
|
var handled = false;
|
||||||
var tmpKind = kind;
|
|
||||||
var tmpVal1 = val1;
|
var tmpKind = kind;
|
||||||
var tmpVal2 = val2;
|
var tmpVal1 = val1;
|
||||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
var tmpVal2 = val2;
|
||||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||||
var tmpColor = color;
|
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||||
var tmpIcon = icon;
|
var tmpColor = color;
|
||||||
var tmpYOffset = yOffset;
|
var tmpIcon = icon;
|
||||||
|
var tmpYOffset = yOffset;
|
||||||
var cmpText1 = tmpText1.ToString();
|
|
||||||
var cmpText2 = tmpText2.ToString();
|
var cmpText1 = tmpText1.ToString();
|
||||||
|
var cmpText2 = tmpText2.ToString();
|
||||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
|
||||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
$"kind({kind}) val1({val1}) val2({val2}) " +
|
||||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||||
Log.Verbose("[FlyText] Calling flytext events!");
|
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||||
this.FlyTextCreated?.Invoke(
|
Log.Verbose("[FlyText] Calling flytext events!");
|
||||||
ref tmpKind,
|
this.FlyTextCreated?.Invoke(
|
||||||
ref tmpVal1,
|
ref tmpKind,
|
||||||
ref tmpVal2,
|
ref tmpVal1,
|
||||||
ref tmpText1,
|
ref tmpVal2,
|
||||||
ref tmpText2,
|
ref tmpText1,
|
||||||
ref tmpColor,
|
ref tmpText2,
|
||||||
ref tmpIcon,
|
ref tmpColor,
|
||||||
ref tmpYOffset,
|
ref tmpIcon,
|
||||||
ref handled);
|
ref tmpYOffset,
|
||||||
|
ref handled);
|
||||||
// If handled, ignore the original call
|
|
||||||
if (handled)
|
// If handled, ignore the original call
|
||||||
{
|
if (handled)
|
||||||
Log.Verbose("[FlyText] FlyText was handled.");
|
{
|
||||||
|
Log.Verbose("[FlyText] FlyText was handled.");
|
||||||
// Returning null to AddFlyText from CreateFlyText will result
|
|
||||||
// in the operation being dropped entirely.
|
// Returning null to AddFlyText from CreateFlyText will result
|
||||||
return IntPtr.Zero;
|
// in the operation being dropped entirely.
|
||||||
}
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
// Check if any values have changed
|
|
||||||
var dirty = tmpKind != kind ||
|
// Check if any values have changed
|
||||||
tmpVal1 != val1 ||
|
var dirty = tmpKind != kind ||
|
||||||
tmpVal2 != val2 ||
|
tmpVal1 != val1 ||
|
||||||
tmpText1.ToString() != cmpText1 ||
|
tmpVal2 != val2 ||
|
||||||
tmpText2.ToString() != cmpText2 ||
|
tmpText1.ToString() != cmpText1 ||
|
||||||
tmpColor != color ||
|
tmpText2.ToString() != cmpText2 ||
|
||||||
tmpIcon != icon ||
|
tmpColor != color ||
|
||||||
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
tmpIcon != icon ||
|
||||||
|
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
||||||
// If not dirty, make the original call
|
|
||||||
if (!dirty)
|
// If not dirty, make the original call
|
||||||
{
|
if (!dirty)
|
||||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
{
|
||||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||||
}
|
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
||||||
|
}
|
||||||
var terminated1 = Terminate(tmpText1.Encode());
|
|
||||||
var terminated2 = Terminate(tmpText2.Encode());
|
var terminated1 = Terminate(tmpText1.Encode());
|
||||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
var terminated2 = Terminate(tmpText2.Encode());
|
||||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||||
|
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||||
retVal = this.createFlyTextHook.Original(
|
|
||||||
addonFlyText,
|
retVal = this.createFlyTextHook.Original(
|
||||||
tmpKind,
|
addonFlyText,
|
||||||
tmpVal1,
|
tmpKind,
|
||||||
tmpVal2,
|
tmpVal1,
|
||||||
pText2,
|
tmpVal2,
|
||||||
tmpColor,
|
pText2,
|
||||||
tmpIcon,
|
tmpColor,
|
||||||
pText1,
|
tmpIcon,
|
||||||
tmpYOffset);
|
pText1,
|
||||||
|
tmpYOffset);
|
||||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
|
||||||
|
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||||
Task.Delay(2000).ContinueWith(_ =>
|
|
||||||
{
|
Task.Delay(2000).ContinueWith(_ =>
|
||||||
try
|
{
|
||||||
{
|
try
|
||||||
Marshal.FreeHGlobal(pText1);
|
{
|
||||||
Marshal.FreeHGlobal(pText2);
|
Marshal.FreeHGlobal(pText1);
|
||||||
Log.Verbose("[FlyText] Freed strings.");
|
Marshal.FreeHGlobal(pText2);
|
||||||
}
|
Log.Verbose("[FlyText] Freed strings.");
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
{
|
||||||
}
|
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
{
|
||||||
}
|
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
||||||
|
}
|
||||||
return retVal;
|
|
||||||
}
|
return retVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,31 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.FlyText
|
namespace Dalamud.Game.Gui.FlyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
/// Gets the address of the native AddFlyText method, which occurs
|
||||||
|
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||||
|
/// elements can be added in a single AddFlyText call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
public IntPtr AddFlyText { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native CreateFlyText method, which occurs
|
||||||
|
/// when the game creates a new fly text element. This method is called
|
||||||
|
/// once per fly text element, and can be called multiple times per
|
||||||
|
/// AddFlyText call.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr CreateFlyText { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||||
/// Gets the address of the native AddFlyText method, which occurs
|
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
||||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
|
||||||
/// elements can be added in a single AddFlyText call.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr AddFlyText { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native CreateFlyText method, which occurs
|
|
||||||
/// when the game creates a new fly text element. This method is called
|
|
||||||
/// once per fly text element, and can be called multiple times per
|
|
||||||
/// AddFlyText call.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr CreateFlyText { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
|
||||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,298 +1,297 @@
|
||||||
namespace Dalamud.Game.Gui.FlyText
|
namespace Dalamud.Game.Gui.FlyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum of FlyTextKind values. Members suffixed with
|
||||||
|
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||||
|
/// </summary>
|
||||||
|
public enum FlyTextKind : int
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum of FlyTextKind values. Members suffixed with
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
/// Used for autos and incoming DoTs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum FlyTextKind : int
|
AutoAttack = 0,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
|
||||||
/// Used for autos and incoming DoTs.
|
|
||||||
/// </summary>
|
|
||||||
AutoAttack = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
/// Does a bounce effect on appearance.
|
/// Does a bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DirectHit = 1,
|
DirectHit = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit = 2,
|
CriticalHit = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||||
/// Does not scroll up or down the screen.
|
/// Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalDirectHit = 3,
|
CriticalDirectHit = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack = 4,
|
NamedAttack = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedDirectHit = 5,
|
NamedDirectHit = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHit = 6,
|
NamedCriticalHit = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalDirectHit = 7,
|
NamedCriticalDirectHit = 7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps, serif MISS.
|
/// All caps, serif MISS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Miss = 8,
|
Miss = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to all caps serif MISS.
|
/// Sans-serif Text1 next to all caps serif MISS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMiss = 9,
|
NamedMiss = 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif DODGE.
|
/// All caps serif DODGE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Dodge = 10,
|
Dodge = 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedDodge = 11,
|
NamedDodge = 11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Icon next to sans-serif Text1.
|
/// Icon next to sans-serif Text1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIcon = 12,
|
NamedIcon = 12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Icon next to sans-serif Text1 (2).
|
/// Icon next to sans-serif Text1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIcon2 = 13,
|
NamedIcon2 = 13,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Exp = 14,
|
Exp = 14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle.
|
/// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IslandExp = 15,
|
IslandExp = 15,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp = 16,
|
NamedMp = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp = 17,
|
NamedTp = 17,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack2 = 18,
|
NamedAttack2 = 18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp2 = 19,
|
NamedMp2 = 19,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp2 = 20,
|
NamedTp2 = 20,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedEp = 21,
|
NamedEp = 21,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCp = 22,
|
NamedCp = 22,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedGp = 23,
|
NamedGp = 23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays nothing.
|
/// Displays nothing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 24,
|
None = 24,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif INVULNERABLE.
|
/// All caps serif INVULNERABLE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Invulnerable = 25,
|
Invulnerable = 25,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps sans-serif condensed font INTERRUPTED!
|
/// All caps sans-serif condensed font INTERRUPTED!
|
||||||
/// Does a large bounce effect on appearance.
|
/// Does a large bounce effect on appearance.
|
||||||
/// Does not scroll up or down the screen.
|
/// Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Interrupted = 26,
|
Interrupted = 26,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2.
|
/// AutoAttack with no Text2.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText = 27,
|
AutoAttackNoText = 27,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (2).
|
/// AutoAttack with no Text2 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText2 = 28,
|
AutoAttackNoText2 = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit2 = 29,
|
CriticalHit2 = 29,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (3).
|
/// AutoAttack with no Text2 (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText3 = 30,
|
AutoAttackNoText3 = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHit2 = 31,
|
NamedCriticalHit2 = 31,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||||
/// Does a jiggle effect to the right on appearance.
|
/// Does a jiggle effect to the right on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHitWithMp = 32,
|
NamedCriticalHitWithMp = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||||
/// Does a jiggle effect to the right on appearance.
|
/// Does a jiggle effect to the right on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedCriticalHitWithTp = 33,
|
NamedCriticalHitWithTp = 33,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconHasNoEffect = 34,
|
NamedIconHasNoEffect = 34,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFaded = 35,
|
NamedIconFaded = 35,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||||
/// Used for buff expiration.
|
/// Used for buff expiration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFaded2 = 36,
|
NamedIconFaded2 = 36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 in sans-serif font.
|
/// Text1 in sans-serif font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Named = 37,
|
Named = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconFullyResisted = 38,
|
NamedIconFullyResisted = 38,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif 'INCAPACITATED!'.
|
/// All caps serif 'INCAPACITATED!'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Incapacitated = 39,
|
Incapacitated = 39,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedFullyResisted = 40,
|
NamedFullyResisted = 40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text1 with sans-serif "has no effect!" to the right.
|
/// Text1 with sans-serif "has no effect!" to the right.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedHasNoEffect = 41,
|
NamedHasNoEffect = 41,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedAttack3 = 42,
|
NamedAttack3 = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedMp3 = 43,
|
NamedMp3 = 43,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedTp3 = 44,
|
NamedTp3 = 44,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconInvulnerable = 45,
|
NamedIconInvulnerable = 45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif RESIST.
|
/// All caps serif RESIST.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Resist = 46,
|
Resist = 46,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NamedIconWithItemOutline = 47,
|
NamedIconWithItemOutline = 47,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AutoAttack with no Text2 (4).
|
/// AutoAttack with no Text2 (4).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AutoAttackNoText4 = 48,
|
AutoAttackNoText4 = 48,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit3 = 49,
|
CriticalHit3 = 49,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif REFLECT.
|
/// All caps serif REFLECT.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Reflect = 50,
|
Reflect = 50,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All caps serif REFLECTED.
|
/// All caps serif REFLECTED.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Reflected = 51,
|
Reflected = 51,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||||
/// Does a bounce effect on appearance.
|
/// Does a bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DirectHit2 = 52,
|
DirectHit2 = 52,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||||
/// Does a bigger bounce effect on appearance.
|
/// Does a bigger bounce effect on appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalHit4 = 53,
|
CriticalHit4 = 53,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CriticalDirectHit2 = 54,
|
CriticalDirectHit2 = 54,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,80 +1,79 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The address resolver for the <see cref="GameGui"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The address resolver for the <see cref="GameGui"/> class.
|
/// Gets the base address of the native GuiManager class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
public IntPtr BaseAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native SetGlobalBgm method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr SetGlobalBgm { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleItemHover method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleItemHover { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleItemOut method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleItemOut { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleActionHover method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleActionHover { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleActionOut method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleActionOut { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native HandleImm method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr HandleImm { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native GetMatrixSingleton method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr GetMatrixSingleton { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ScreenToWorld method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr ScreenToWorld { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native ToggleUiHide method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr ToggleUiHide { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the native Utf8StringFromSequence method.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Utf8StringFromSequence { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||||
/// Gets the base address of the native GuiManager class.
|
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||||
/// </summary>
|
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||||
public IntPtr BaseAddress { get; private set; }
|
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");
|
||||||
/// <summary>
|
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||||
/// Gets the address of the native SetGlobalBgm method.
|
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||||
/// </summary>
|
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||||
public IntPtr SetGlobalBgm { get; private set; }
|
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||||
|
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native HandleItemHover method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr HandleItemHover { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native HandleItemOut method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr HandleItemOut { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native HandleActionHover method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr HandleActionHover { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native HandleActionOut method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr HandleActionOut { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native HandleImm method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr HandleImm { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native GetMatrixSingleton method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr GetMatrixSingleton { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native ScreenToWorld method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ScreenToWorld { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native ToggleUiHide method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ToggleUiHide { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the native Utf8StringFromSequence method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Utf8StringFromSequence { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
|
||||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
|
||||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
|
||||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
|
||||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
|
||||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
|
||||||
this.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.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
|
||||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue