mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Merge branch 'api12'
This commit is contained in:
commit
f94f03e114
137 changed files with 6870 additions and 944 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.100'
|
||||
dotnet-version: '9.0.200'
|
||||
- name: Define VERSION
|
||||
run: |
|
||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
|||
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
||||
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
||||
config.Language = json.value("Language", config.Language);
|
||||
config.Platform = json.value("Platform", config.Platform);
|
||||
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
||||
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
|
||||
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
|
||||
|
|
@ -123,6 +124,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
|||
config.BootVehEnabled = json.value("BootVehEnabled", config.BootVehEnabled);
|
||||
config.BootVehFull = json.value("BootVehFull", config.BootVehFull);
|
||||
config.BootEnableEtw = json.value("BootEnableEtw", config.BootEnableEtw);
|
||||
config.BootDisableLegacyCorruptedStateExceptions = json.value("BootDisableLegacyCorruptedStateExceptions", config.BootDisableLegacyCorruptedStateExceptions);
|
||||
config.BootDotnetOpenProcessHookMode = json.value("BootDotnetOpenProcessHookMode", config.BootDotnetOpenProcessHookMode);
|
||||
if (const auto it = json.find("BootEnabledGameFixes"); it != json.end() && it->is_array()) {
|
||||
config.BootEnabledGameFixes.clear();
|
||||
|
|
@ -148,6 +150,7 @@ void DalamudStartInfo::from_envvars() {
|
|||
BootVehEnabled = utils::get_env<bool>(L"DALAMUD_IS_VEH");
|
||||
BootVehFull = utils::get_env<bool>(L"DALAMUD_IS_VEH_FULL");
|
||||
BootEnableEtw = utils::get_env<bool>(L"DALAMUD_ENABLE_ETW");
|
||||
BootDisableLegacyCorruptedStateExceptions = utils::get_env<bool>(L"DALAMUD_DISABLE_LEGACY_CORRUPTED_STATE_EXCEPTIONS");
|
||||
BootDotnetOpenProcessHookMode = static_cast<DotNetOpenProcessHookMode>(utils::get_env<int>(L"DALAMUD_DOTNET_OPENPROCESS_HOOKMODE"));
|
||||
for (const auto& item : utils::get_env_list<std::string>(L"DALAMUD_GAMEFIX_LIST"))
|
||||
BootEnabledGameFixes.insert(unicode::convert<std::string>(item, &unicode::lower));
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ struct DalamudStartInfo {
|
|||
std::string PluginDirectory;
|
||||
std::string AssetDirectory;
|
||||
ClientLanguage Language = ClientLanguage::English;
|
||||
std::string Platform;
|
||||
std::string GameVersion;
|
||||
std::string TroubleshootingPackData;
|
||||
int DelayInitializeMs = 0;
|
||||
|
|
@ -61,6 +62,7 @@ struct DalamudStartInfo {
|
|||
bool BootVehEnabled = false;
|
||||
bool BootVehFull = false;
|
||||
bool BootEnableEtw = false;
|
||||
bool BootDisableLegacyCorruptedStateExceptions = false;
|
||||
DotNetOpenProcessHookMode BootDotnetOpenProcessHookMode = DotNetOpenProcessHookMode::ImportHooks;
|
||||
std::set<std::string> BootEnabledGameFixes{};
|
||||
std::set<std::string> BootUnhookDlls{};
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
const auto result = InitializeClrAndGetEntryPoint(
|
||||
g_hModule,
|
||||
g_startInfo.BootEnableEtw,
|
||||
false, // !g_startInfo.BootDisableLegacyCorruptedStateExceptions,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.EntryPoint, Dalamud",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "pch.h"
|
||||
#include "DalamudStartInfo.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
|
|
@ -584,6 +585,10 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
|
|||
return res;
|
||||
}
|
||||
|
||||
bool utils::is_running_on_wine() {
|
||||
return g_startInfo.Platform != "WINDOWS";
|
||||
}
|
||||
|
||||
std::filesystem::path utils::get_module_path(HMODULE hModule) {
|
||||
std::wstring buf(MAX_PATH, L'\0');
|
||||
while (true) {
|
||||
|
|
|
|||
|
|
@ -267,6 +267,8 @@ namespace utils {
|
|||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
bool is_running_on_wine();
|
||||
|
||||
std::filesystem::path get_module_path(HMODULE hModule);
|
||||
|
||||
/// @brief Find the game main window.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Common.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public DalamudStartInfo()
|
||||
{
|
||||
// ignored
|
||||
this.Platform = OSPlatform.Create("UNKNOWN");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -58,6 +59,12 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public ClientLanguage Language { get; set; } = ClientLanguage.English;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying platform<72>Dalamud runs on.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(OSPlatformConverter))]
|
||||
public OSPlatform Platform { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current game version code.
|
||||
/// </summary>
|
||||
|
|
@ -120,10 +127,15 @@ public record DalamudStartInfo
|
|||
public bool BootVehFull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not ETW should be enabled.
|
||||
/// Gets or sets a value indicating whether ETW should be enabled.
|
||||
/// </summary>
|
||||
public bool BootEnableEtw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to enable legacy corrupted state exceptions.
|
||||
/// </summary>
|
||||
public bool BootDisableLegacyCorruptedStateExceptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value choosing the OpenProcess hookmode.
|
||||
/// </summary>
|
||||
|
|
|
|||
78
Dalamud.Common/OSPlatformConverter.cs
Normal file
78
Dalamud.Common/OSPlatformConverter.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="OSPlatform"/> to and from a string (e.g. <c>"FreeBSD"</c>).
|
||||
/// </summary>
|
||||
public sealed class OSPlatformConverter : JsonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 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();
|
||||
}
|
||||
else if (value is OSPlatform)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException("Expected OSPlatform 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 OSPlatform.Create((string)reader.Value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. 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(OSPlatform);
|
||||
}
|
||||
}
|
||||
18
Dalamud.Common/Util/EnvironmentUtils.cs
Normal file
18
Dalamud.Common/Util/EnvironmentUtils.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Common.Util;
|
||||
|
||||
public static class EnvironmentUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to get an environment variable using the Try pattern.
|
||||
/// </summary>
|
||||
/// <param name="variableName">The env var to get.</param>
|
||||
/// <param name="value">An output containing the env var, if present.</param>
|
||||
/// <returns>A boolean indicating whether the var was present.</returns>
|
||||
public static bool TryGetEnvironmentVariable(string variableName, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
value = Environment.GetEnvironmentVariable(variableName);
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Dalamud.CorePlugin</AssemblyName>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ int wmain(int argc, wchar_t** argv)
|
|||
const auto result = InitializeClrAndGetEntryPoint(
|
||||
GetModuleHandleW(nullptr),
|
||||
false,
|
||||
false,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ using System.Text.RegularExpressions;
|
|||
|
||||
using Dalamud.Common;
|
||||
using Dalamud.Common.Game;
|
||||
using Dalamud.Common.Util;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Reloaded.Memory.Buffers;
|
||||
using Serilog;
|
||||
|
|
@ -94,6 +96,7 @@ namespace Dalamud.Injector
|
|||
args.Remove("--msgbox2");
|
||||
args.Remove("--msgbox3");
|
||||
args.Remove("--etw");
|
||||
args.Remove("--no-legacy-corrupted-state-exceptions");
|
||||
args.Remove("--veh");
|
||||
args.Remove("--veh-full");
|
||||
args.Remove("--no-plugin");
|
||||
|
|
@ -262,6 +265,35 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static OSPlatform DetectPlatformHeuristic()
|
||||
{
|
||||
var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll");
|
||||
var wineServerCallPtr = NativeFunctions.GetProcAddress(ntdll, "wine_server_call");
|
||||
var wineGetHostVersionPtr = NativeFunctions.GetProcAddress(ntdll, "wine_get_host_version");
|
||||
var winePlatform = GetWinePlatform(wineGetHostVersionPtr);
|
||||
var isWine = wineServerCallPtr != nint.Zero;
|
||||
|
||||
static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr)
|
||||
{
|
||||
if (wineGetHostVersionPtr == nint.Zero) return null;
|
||||
|
||||
var methodDelegate = (delegate* unmanaged[Cdecl]<out char*, out char*, void>)wineGetHostVersionPtr;
|
||||
methodDelegate(out var platformPtr, out var _);
|
||||
|
||||
if (platformPtr == null) return null;
|
||||
|
||||
return Marshal.PtrToStringAnsi((nint)platformPtr);
|
||||
}
|
||||
|
||||
if (!isWine)
|
||||
return OSPlatform.Windows;
|
||||
|
||||
if (winePlatform == "Darwin")
|
||||
return OSPlatform.OSX;
|
||||
|
||||
return OSPlatform.Linux;
|
||||
}
|
||||
|
||||
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
|
||||
{
|
||||
int len;
|
||||
|
|
@ -277,9 +309,14 @@ namespace Dalamud.Injector
|
|||
var logName = startInfo.LogName;
|
||||
var logPath = startInfo.LogPath;
|
||||
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
|
||||
var platformStr = startInfo.Platform.ToString().ToLowerInvariant();
|
||||
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
|
||||
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
|
||||
|
||||
// env vars are brought in prior to launch args, since args can override them.
|
||||
if (EnvironmentUtils.TryGetEnvironmentVariable("XL_PLATFORM", out var xlPlatformEnv))
|
||||
platformStr = xlPlatformEnv.ToLowerInvariant();
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
||||
|
|
@ -306,6 +343,10 @@ namespace Dalamud.Injector
|
|||
{
|
||||
languageStr = args[i][key.Length..].ToLowerInvariant();
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-platform="))
|
||||
{
|
||||
platformStr = args[i][key.Length..].ToLowerInvariant();
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
|
||||
{
|
||||
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
|
||||
|
|
@ -377,11 +418,37 @@ namespace Dalamud.Injector
|
|||
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
|
||||
}
|
||||
|
||||
OSPlatform platform;
|
||||
|
||||
// covers both win32 and Windows
|
||||
if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.Windows;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.Linux;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.OSX;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.OSX;
|
||||
}
|
||||
else
|
||||
{
|
||||
platform = DetectPlatformHeuristic();
|
||||
Log.Warning("Heuristically determined host system platform as {platform}", platform);
|
||||
}
|
||||
|
||||
startInfo.WorkingDirectory = workingDirectory;
|
||||
startInfo.ConfigurationPath = configurationPath;
|
||||
startInfo.PluginDirectory = pluginDirectory;
|
||||
startInfo.AssetDirectory = assetDirectory;
|
||||
startInfo.Language = clientLanguage;
|
||||
startInfo.Platform = platform;
|
||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||
startInfo.GameVersion = null;
|
||||
startInfo.TroubleshootingPackData = troubleshootingData;
|
||||
|
|
@ -399,6 +466,7 @@ namespace Dalamud.Injector
|
|||
// Set boot defaults
|
||||
startInfo.BootShowConsole = args.Contains("--console");
|
||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||
startInfo.BootDisableLegacyCorruptedStateExceptions = args.Contains("--no-legacy-corrupted-state-exceptions");
|
||||
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
||||
startInfo.BootEnabledGameFixes = new()
|
||||
{
|
||||
|
|
@ -463,13 +531,14 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory=path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]");
|
||||
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
|
||||
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
|
||||
|
||||
Console.WriteLine("Verbose logging:\t[-v]");
|
||||
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
|
||||
Console.WriteLine("Enable ETW:\t[--etw]");
|
||||
Console.WriteLine("Disable legacy corrupted state exceptions:\t[--no-legacy-corrupted-state-exceptions]");
|
||||
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]");
|
||||
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
|
||||
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
|
||||
|
|
@ -729,15 +798,42 @@ namespace Dalamud.Injector
|
|||
{
|
||||
try
|
||||
{
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
|
||||
gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize<Dictionary<string, string>>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
|
||||
if (dalamudStartInfo.Platform == OSPlatform.Windows)
|
||||
{
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
|
||||
gamePath = Path.Combine(
|
||||
JsonSerializer.CreateDefault()
|
||||
.Deserialize<Dictionary<string, string>>(
|
||||
new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"],
|
||||
"game",
|
||||
"ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
|
||||
}
|
||||
else if (dalamudStartInfo.Platform == OSPlatform.Linux)
|
||||
{
|
||||
var homeDir = $"Z:\\home\\{Environment.UserName}";
|
||||
var xivlauncherDir = Path.Combine(homeDir, ".xlcore");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini");
|
||||
var config = File.ReadAllLines(launcherConfigPath)
|
||||
.Where(line => line.Contains('='))
|
||||
.ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]);
|
||||
gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var homeDir = $"Z:\\Users\\{Environment.UserName}";
|
||||
var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac");
|
||||
// we could try to parse the binary plist file here if we really wanted to...
|
||||
gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using default game installation path from XOM: {0}", gamePath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g");
|
||||
Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -792,20 +888,6 @@ namespace Dalamud.Injector
|
|||
if (encryptArguments)
|
||||
{
|
||||
var rawTickCount = (uint)Environment.TickCount;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
[System.Runtime.InteropServices.DllImport("c")]
|
||||
#pragma warning disable SA1300
|
||||
static extern ulong clock_gettime_nsec_np(int clockId);
|
||||
#pragma warning restore SA1300
|
||||
|
||||
const int CLOCK_MONOTONIC_RAW = 4;
|
||||
var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000;
|
||||
Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
|
||||
rawTickCount = (uint)rawTickCountFixed;
|
||||
}
|
||||
|
||||
var ticks = rawTickCount & 0xFFFF_FFFFu;
|
||||
var key = ticks & 0xFFFF_0000u;
|
||||
gameArguments.Insert(0, $"T={ticks}");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
|
|
@ -910,5 +911,46 @@ namespace Dalamud.Injector
|
|||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
DuplicateOptions dwOptions);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew.
|
||||
/// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To
|
||||
/// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function.
|
||||
/// </summary>
|
||||
/// <param name="lpModuleName">
|
||||
/// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default
|
||||
/// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate
|
||||
/// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure
|
||||
/// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules
|
||||
/// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns
|
||||
/// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve
|
||||
/// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return
|
||||
/// value is NULL.To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||
public static extern IntPtr GetModuleHandleW(string lpModuleName);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
|
||||
/// </summary>
|
||||
/// <param name="hModule">
|
||||
/// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary,
|
||||
/// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules
|
||||
/// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
|
||||
/// </param>
|
||||
/// <param name="procName">
|
||||
/// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be
|
||||
/// in the low-order word; the high-order word must be zero.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is the address of the exported function or variable. If the function
|
||||
/// fails, the return value is NULL.To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
|
||||
[SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>11.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<RootNamespace>Dalamud.Test</RootNamespace>
|
||||
<AssemblyTitle>Dalamud.Test</AssemblyTitle>
|
||||
|
|
|
|||
22
Dalamud.sln
22
Dalamud.sln
|
|
@ -74,8 +74,10 @@ Global
|
|||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
|
|
@ -124,14 +126,14 @@ Global
|
|||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|x64
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EFormat_002ESettingsUpgrade_002EAlignmentTabFillStyleMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bannedplugin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=clientopcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=collectability/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dalamud/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=FFXIV/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flytext/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
@ -66,6 +67,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PLUGINR/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Refilter/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=serveropcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=spiritbond/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Universalis/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsanitized/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uploaders/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal;
|
|||
/// </summary>
|
||||
internal class EnvironmentConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<EnableWindowsTargeting>True</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<DalamudVersion>11.0.8.0</DalamudVersion>
|
||||
<DalamudVersion>12.0.0.0</DalamudVersion>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||
|
|
@ -94,6 +90,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
|
||||
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
|
||||
<ProjectReference Include="..\lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
|
||||
|
|
|
|||
|
|
@ -182,10 +182,8 @@ public sealed class EntryPoint
|
|||
// Apply common fixes for culture issues
|
||||
CultureFixes.Apply();
|
||||
|
||||
// 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.IsWine())
|
||||
// Currently VEH is not fully functional on WINE
|
||||
if (info.Platform != OSPlatform.Windows)
|
||||
InitSymbolHandler(info);
|
||||
|
||||
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
||||
|
|
|
|||
89
Dalamud/Game/ActionKind.cs
Normal file
89
Dalamud/Game/ActionKind.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing possible action kinds.
|
||||
/// </summary>
|
||||
public enum ActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// A Trait.
|
||||
/// </summary>
|
||||
Trait = 0,
|
||||
|
||||
/// <summary>
|
||||
/// An Action.
|
||||
/// </summary>
|
||||
Action = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A usable Item.
|
||||
/// </summary>
|
||||
Item = 2, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// A usable EventItem.
|
||||
/// </summary>
|
||||
EventItem = 3, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// An EventAction.
|
||||
/// </summary>
|
||||
EventAction = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A GeneralAction.
|
||||
/// </summary>
|
||||
GeneralAction = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A BuddyAction.
|
||||
/// </summary>
|
||||
BuddyAction = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A MainCommand.
|
||||
/// </summary>
|
||||
MainCommand = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A Companion.
|
||||
/// </summary>
|
||||
Companion = 8, // unresolved?!
|
||||
|
||||
/// <summary>
|
||||
/// A CraftAction.
|
||||
/// </summary>
|
||||
CraftAction = 9,
|
||||
|
||||
/// <summary>
|
||||
/// An Action (again).
|
||||
/// </summary>
|
||||
Action2 = 10, // what's the difference?
|
||||
|
||||
/// <summary>
|
||||
/// A PetAction.
|
||||
/// </summary>
|
||||
PetAction = 11,
|
||||
|
||||
/// <summary>
|
||||
/// A CompanyAction.
|
||||
/// </summary>
|
||||
CompanyAction = 12,
|
||||
|
||||
/// <summary>
|
||||
/// A Mount.
|
||||
/// </summary>
|
||||
Mount = 13,
|
||||
|
||||
// 14-18 unused
|
||||
|
||||
/// <summary>
|
||||
/// A BgcArmyAction.
|
||||
/// </summary>
|
||||
BgcArmyAction = 19,
|
||||
|
||||
/// <summary>
|
||||
/// An Ornament.
|
||||
/// </summary>
|
||||
Ornament = 20,
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ internal unsafe class AddonEventListener : IDisposable
|
|||
|
||||
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
|
||||
this.eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)Marshal.AllocHGlobal(sizeof(void*) * 3);
|
||||
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, void>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, AtkEventListener*>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,6 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
|
|||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
|
||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
|
||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20");
|
||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
/// </summary>
|
||||
public IntPtr ProcessPacketPlayerSetup { 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>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
|
|
@ -43,7 +37,5 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
// 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.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
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>
|
||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x78)]
|
||||
public int LeftStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x7C)]
|
||||
public int LeftStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x80)]
|
||||
public int RightStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x84)]
|
||||
public int RightStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x88)]
|
||||
public ushort ButtonsRaw;
|
||||
|
||||
/// <summary>
|
||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x8C)]
|
||||
public ushort ButtonsPressed;
|
||||
|
||||
/// <summary>
|
||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x90)]
|
||||
public ushort ButtonsReleased;
|
||||
|
||||
/// <summary>
|
||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x94)]
|
||||
public ushort ButtonsRepeat;
|
||||
}
|
||||
|
|
@ -4,7 +4,8 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
||||
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
|
@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.GamePad;
|
|||
#pragma warning restore SA1015
|
||||
internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
||||
{
|
||||
private readonly Hook<ControllerPoll>? gamepadPoll;
|
||||
private readonly Hook<PadDevice.Delegates.Poll>? gamepadPoll;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
|
|
@ -35,14 +36,10 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
|||
[ServiceManager.ServiceConstructor]
|
||||
private GamepadState(ClientState clientState)
|
||||
{
|
||||
var resolver = clientState.AddressResolver;
|
||||
Log.Verbose($"GamepadPoll address {Util.DescribeAddress(resolver.GamepadPoll)}");
|
||||
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||
this.gamepadPoll = Hook<PadDevice.Delegates.Poll>.FromAddress((nint)PadDevice.StaticVirtualTablePointer->Poll, this.GamepadPollDetour);
|
||||
this.gamepadPoll?.Enable();
|
||||
}
|
||||
|
||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
||||
/// </summary>
|
||||
|
|
@ -61,28 +58,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
|||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsPressed { get; private set; }
|
||||
internal GamepadButtons 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; }
|
||||
internal GamepadButtons 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; }
|
||||
internal GamepadButtons 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; }
|
||||
internal GamepadButtons ButtonsRepeat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether detour should block gamepad input for game.
|
||||
|
|
@ -95,16 +92,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
|||
internal bool NavEnableGamepad { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & button) > 0 ? 1 : 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & button) > 0 ? 1 : 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & button) > 0 ? 1 : 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
|
|
@ -115,28 +112,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private int GamepadPollDetour(IntPtr gamepadInput)
|
||||
private nint GamepadPollDetour(PadDevice* gamepadInput)
|
||||
{
|
||||
var original = this.gamepadPoll!.Original(gamepadInput);
|
||||
try
|
||||
{
|
||||
this.GamepadInputAddress = gamepadInput;
|
||||
var input = (GamepadInput*)gamepadInput;
|
||||
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;
|
||||
this.GamepadInputAddress = (nint)gamepadInput;
|
||||
|
||||
this.leftStickX = gamepadInput->GamepadInputData.LeftStickX;
|
||||
this.leftStickY = gamepadInput->GamepadInputData.LeftStickY;
|
||||
this.rightStickX = gamepadInput->GamepadInputData.RightStickX;
|
||||
this.rightStickY = gamepadInput->GamepadInputData.RightStickY;
|
||||
this.ButtonsRaw = (GamepadButtons)gamepadInput->GamepadInputData.Buttons;
|
||||
this.ButtonsPressed = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsPressed;
|
||||
this.ButtonsReleased = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsReleased;
|
||||
this.ButtonsRepeat = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsRepeat;
|
||||
|
||||
if (this.NavEnableGamepad)
|
||||
{
|
||||
input->LeftStickX = 0;
|
||||
input->LeftStickY = 0;
|
||||
input->RightStickX = 0;
|
||||
input->RightStickY = 0;
|
||||
gamepadInput->GamepadInputData.LeftStickX = 0;
|
||||
gamepadInput->GamepadInputData.LeftStickY = 0;
|
||||
gamepadInput->GamepadInputData.RightStickX = 0;
|
||||
gamepadInput->GamepadInputData.RightStickY = 0;
|
||||
|
||||
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
|
||||
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
|
||||
|
|
@ -153,16 +150,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
|||
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
||||
// This is debatable.
|
||||
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
||||
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
||||
& ~GamepadButtons.R2
|
||||
& ~GamepadButtons.DpadDown
|
||||
& ~GamepadButtons.DpadLeft
|
||||
& ~GamepadButtons.DpadUp
|
||||
& ~GamepadButtons.DpadRight);
|
||||
input->ButtonsRaw &= deletionMask;
|
||||
input->ButtonsPressed = 0;
|
||||
input->ButtonsReleased = 0;
|
||||
input->ButtonsRepeat = 0;
|
||||
const GamepadButtonsFlags deletionMask = ~GamepadButtonsFlags.L2
|
||||
& ~GamepadButtonsFlags.R2
|
||||
& ~GamepadButtonsFlags.DPadDown
|
||||
& ~GamepadButtonsFlags.DPadLeft
|
||||
& ~GamepadButtonsFlags.DPadUp
|
||||
& ~GamepadButtonsFlags.DPadRight;
|
||||
gamepadInput->GamepadInputData.Buttons &= deletionMask;
|
||||
gamepadInput->GamepadInputData.ButtonsPressed = 0;
|
||||
gamepadInput->GamepadInputData.ButtonsReleased = 0;
|
||||
gamepadInput->GamepadInputData.ButtonsRepeat = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ public enum BeastChakra : byte
|
|||
/// <summary>
|
||||
/// No chakra.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Opo-Opo chakra.
|
||||
/// </summary>
|
||||
OPOOPO = 1,
|
||||
OpoOpo = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The Raptor chakra.
|
||||
/// </summary>
|
||||
RAPTOR = 2,
|
||||
Raptor = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The Coeurl chakra.
|
||||
/// </summary>
|
||||
COEURL = 3,
|
||||
Coeurl = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,45 +8,45 @@ public enum CardType : byte
|
|||
/// <summary>
|
||||
/// No card.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Balance card.
|
||||
/// </summary>
|
||||
BALANCE = 1,
|
||||
Balance = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The Bole card.
|
||||
/// </summary>
|
||||
BOLE = 2,
|
||||
Bole = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The Arrow card.
|
||||
/// </summary>
|
||||
ARROW = 3,
|
||||
Arrow = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The Spear card.
|
||||
/// </summary>
|
||||
SPEAR = 4,
|
||||
Spear = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The Ewer card.
|
||||
/// </summary>
|
||||
EWER = 5,
|
||||
Ewer = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The Spire card.
|
||||
/// </summary>
|
||||
SPIRE = 6,
|
||||
Spire = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The Lord of Crowns card.
|
||||
/// </summary>
|
||||
LORD = 7,
|
||||
Lord = 7,
|
||||
|
||||
/// <summary>
|
||||
/// The Lady of Crowns card.
|
||||
/// </summary>
|
||||
LADY = 8,
|
||||
Lady = 8,
|
||||
}
|
||||
|
|
|
|||
22
Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs
Normal file
22
Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the current step of Delirium.
|
||||
/// </summary>
|
||||
public enum DeliriumStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Scarlet Delirium can be used.
|
||||
/// </summary>
|
||||
ScarletDelirium = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Comeuppance can be used.
|
||||
/// </summary>
|
||||
Comeuppance = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Torcleaver can be used.
|
||||
/// </summary>
|
||||
Torcleaver = 2,
|
||||
}
|
||||
|
|
@ -8,10 +8,10 @@ public enum DismissedFairy : byte
|
|||
/// <summary>
|
||||
/// Dismissed fairy is Eos.
|
||||
/// </summary>
|
||||
EOS = 6,
|
||||
Eos = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Dismissed fairy is Selene.
|
||||
/// </summary>
|
||||
SELENE = 7,
|
||||
Selene = 7,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ public enum DrawType : byte
|
|||
/// <summary>
|
||||
/// Astral Draw active.
|
||||
/// </summary>
|
||||
ASTRAL = 0,
|
||||
Astral = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Umbral Draw active.
|
||||
/// </summary>
|
||||
UMBRAL = 1,
|
||||
Umbral = 1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,25 +8,25 @@ public enum Kaeshi : byte
|
|||
/// <summary>
|
||||
/// No Kaeshi is active.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Kaeshi: Higanbana type.
|
||||
/// </summary>
|
||||
HIGANBANA = 1,
|
||||
Higanbana = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Kaeshi: Goken type.
|
||||
/// </summary>
|
||||
GOKEN = 2,
|
||||
Goken = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Kaeshi: Setsugekka type.
|
||||
/// </summary>
|
||||
SETSUGEKKA = 3,
|
||||
Setsugekka = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Kaeshi: Namikiri type.
|
||||
/// </summary>
|
||||
NAMIKIRI = 4,
|
||||
Namikiri = 4,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ public enum Mudras : byte
|
|||
/// <summary>
|
||||
/// Ten mudra.
|
||||
/// </summary>
|
||||
TEN = 1,
|
||||
Ten = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Chi mudra.
|
||||
/// </summary>
|
||||
CHI = 2,
|
||||
Chi = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Jin mudra.
|
||||
/// </summary>
|
||||
JIN = 3,
|
||||
Jin = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ public enum Nadi : byte
|
|||
/// <summary>
|
||||
/// No nadi.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Lunar nadi.
|
||||
/// </summary>
|
||||
LUNAR = 1,
|
||||
Lunar = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The Solar nadi.
|
||||
/// </summary>
|
||||
SOLAR = 2,
|
||||
Solar = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,40 +8,40 @@ public enum PetGlam : byte
|
|||
/// <summary>
|
||||
/// No pet glam.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Emerald carbuncle pet glam.
|
||||
/// </summary>
|
||||
EMERALD = 1,
|
||||
Emerald = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Topaz carbuncle pet glam.
|
||||
/// </summary>
|
||||
TOPAZ = 2,
|
||||
Topaz = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Ruby carbuncle pet glam.
|
||||
/// </summary>
|
||||
RUBY = 3,
|
||||
Ruby = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Normal carbuncle pet glam.
|
||||
/// </summary>
|
||||
CARBUNCLE = 4,
|
||||
Carbuncle = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Ifrit Egi pet glam.
|
||||
/// </summary>
|
||||
IFRIT = 5,
|
||||
Ifrit = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Titan Egi pet glam.
|
||||
/// </summary>
|
||||
TITAN = 6,
|
||||
Titan = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Garuda Egi pet glam.
|
||||
/// </summary>
|
||||
GARUDA = 7,
|
||||
Garuda = 7,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ public enum Sen : byte
|
|||
/// <summary>
|
||||
/// No Sen.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Setsu Sen type.
|
||||
/// </summary>
|
||||
SETSU = 1 << 0,
|
||||
Setsu = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Getsu Sen type.
|
||||
/// </summary>
|
||||
GETSU = 1 << 1,
|
||||
Getsu = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Ka Sen type.
|
||||
/// </summary>
|
||||
KA = 1 << 2,
|
||||
Ka = 1 << 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,35 +8,35 @@ public enum SerpentCombo : byte
|
|||
/// <summary>
|
||||
/// No Serpent combo is active.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Death Rattle action.
|
||||
/// </summary>
|
||||
DEATHRATTLE = 1,
|
||||
DeathRattle = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Last Lash action.
|
||||
/// </summary>
|
||||
LASTLASH = 2,
|
||||
LastLash = 2,
|
||||
|
||||
/// <summary>
|
||||
/// First Legacy action.
|
||||
/// </summary>
|
||||
FIRSTLEGACY = 3,
|
||||
FirstLegacy = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Second Legacy action.
|
||||
/// </summary>
|
||||
SECONDLEGACY = 4,
|
||||
SecondLegacy = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Third Legacy action.
|
||||
/// </summary>
|
||||
THIRDLEGACY = 5,
|
||||
ThirdLegacy = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Fourth Legacy action.
|
||||
/// </summary>
|
||||
FOURTHLEGACY = 6,
|
||||
FourthLegacy = 6,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ public enum Song : byte
|
|||
/// <summary>
|
||||
/// No song is active type.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Mage's Ballad type.
|
||||
/// </summary>
|
||||
MAGE = 1,
|
||||
Mage = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Army's Paeon type.
|
||||
/// </summary>
|
||||
ARMY = 2,
|
||||
Army = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The Wanderer's Minuet type.
|
||||
/// </summary>
|
||||
WANDERER = 3,
|
||||
Wanderer = 3,
|
||||
}
|
||||
|
|
|
|||
30
Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs
Normal file
30
Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the current attunement of a summoner.
|
||||
/// </summary>
|
||||
public enum SummonAttunement
|
||||
{
|
||||
/// <summary>
|
||||
/// No attunement.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Attuned to the summon Ifrit.
|
||||
/// Same as <see cref="JobGauge.Types.SMNGauge.IsIfritAttuned"/>.
|
||||
/// </summary>
|
||||
Ifrit = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Attuned to the summon Titan.
|
||||
/// Same as <see cref="JobGauge.Types.SMNGauge.IsTitanAttuned"/>.
|
||||
/// </summary>
|
||||
Titan = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Attuned to the summon Garuda.
|
||||
/// Same as <see cref="JobGauge.Types.SMNGauge.IsGarudaAttuned"/>.
|
||||
/// </summary>
|
||||
Garuda = 3,
|
||||
}
|
||||
|
|
@ -8,10 +8,10 @@ public enum SummonPet : byte
|
|||
/// <summary>
|
||||
/// No pet.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The summoned pet Carbuncle.
|
||||
/// </summary>
|
||||
CARBUNCLE = 23,
|
||||
Carbuncle = 23,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,15 +40,15 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
get
|
||||
{
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet))
|
||||
return Song.WANDERER;
|
||||
return Song.Wanderer;
|
||||
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon))
|
||||
return Song.ARMY;
|
||||
return Song.Army;
|
||||
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad))
|
||||
return Song.MAGE;
|
||||
return Song.Mage;
|
||||
|
||||
return Song.NONE;
|
||||
return Song.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,15 +60,15 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
get
|
||||
{
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed))
|
||||
return Song.WANDERER;
|
||||
return Song.Wanderer;
|
||||
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed))
|
||||
return Song.ARMY;
|
||||
return Song.Army;
|
||||
|
||||
if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed))
|
||||
return Song.MAGE;
|
||||
return Song.Mage;
|
||||
|
||||
return Song.NONE;
|
||||
return Song.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
/// 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"/>.
|
||||
/// This will always return an array of size 3, inactive Coda are represented by <see cref="Enums.Song.None"/>.
|
||||
/// </remarks>
|
||||
public Song[] Coda
|
||||
{
|
||||
|
|
@ -84,9 +84,9 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
{
|
||||
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,
|
||||
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,9 +1,12 @@
|
|||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory DRK job gauge.
|
||||
/// </summary>
|
||||
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
|
||||
public unsafe class DRKGauge : JobGaugeBase<DarkKnightGauge>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
|
||||
|
|
@ -34,4 +37,16 @@ public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
/// </summary>
|
||||
/// <returns><c>true</c> or <c>false</c>.</returns>
|
||||
public bool HasDarkArts => this.Struct->DarkArtsState > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the step of the Delirium Combo (Scarlet Delirium, Comeuppance,
|
||||
/// Torcleaver) that the player is on.<br/>
|
||||
/// Does not in any way consider whether the player is still under Delirium, or
|
||||
/// if the player still has stacks of Delirium to use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value will persist until combo is finished OR
|
||||
/// if the combo is not completed then the value will stay until about halfway into Delirium's cooldown.
|
||||
/// </remarks>
|
||||
public DeliriumStep DeliriumComboStep => (DeliriumStep)this.Struct->DeliriumStep;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
/// 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"/>.
|
||||
/// This will always return an array of size 3, inactive Beast Chakra are represented by <see cref="Enums.BeastChakra.None"/>.
|
||||
/// </remarks>
|
||||
public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray();
|
||||
|
||||
|
|
|
|||
|
|
@ -40,17 +40,17 @@ public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
/// 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;
|
||||
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;
|
||||
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;
|
||||
public bool HasKa => (this.Sen & Sen.Ka) != 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,13 @@ public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
|||
/// <summary>
|
||||
/// Gets the time remaining for the current attunement.
|
||||
/// </summary>
|
||||
public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer;
|
||||
[Obsolete("Typo fixed. Use AttunementTimerRemaining instead.", true)]
|
||||
public ushort AttunmentTimerRemaining => this.AttunementTimerRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining for the current attunement.
|
||||
/// </summary>
|
||||
public ushort AttunementTimerRemaining => this.Struct->AttunementTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon that will return after the current summon expires.
|
||||
|
|
@ -40,10 +46,25 @@ public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
|||
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of aspected Attunment remaining.
|
||||
/// Gets the amount of aspected Attunement remaining.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As of 7.01, this should be treated as a bit field.
|
||||
/// Use <see cref="AttunementCount"/> and <see cref="AttunementType"/> instead.
|
||||
/// </remarks>
|
||||
public byte Attunement => this.Struct->Attunement;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of attunement cost resource available.
|
||||
/// </summary>
|
||||
public byte AttunementCount => this.Struct->AttunementCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of attunement available.
|
||||
/// Use the summon attuned accessors instead.
|
||||
/// </summary>
|
||||
public SummonAttunement AttunementType => (SummonAttunement)this.Struct->AttunementType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current aether flags.
|
||||
/// Use the summon accessors instead.
|
||||
|
|
@ -84,19 +105,19 @@ public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
|||
/// 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);
|
||||
public bool IsIfritAttuned => this.AttunementType == SummonAttunement.Ifrit;
|
||||
|
||||
/// <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);
|
||||
public bool IsTitanAttuned => this.AttunementType == SummonAttunement.Titan;
|
||||
|
||||
/// <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);
|
||||
public bool IsGarudaAttuned => this.AttunementType == SummonAttunement.Garuda;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there are any Aetherflow stacks available.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
|
|
@ -31,20 +29,13 @@ namespace Dalamud.Game.ClientState.Objects;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("ObjectTable");
|
||||
|
||||
private static int objectTableLength;
|
||||
|
||||
private readonly ClientState clientState;
|
||||
private readonly CachedEntry[] cachedObjectTable;
|
||||
|
||||
private readonly ObjectPool<Enumerator> multiThreadedEnumerators =
|
||||
new DefaultObjectPoolProvider().Create<Enumerator>();
|
||||
|
||||
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
|
||||
|
||||
private long nextMultithreadedUsageWarnTime;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe ObjectTable(ClientState clientState)
|
||||
{
|
||||
|
|
@ -66,7 +57,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
get
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
return (nint)(&CSGameObjectManager.Instance()->Objects);
|
||||
}
|
||||
|
|
@ -80,7 +71,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
get
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update();
|
||||
}
|
||||
|
|
@ -89,7 +80,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <inheritdoc/>
|
||||
public IGameObject? SearchById(ulong gameObjectId)
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
if (gameObjectId is 0)
|
||||
return null;
|
||||
|
|
@ -106,7 +97,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <inheritdoc/>
|
||||
public IGameObject? SearchByEntityId(uint entityId)
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
if (entityId is 0 or 0xE0000000)
|
||||
return null;
|
||||
|
|
@ -123,7 +114,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <inheritdoc/>
|
||||
public unsafe nint GetObjectAddress(int index)
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address;
|
||||
}
|
||||
|
|
@ -131,7 +122,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <inheritdoc/>
|
||||
public unsafe IGameObject? CreateObjectReference(nint address)
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
if (this.clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
|
@ -155,27 +146,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
};
|
||||
}
|
||||
|
||||
[Api12ToDo("Use ThreadSafety.AssertMainThread() instead of this.")]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool WarnMultithreadedUsage()
|
||||
{
|
||||
if (ThreadSafety.IsMainThread)
|
||||
return false;
|
||||
|
||||
var n = Environment.TickCount64;
|
||||
if (this.nextMultithreadedUsageWarnTime < n)
|
||||
{
|
||||
this.nextMultithreadedUsageWarnTime = n + 30000;
|
||||
|
||||
Log.Warning(
|
||||
"{plugin} is accessing {objectTable} outside the main thread. This is deprecated.",
|
||||
Service<PluginManager>.Get().FindCallingPlugin()?.Name ?? "<unknown plugin>",
|
||||
nameof(ObjectTable));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Stores an object table entry, with preallocated concrete types.</summary>
|
||||
/// <remarks>Initializes a new instance of the <see cref="CachedEntry"/> struct.</remarks>
|
||||
/// <param name="gameObjectPtr">A pointer to the object table entry this entry should be pointing to.</param>
|
||||
|
|
@ -228,14 +198,7 @@ internal sealed partial class ObjectTable
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IGameObject> GetEnumerator()
|
||||
{
|
||||
// If something's trying to enumerate outside the framework thread, we use the ObjectPool.
|
||||
if (this.WarnMultithreadedUsage())
|
||||
{
|
||||
// let's not
|
||||
var e = this.multiThreadedEnumerators.Get();
|
||||
e.InitializeForPooledObjects(this);
|
||||
return e;
|
||||
}
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
// If we're on the framework thread, see if there's an already allocated enumerator available for use.
|
||||
foreach (ref var x in this.frameworkThreadEnumerators.AsSpan())
|
||||
|
|
@ -256,21 +219,12 @@ internal sealed partial class ObjectTable
|
|||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private sealed class Enumerator : IEnumerator<IGameObject>, IResettable
|
||||
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
|
||||
{
|
||||
private readonly int slotId;
|
||||
private ObjectTable? owner;
|
||||
private ObjectTable? owner = owner;
|
||||
|
||||
private int index = -1;
|
||||
|
||||
public Enumerator() => this.slotId = -1;
|
||||
|
||||
public Enumerator(ObjectTable owner, int slotId)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.slotId = slotId;
|
||||
}
|
||||
|
||||
public IGameObject Current { get; private set; } = null!;
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
|
@ -293,8 +247,6 @@ internal sealed partial class ObjectTable
|
|||
return false;
|
||||
}
|
||||
|
||||
public void InitializeForPooledObjects(ObjectTable ot) => this.owner = ot;
|
||||
|
||||
public void Reset() => this.index = -1;
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -302,10 +254,8 @@ internal sealed partial class ObjectTable
|
|||
if (this.owner is not { } o)
|
||||
return;
|
||||
|
||||
if (this.slotId == -1)
|
||||
o.multiThreadedEnumerators.Return(this);
|
||||
else
|
||||
o.frameworkThreadEnumerators[this.slotId] = this;
|
||||
if (slotId != -1)
|
||||
o.frameworkThreadEnumerators[slotId] = this;
|
||||
}
|
||||
|
||||
public bool TryReset()
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ public unsafe class Status
|
|||
|
||||
/// <summary>
|
||||
/// Gets the stack count of this status.
|
||||
/// Only valid if this is a non-food status.
|
||||
/// </summary>
|
||||
public byte StackCount => this.Struct->StackCount;
|
||||
[Obsolete($"Replaced with {nameof(Param)}", true)]
|
||||
public byte StackCount => (byte)this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining of this status.
|
||||
|
|
|
|||
|
|
@ -526,8 +526,8 @@ public class GameConfigSection
|
|||
{
|
||||
if (!this.enumMap.TryGetValue(entry->Index, out var enumObject))
|
||||
{
|
||||
if (entry->Name == null) return null;
|
||||
var name = MemoryHelper.ReadStringNullTerminated(new IntPtr(entry->Name));
|
||||
if (entry->Name.Value == null) return null;
|
||||
var name = entry->Name.ToString();
|
||||
if (Enum.TryParse(typeof(TEnum), name, out enumObject))
|
||||
{
|
||||
this.enumMap.TryAdd(entry->Index, enumObject);
|
||||
|
|
@ -556,12 +556,12 @@ public class GameConfigSection
|
|||
var e = configBase->ConfigEntry;
|
||||
for (var i = 0U; i < configBase->ConfigCount; i++, e++)
|
||||
{
|
||||
if (e->Name == null)
|
||||
if (e->Name.Value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eName = MemoryHelper.ReadStringNullTerminated(new IntPtr(e->Name));
|
||||
var eName = e->Name.ToString();
|
||||
if (eName.Equals(name))
|
||||
{
|
||||
this.indexMap.TryAdd(name, i);
|
||||
|
|
|
|||
|
|
@ -597,6 +597,20 @@ public enum SystemConfigOption
|
|||
[GameConfigOption("EnablePsFunction", ConfigType.UInt)]
|
||||
EnablePsFunction,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name ActiveInstanceGuid.
|
||||
/// This option is a String.
|
||||
/// </summary>
|
||||
[GameConfigOption("ActiveInstanceGuid", ConfigType.String)]
|
||||
ActiveInstanceGuid,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name ActiveProductGuid.
|
||||
/// This option is a String.
|
||||
/// </summary>
|
||||
[GameConfigOption("ActiveProductGuid", ConfigType.String)]
|
||||
ActiveProductGuid,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name WaterWet.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -996,6 +1010,27 @@ public enum SystemConfigOption
|
|||
[GameConfigOption("AutoChangeCameraMode", ConfigType.UInt)]
|
||||
AutoChangeCameraMode,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name MsqProgress.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("MsqProgress", ConfigType.UInt)]
|
||||
MsqProgress,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name PromptConfigUpdate.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("PromptConfigUpdate", ConfigType.UInt)]
|
||||
PromptConfigUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name TitleScreenType.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("TitleScreenType", ConfigType.UInt)]
|
||||
TitleScreenType,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name AccessibilitySoundVisualEnable.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -1059,6 +1094,13 @@ public enum SystemConfigOption
|
|||
[GameConfigOption("IdlingCameraAFK", ConfigType.UInt)]
|
||||
IdlingCameraAFK,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name FirstConfigBackup.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("FirstConfigBackup", ConfigType.UInt)]
|
||||
FirstConfigBackup,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name MouseSpeed.
|
||||
/// This option is a Float.
|
||||
|
|
@ -1436,46 +1478,4 @@ public enum SystemConfigOption
|
|||
/// </summary>
|
||||
[GameConfigOption("PadButton_R3", ConfigType.String)]
|
||||
PadButton_R3,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name ActiveInstanceGuid.
|
||||
/// This option is a String.
|
||||
/// </summary>
|
||||
[GameConfigOption("ActiveInstanceGuid", ConfigType.String)]
|
||||
ActiveInstanceGuid,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name ActiveProductGuid.
|
||||
/// This option is a String.
|
||||
/// </summary>
|
||||
[GameConfigOption("ActiveProductGuid", ConfigType.String)]
|
||||
ActiveProductGuid,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name MsqProgress.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("MsqProgress", ConfigType.UInt)]
|
||||
MsqProgress,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name PromptConfigUpdate.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("PromptConfigUpdate", ConfigType.UInt)]
|
||||
PromptConfigUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name TitleScreenType.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("TitleScreenType", ConfigType.UInt)]
|
||||
TitleScreenType,
|
||||
|
||||
/// <summary>
|
||||
/// System option with the internal name FirstConfigBackup.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("FirstConfigBackup", ConfigType.UInt)]
|
||||
FirstConfigBackup,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ public enum UiConfigOption
|
|||
[GameConfigOption("BattleEffectPvPEnemyPc", ConfigType.UInt)]
|
||||
BattleEffectPvPEnemyPc,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name PadMode.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("PadMode", ConfigType.UInt)]
|
||||
PadMode,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name WeaponAutoPutAway.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -114,14 +121,6 @@ public enum UiConfigOption
|
|||
[GameConfigOption("LockonDefaultZoom", ConfigType.Float)]
|
||||
LockonDefaultZoom,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name LockonDefaultZoom_179.
|
||||
/// This option is a Float.
|
||||
/// </summary>
|
||||
[Obsolete("This option won't work. Use LockonDefaultZoom.", true)]
|
||||
[GameConfigOption("LockonDefaultZoom_179", ConfigType.Float)]
|
||||
LockonDefaultZoom_179,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name CameraProductionOfAction.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -311,6 +310,27 @@ public enum UiConfigOption
|
|||
[GameConfigOption("RightClickExclusionMinion", ConfigType.UInt)]
|
||||
RightClickExclusionMinion,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltCharacter.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)]
|
||||
EnableMoveTiltCharacter,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltMountGround.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)]
|
||||
EnableMoveTiltMountGround,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltMountFly.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)]
|
||||
EnableMoveTiltMountFly,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name TurnSpeed.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -1130,6 +1150,27 @@ public enum UiConfigOption
|
|||
[GameConfigOption("HotbarXHBEditEnable", ConfigType.UInt)]
|
||||
HotbarXHBEditEnable,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name HotbarContentsAction2ReverseOperation.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("HotbarContentsAction2ReverseOperation", ConfigType.UInt)]
|
||||
HotbarContentsAction2ReverseOperation,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name HotbarContentsAction2ReturnInitialSlot.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("HotbarContentsAction2ReturnInitialSlot", ConfigType.UInt)]
|
||||
HotbarContentsAction2ReturnInitialSlot,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name HotbarContentsAction2ReverseRotate.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("HotbarContentsAction2ReverseRotate", ConfigType.UInt)]
|
||||
HotbarContentsAction2ReverseRotate,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name PlateType.
|
||||
/// This option is a UInt.
|
||||
|
|
@ -3572,32 +3613,4 @@ public enum UiConfigOption
|
|||
/// </summary>
|
||||
[GameConfigOption("PvPFrontlinesGCFree", ConfigType.UInt)]
|
||||
PvPFrontlinesGCFree,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name PadMode.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("PadMode", ConfigType.UInt)]
|
||||
PadMode,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltCharacter.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)]
|
||||
EnableMoveTiltCharacter,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltMountGround.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)]
|
||||
EnableMoveTiltMountGround,
|
||||
|
||||
/// <summary>
|
||||
/// UiConfig option with the internal name EnableMoveTiltMountFly.
|
||||
/// This option is a UInt.
|
||||
/// </summary>
|
||||
[GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)]
|
||||
EnableMoveTiltMountFly,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,214 +92,232 @@ public enum FlyTextKind : int
|
|||
/// </summary>
|
||||
IslandExp = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
||||
/// Added in 7.2, usage currently unknown.
|
||||
/// </summary>
|
||||
Unknown16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Added in 7.2, usage currently unknown.
|
||||
/// </summary>
|
||||
Unknown17 = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Added in 7.2, usage currently unknown.
|
||||
/// </summary>
|
||||
Unknown18 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
MpDrain = 16,
|
||||
MpDrain = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 17,
|
||||
NamedTp = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
Healing = 18,
|
||||
Healing = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
MpRegen = 19,
|
||||
MpRegen = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp2 = 20,
|
||||
NamedTp2 = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
EpRegen = 21,
|
||||
EpRegen = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
CpRegen = 22,
|
||||
CpRegen = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
GpRegen = 23,
|
||||
GpRegen = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
/// </summary>
|
||||
None = 24,
|
||||
None = 27,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif INVULNERABLE.
|
||||
/// </summary>
|
||||
Invulnerable = 25,
|
||||
Invulnerable = 28,
|
||||
|
||||
/// <summary>
|
||||
/// All caps sans-serif condensed font INTERRUPTED!
|
||||
/// Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
Interrupted = 26,
|
||||
Interrupted = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
CraftingProgress = 27,
|
||||
CraftingProgress = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
CraftingQuality = 28,
|
||||
CraftingQuality = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CraftingQualityCrit = 29,
|
||||
CraftingQualityCrit = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 30,
|
||||
AutoAttackNoText3 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
HealingCrit = 31,
|
||||
HealingCrit = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Same as DamageCrit with a MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 32,
|
||||
NamedCriticalHitWithMp = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Same as DamageCrit with a TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 33,
|
||||
NamedCriticalHitWithTp = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
DebuffNoEffect = 34,
|
||||
DebuffNoEffect = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif slightly faded Text1.
|
||||
/// </summary>
|
||||
BuffFading = 35,
|
||||
BuffFading = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif slightly faded Text1.
|
||||
/// </summary>
|
||||
DebuffFading = 36,
|
||||
DebuffFading = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
/// </summary>
|
||||
Named = 37,
|
||||
Named = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
DebuffResisted = 38,
|
||||
DebuffResisted = 41,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
/// </summary>
|
||||
Incapacitated = 39,
|
||||
Incapacitated = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
FullyResisted = 40,
|
||||
FullyResisted = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
HasNoEffect = 41,
|
||||
HasNoEffect = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
HpDrain = 42,
|
||||
HpDrain = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp3 = 43,
|
||||
NamedMp3 = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp3 = 44,
|
||||
NamedTp3 = 47,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
DebuffInvulnerable = 45,
|
||||
DebuffInvulnerable = 48,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
/// </summary>
|
||||
Resist = 46,
|
||||
Resist = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Icon with an item icon outline next to sans-serif Text1.
|
||||
/// </summary>
|
||||
LootedItem = 47,
|
||||
LootedItem = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
Collectability = 48,
|
||||
Collectability = 51,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CollectabilityCrit = 49,
|
||||
CollectabilityCrit = 52,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
/// </summary>
|
||||
Reflect = 50,
|
||||
Reflect = 53,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECTED.
|
||||
/// </summary>
|
||||
Reflected = 51,
|
||||
Reflected = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
CraftingQualityDh = 52,
|
||||
CraftingQualityDh = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 53,
|
||||
CriticalHit4 = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle.
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CraftingQualityCritDh = 54,
|
||||
CraftingQualityCritDh = 57,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
private void HandleActionHoverDetour(AgentActionDetail* hoverState, FFXIVClientStructs.FFXIV.Client.UI.Agent.ActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
{
|
||||
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||
this.HoveredAction.ActionKind = (HoverActionKind)actionKind;
|
||||
|
|
|
|||
|
|
@ -55,10 +55,16 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
|||
/// </summary>
|
||||
public int Quantity => this.InternalItem.Quantity;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spiritbond or collectability of this item.
|
||||
/// </summary>
|
||||
public uint SpiritbondOrCollectability => this.InternalItem.SpiritbondOrCollectability;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spiritbond of this item.
|
||||
/// </summary>
|
||||
public uint Spiritbond => this.InternalItem.Spiritbond;
|
||||
[Obsolete($"Renamed to {nameof(SpiritbondOrCollectability)}", true)]
|
||||
public uint Spiritbond => this.SpiritbondOrCollectability;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the repair condition of this item.
|
||||
|
|
|
|||
|
|
@ -565,7 +565,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
return this.configuration.IsMbCollect;
|
||||
}
|
||||
|
||||
private void MarketPurchasePacketDetour(PacketDispatcher* a1, nint packetData)
|
||||
private void MarketPurchasePacketDetour(uint targetId, nint packetData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -576,7 +576,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketPurchasePacketHandler threw an exception");
|
||||
}
|
||||
|
||||
this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData);
|
||||
this.mbPurchaseHook.OriginalDisposeSafe(targetId, packetData);
|
||||
}
|
||||
|
||||
private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData)
|
||||
|
|
@ -609,7 +609,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount);
|
||||
}
|
||||
|
||||
private void MarketItemRequestStartDetour(PacketDispatcher* a1, nint packetRef)
|
||||
private void MarketItemRequestStartDetour(uint targetId, nint packetRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -620,7 +620,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketItemRequestStartDetour threw an exception");
|
||||
}
|
||||
|
||||
this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef);
|
||||
this.mbItemRequestStartHook.OriginalDisposeSafe(targetId, packetRef);
|
||||
}
|
||||
|
||||
private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Lumina.Text;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps payloads in an open and close icon, for example the Auto Translation open/close brackets.
|
||||
/// </summary>
|
||||
internal readonly struct SeStringBuilderIconWrap : IDisposable
|
||||
{
|
||||
private readonly SeStringBuilder builder;
|
||||
private readonly uint iconClose;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringBuilderIconWrap"/> struct.<br/>
|
||||
/// Appends an icon macro with <paramref name="iconOpen"/> on creation, and an icon macro with
|
||||
/// <paramref name="iconClose"/> on disposal.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to use.</param>
|
||||
/// <param name="iconOpen">The open icon id.</param>
|
||||
/// <param name="iconClose">The close icon id.</param>
|
||||
public SeStringBuilderIconWrap(SeStringBuilder builder, uint iconOpen, uint iconClose)
|
||||
{
|
||||
this.builder = builder;
|
||||
this.iconClose = iconClose;
|
||||
this.builder.AppendIcon(iconOpen);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.builder.AppendIcon(this.iconClose);
|
||||
}
|
||||
83
Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs
Normal file
83
Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System.Globalization;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A context wrapper used in <see cref="SeStringEvaluator"/>.
|
||||
/// </summary>
|
||||
internal readonly ref struct SeStringContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SeStringBuilder"/> to append text and macros to.
|
||||
/// </summary>
|
||||
internal readonly SeStringBuilder Builder;
|
||||
|
||||
/// <summary>
|
||||
/// A list of local parameters.
|
||||
/// </summary>
|
||||
internal readonly Span<SeStringParameter> LocalParameters;
|
||||
|
||||
/// <summary>
|
||||
/// The target language, used for sheet lookups.
|
||||
/// </summary>
|
||||
internal readonly ClientLanguage Language;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringContext"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="SeStringBuilder"/> to append text and macros to.</param>
|
||||
/// <param name="localParameters">A list of local parameters.</param>
|
||||
/// <param name="language">The target language, used for sheet lookups.</param>
|
||||
internal SeStringContext(SeStringBuilder builder, Span<SeStringParameter> localParameters, ClientLanguage language)
|
||||
{
|
||||
this.Builder = builder;
|
||||
this.LocalParameters = localParameters;
|
||||
this.Language = language;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Globalization.CultureInfo"/> of the current target <see cref="Language"/>.
|
||||
/// </summary>
|
||||
internal CultureInfo CultureInfo => Localization.GetCultureInfoFromLangCode(this.Language.ToCode());
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a number from the local parameters at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index in the <see cref="LocalParameters"/> list.</param>
|
||||
/// <param name="value">The local parameter number.</param>
|
||||
/// <returns><c>true</c> if the local parameters list contained a parameter at given index, <c>false</c> otherwise.</returns>
|
||||
internal bool TryGetLNum(int index, out uint value)
|
||||
{
|
||||
if (index >= 0 && this.LocalParameters.Length > index)
|
||||
{
|
||||
value = this.LocalParameters[index].UIntValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a string from the local parameters at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index in the <see cref="LocalParameters"/> list.</param>
|
||||
/// <param name="value">The local parameter string.</param>
|
||||
/// <returns><c>true</c> if the local parameters list contained a parameter at given index, <c>false</c> otherwise.</returns>
|
||||
internal bool TryGetLStr(int index, out ReadOnlySeString value)
|
||||
{
|
||||
if (index >= 0 && this.LocalParameters.Length > index)
|
||||
{
|
||||
value = this.LocalParameters[index].StringValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
49
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs
Normal file
49
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// An enum providing additional information about the sheet redirect.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum SheetRedirectFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a sheet related with items.
|
||||
/// </summary>
|
||||
Item = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to the EventItem sheet.
|
||||
/// </summary>
|
||||
EventItem = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a high quality item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append Addon#9.
|
||||
/// </remarks>
|
||||
HighQuality = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a collectible item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append Addon#150.
|
||||
/// </remarks>
|
||||
Collectible = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a sheet related with actions.
|
||||
/// </summary>
|
||||
Action = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to the Action sheet.
|
||||
/// </summary>
|
||||
ActionSheet = 32,
|
||||
}
|
||||
233
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
Normal file
233
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Extensions;
|
||||
|
||||
using ItemKind = Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload.ItemKind;
|
||||
using LSheets = Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A service to resolve sheet redirects in expressions.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class SheetRedirectResolver : IServiceType
|
||||
{
|
||||
private static readonly (string SheetName, uint ColumnIndex, bool ReturnActionSheetFlag)[] ActStrSheets =
|
||||
[
|
||||
(nameof(LSheets.Trait), 0, false),
|
||||
(nameof(LSheets.Action), 0, true),
|
||||
(nameof(LSheets.Item), 0, false),
|
||||
(nameof(LSheets.EventItem), 0, false),
|
||||
(nameof(LSheets.EventAction), 0, false),
|
||||
(nameof(LSheets.GeneralAction), 0, false),
|
||||
(nameof(LSheets.BuddyAction), 0, false),
|
||||
(nameof(LSheets.MainCommand), 5, false),
|
||||
(nameof(LSheets.Companion), 0, false),
|
||||
(nameof(LSheets.CraftAction), 0, false),
|
||||
(nameof(LSheets.Action), 0, true),
|
||||
(nameof(LSheets.PetAction), 0, false),
|
||||
(nameof(LSheets.CompanyAction), 0, false),
|
||||
(nameof(LSheets.Mount), 0, false),
|
||||
(string.Empty, 0, false),
|
||||
(string.Empty, 0, false),
|
||||
(string.Empty, 0, false),
|
||||
(string.Empty, 0, false),
|
||||
(string.Empty, 0, false),
|
||||
(nameof(LSheets.BgcArmyAction), 1, false),
|
||||
(nameof(LSheets.Ornament), 8, false),
|
||||
];
|
||||
|
||||
private static readonly string[] ObjStrSheetNames =
|
||||
[
|
||||
nameof(LSheets.BNpcName),
|
||||
nameof(LSheets.ENpcResident),
|
||||
nameof(LSheets.Treasure),
|
||||
nameof(LSheets.Aetheryte),
|
||||
nameof(LSheets.GatheringPointName),
|
||||
nameof(LSheets.EObjName),
|
||||
nameof(LSheets.Mount),
|
||||
nameof(LSheets.Companion),
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
nameof(LSheets.Item),
|
||||
];
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private SheetRedirectResolver()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the sheet redirect, if any is present.
|
||||
/// </summary>
|
||||
/// <param name="sheetName">The sheet name.</param>
|
||||
/// <param name="rowId">The row id.</param>
|
||||
/// <param name="colIndex">The column index. Use <c>ushort.MaxValue</c> as default.</param>
|
||||
/// <returns>Flags giving additional information about the redirect.</returns>
|
||||
internal SheetRedirectFlags Resolve(ref string sheetName, ref uint rowId, ref uint colIndex)
|
||||
{
|
||||
var flags = SheetRedirectFlags.None;
|
||||
|
||||
switch (sheetName)
|
||||
{
|
||||
case nameof(LSheets.Item) or "ItemHQ" or "ItemMP":
|
||||
{
|
||||
flags |= SheetRedirectFlags.Item;
|
||||
|
||||
var (itemId, kind) = ItemUtil.GetBaseId(rowId);
|
||||
|
||||
if (kind == ItemKind.Hq || sheetName == "ItemHQ")
|
||||
{
|
||||
flags |= SheetRedirectFlags.HighQuality;
|
||||
}
|
||||
else if (kind == ItemKind.Collectible || sheetName == "ItemMP")
|
||||
{
|
||||
// MP for Masterpiece?!
|
||||
flags |= SheetRedirectFlags.Collectible;
|
||||
}
|
||||
|
||||
if (kind == ItemKind.EventItem &&
|
||||
rowId - 2_000_000 <= this.dataManager.GetExcelSheet<LSheets.EventItem>().Count)
|
||||
{
|
||||
flags |= SheetRedirectFlags.EventItem;
|
||||
sheetName = nameof(LSheets.EventItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
sheetName = nameof(LSheets.Item);
|
||||
rowId = itemId;
|
||||
}
|
||||
|
||||
if (colIndex is >= 4 and <= 7)
|
||||
return SheetRedirectFlags.None;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "ActStr":
|
||||
{
|
||||
var returnActionSheetFlag = false;
|
||||
(var index, rowId) = uint.DivRem(rowId, 1000000);
|
||||
if (index < ActStrSheets.Length)
|
||||
(sheetName, colIndex, returnActionSheetFlag) = ActStrSheets[index];
|
||||
|
||||
if (sheetName != nameof(LSheets.Companion) && colIndex != 13)
|
||||
flags |= SheetRedirectFlags.Action;
|
||||
|
||||
if (returnActionSheetFlag)
|
||||
flags |= SheetRedirectFlags.ActionSheet;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "ObjStr":
|
||||
{
|
||||
(var index, rowId) = uint.DivRem(rowId, 1000000);
|
||||
if (index < ObjStrSheetNames.Length)
|
||||
sheetName = ObjStrSheetNames[index];
|
||||
|
||||
colIndex = 0;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case 0: // BNpcName
|
||||
if (rowId >= 100000)
|
||||
rowId += 900000;
|
||||
break;
|
||||
|
||||
case 1: // ENpcResident
|
||||
rowId += 1000000;
|
||||
break;
|
||||
|
||||
case 2: // Treasure
|
||||
if (this.dataManager.GetExcelSheet<LSheets.Treasure>().TryGetRow(rowId, out var treasureRow) &&
|
||||
treasureRow.Unknown0.IsEmpty)
|
||||
rowId = 0; // defaulting to "Treasure Coffer"
|
||||
break;
|
||||
|
||||
case 3: // Aetheryte
|
||||
rowId = this.dataManager.GetExcelSheet<LSheets.Aetheryte>()
|
||||
.TryGetRow(rowId, out var aetheryteRow) && aetheryteRow.IsAetheryte
|
||||
? 0u // "Aetheryte"
|
||||
: 1; // "Aethernet Shard"
|
||||
break;
|
||||
|
||||
case 5: // EObjName
|
||||
rowId += 2000000;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.EObj) when colIndex is <= 7 or ushort.MaxValue:
|
||||
sheetName = nameof(LSheets.EObjName);
|
||||
break;
|
||||
|
||||
case nameof(LSheets.Treasure)
|
||||
when this.dataManager.GetExcelSheet<LSheets.Treasure>().TryGetRow(rowId, out var treasureRow) &&
|
||||
treasureRow.Unknown0.IsEmpty:
|
||||
rowId = 0; // defaulting to "Treasure Coffer"
|
||||
break;
|
||||
|
||||
case "WeatherPlaceName":
|
||||
{
|
||||
sheetName = nameof(LSheets.PlaceName);
|
||||
|
||||
var placeNameSubId = rowId;
|
||||
if (this.dataManager.GetExcelSheet<LSheets.WeatherReportReplace>().TryGetFirst(
|
||||
r => r.PlaceNameSub.RowId == placeNameSubId,
|
||||
out var row))
|
||||
rowId = row.PlaceNameParent.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.InstanceContent) when colIndex == 3:
|
||||
{
|
||||
sheetName = nameof(LSheets.ContentFinderCondition);
|
||||
colIndex = 43;
|
||||
|
||||
if (this.dataManager.GetExcelSheet<LSheets.InstanceContent>().TryGetRow(rowId, out var row))
|
||||
rowId = row.ContentFinderCondition.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.PartyContent) when colIndex == 2:
|
||||
{
|
||||
sheetName = nameof(LSheets.ContentFinderCondition);
|
||||
colIndex = 43;
|
||||
|
||||
if (this.dataManager.GetExcelSheet<LSheets.PartyContent>().TryGetRow(rowId, out var row))
|
||||
rowId = row.ContentFinderCondition.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.PublicContent) when colIndex == 3:
|
||||
{
|
||||
sheetName = nameof(LSheets.ContentFinderCondition);
|
||||
colIndex = 43;
|
||||
|
||||
if (this.dataManager.GetExcelSheet<LSheets.PublicContent>().TryGetRow(rowId, out var row))
|
||||
rowId = row.ContentFinderCondition.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.AkatsukiNote):
|
||||
{
|
||||
sheetName = nameof(LSheets.AkatsukiNoteString);
|
||||
colIndex = 0;
|
||||
|
||||
if (this.dataManager.Excel.GetSubrowSheet<LSheets.AkatsukiNote>().TryGetRow(rowId, out var row))
|
||||
rowId = (uint)row[0].Unknown2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
1995
Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
Normal file
1995
Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
Normal file
File diff suppressed because it is too large
Load diff
79
Dalamud/Game/Text/Evaluator/SeStringParameter.cs
Normal file
79
Dalamud/Game/Text/Evaluator/SeStringParameter.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Globalization;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||
using LSeString = Lumina.Text.SeString;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper for a local parameter, holding either a number or a string.
|
||||
/// </summary>
|
||||
public readonly struct SeStringParameter
|
||||
{
|
||||
private readonly uint num;
|
||||
private readonly ReadOnlySeString str;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a number parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The number value.</param>
|
||||
public SeStringParameter(uint value)
|
||||
{
|
||||
this.num = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a string parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value.</param>
|
||||
public SeStringParameter(ReadOnlySeString value)
|
||||
{
|
||||
this.str = value;
|
||||
this.IsString = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a string parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value.</param>
|
||||
public SeStringParameter(string value)
|
||||
{
|
||||
this.str = new ReadOnlySeString(value);
|
||||
this.IsString = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the backing type of this parameter is a string.
|
||||
/// </summary>
|
||||
public bool IsString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a numeric value.
|
||||
/// </summary>
|
||||
public uint UIntValue =>
|
||||
!this.IsString
|
||||
? this.num
|
||||
: uint.TryParse(this.str.ExtractText(), out var value) ? value : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string value.
|
||||
/// </summary>
|
||||
public ReadOnlySeString StringValue =>
|
||||
this.IsString ? this.str : new(this.num.ToString("D", CultureInfo.InvariantCulture));
|
||||
|
||||
public static implicit operator SeStringParameter(int value) => new((uint)value);
|
||||
|
||||
public static implicit operator SeStringParameter(uint value) => new(value);
|
||||
|
||||
public static implicit operator SeStringParameter(ReadOnlySeString value) => new(value);
|
||||
|
||||
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
||||
|
||||
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
|
||||
|
||||
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
|
||||
|
||||
public static implicit operator SeStringParameter(string value) => new(value);
|
||||
}
|
||||
17
Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs
Normal file
17
Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.English"/>.
|
||||
/// </summary>
|
||||
public enum EnglishArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Indefinite article (a, an).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Definite article (the).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
}
|
||||
32
Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs
Normal file
32
Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.French"/>.
|
||||
/// </summary>
|
||||
public enum FrenchArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Indefinite article (une, des).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Definite article (le, la, les).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (mon, mes).
|
||||
/// </summary>
|
||||
PossessiveFirstPerson = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (ton, tes).
|
||||
/// </summary>
|
||||
PossessiveSecondPerson = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (son, ses).
|
||||
/// </summary>
|
||||
PossessiveThirdPerson = 5,
|
||||
}
|
||||
37
Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs
Normal file
37
Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.German"/>.
|
||||
/// </summary>
|
||||
public enum GermanArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Unbestimmter Artikel (ein, eine, etc.).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Bestimmter Artikel (der, die, das, etc.).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Possessivartikel "dein" (dein, deine, etc.).
|
||||
/// </summary>
|
||||
Possessive = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Negativartikel "kein" (kein, keine, etc.).
|
||||
/// </summary>
|
||||
Negative = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Nullartikel.
|
||||
/// </summary>
|
||||
ZeroArticle = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrativpronomen "dieser" (dieser, diese, etc.).
|
||||
/// </summary>
|
||||
Demonstrative = 6,
|
||||
}
|
||||
17
Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs
Normal file
17
Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.Japanese"/>.
|
||||
/// </summary>
|
||||
public enum JapaneseArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Near listener (それら).
|
||||
/// </summary>
|
||||
NearListener = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Distant from both speaker and listener (あれら).
|
||||
/// </summary>
|
||||
Distant = 2,
|
||||
}
|
||||
73
Dalamud/Game/Text/Noun/NounParams.cs
Normal file
73
Dalamud/Game/Text/Noun/NounParams.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSheets = Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Text.Noun;
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for noun processing.
|
||||
/// </summary>
|
||||
internal record struct NounParams()
|
||||
{
|
||||
/// <summary>
|
||||
/// The language of the sheet to be processed.
|
||||
/// </summary>
|
||||
public required ClientLanguage Language;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the sheet containing the row to process.
|
||||
/// </summary>
|
||||
public required string SheetName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The row id within the sheet to process.
|
||||
/// </summary>
|
||||
public required uint RowId;
|
||||
|
||||
/// <summary>
|
||||
/// The quantity of the entity (default is 1). Used to determine grammatical number (e.g., singular or plural).
|
||||
/// </summary>
|
||||
public int Quantity = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The article type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Depending on the <see cref="Language"/>, this has different meanings.<br/>
|
||||
/// See <see cref="JapaneseArticleType"/>, <see cref="GermanArticleType"/>, <see cref="FrenchArticleType"/>, <see cref="EnglishArticleType"/>.
|
||||
/// </remarks>
|
||||
public int ArticleType = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The grammatical case (e.g., Nominative, Genitive, Dative, Accusative) used for German texts (default is 0).
|
||||
/// </summary>
|
||||
public int GrammaticalCase = 0;
|
||||
|
||||
/// <summary>
|
||||
/// An optional string that is placed in front of the text that should be linked, such as item names (default is an empty string; the game uses "//").
|
||||
/// </summary>
|
||||
public ReadOnlySeString LinkMarker = default;
|
||||
|
||||
/// <summary>
|
||||
/// An indicator that this noun will be processed from an Action sheet. Only used for German texts.
|
||||
/// </summary>
|
||||
public bool IsActionSheet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the column offset.
|
||||
/// </summary>
|
||||
public readonly int ColumnOffset => this.SheetName switch
|
||||
{
|
||||
// See "E8 ?? ?? ?? ?? 44 8B 6B 08"
|
||||
nameof(LSheets.BeastTribe) => 10,
|
||||
nameof(LSheets.DeepDungeonItem) => 1,
|
||||
nameof(LSheets.DeepDungeonEquipment) => 1,
|
||||
nameof(LSheets.DeepDungeonMagicStone) => 1,
|
||||
nameof(LSheets.DeepDungeonDemiclone) => 1,
|
||||
nameof(LSheets.Glasses) => 4,
|
||||
nameof(LSheets.GlassesStyle) => 15,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
461
Dalamud/Game/Text/Noun/NounProcessor.cs
Normal file
461
Dalamud/Game/Text/Noun/NounProcessor.cs
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.Noun.Enums;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using LSheets = Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Text.Noun;
|
||||
|
||||
/*
|
||||
Attributive sheet:
|
||||
Japanese:
|
||||
Unknown0 = Singular Demonstrative
|
||||
Unknown1 = Plural Demonstrative
|
||||
English:
|
||||
Unknown2 = Article before a singular noun beginning with a consonant sound
|
||||
Unknown3 = Article before a generic noun beginning with a consonant sound
|
||||
Unknown4 = N/A
|
||||
Unknown5 = Article before a singular noun beginning with a vowel sound
|
||||
Unknown6 = Article before a generic noun beginning with a vowel sound
|
||||
Unknown7 = N/A
|
||||
German:
|
||||
Unknown8 = Nominative Masculine
|
||||
Unknown9 = Nominative Feminine
|
||||
Unknown10 = Nominative Neutral
|
||||
Unknown11 = Nominative Plural
|
||||
Unknown12 = Genitive Masculine
|
||||
Unknown13 = Genitive Feminine
|
||||
Unknown14 = Genitive Neutral
|
||||
Unknown15 = Genitive Plural
|
||||
Unknown16 = Dative Masculine
|
||||
Unknown17 = Dative Feminine
|
||||
Unknown18 = Dative Neutral
|
||||
Unknown19 = Dative Plural
|
||||
Unknown20 = Accusative Masculine
|
||||
Unknown21 = Accusative Feminine
|
||||
Unknown22 = Accusative Neutral
|
||||
Unknown23 = Accusative Plural
|
||||
French (unsure):
|
||||
Unknown24 = Singular Article
|
||||
Unknown25 = Singular Masculine Article
|
||||
Unknown26 = Plural Masculine Article
|
||||
Unknown27 = ?
|
||||
Unknown28 = ?
|
||||
Unknown29 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown30 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown31 = ?
|
||||
Unknown32 = ?
|
||||
Unknown33 = Singular Feminine Article
|
||||
Unknown34 = Plural Feminine Article
|
||||
Unknown35 = ?
|
||||
Unknown36 = ?
|
||||
Unknown37 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown38 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown39 = ?
|
||||
Unknown40 = ?
|
||||
|
||||
Placeholders:
|
||||
[t] = article or grammatical gender (EN: the, DE: der, die, das)
|
||||
[n] = amount (number)
|
||||
[a] = declension
|
||||
[p] = plural
|
||||
[pa] = ?
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to process texts from sheets containing grammatical placeholders.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NounProcessor : IServiceType
|
||||
{
|
||||
// column names from ExdSchema, most likely incorrect
|
||||
private const int SingularColumnIdx = 0;
|
||||
private const int AdjectiveColumnIdx = 1;
|
||||
private const int PluralColumnIdx = 2;
|
||||
private const int PossessivePronounColumnIdx = 3;
|
||||
private const int StartsWithVowelColumnIdx = 4;
|
||||
private const int Unknown5ColumnIdx = 5; // probably used in Chinese texts
|
||||
private const int PronounColumnIdx = 6;
|
||||
private const int ArticleColumnIdx = 7;
|
||||
|
||||
private static readonly ModuleLog Log = new("NounProcessor");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private readonly ConcurrentDictionary<NounParams, ReadOnlySeString> cache = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NounProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a specific row from a sheet and generates a formatted string based on grammatical and language-specific rules.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
public ReadOnlySeString ProcessNoun(NounParams nounParams)
|
||||
{
|
||||
if (nounParams.GrammaticalCase < 0 || nounParams.GrammaticalCase > 5)
|
||||
return default;
|
||||
|
||||
if (this.cache.TryGetValue(nounParams, out var value))
|
||||
return value;
|
||||
|
||||
var output = nounParams.Language switch
|
||||
{
|
||||
ClientLanguage.Japanese => this.ResolveNounJa(nounParams),
|
||||
ClientLanguage.English => this.ResolveNounEn(nounParams),
|
||||
ClientLanguage.German => this.ResolveNounDe(nounParams),
|
||||
ClientLanguage.French => this.ResolveNounFr(nounParams),
|
||||
_ => default,
|
||||
};
|
||||
|
||||
this.cache.TryAdd(nounParams, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in Japanese text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounJa.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounJa(NounParams nounParams)
|
||||
{
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
// Ko-So-A-Do
|
||||
var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0);
|
||||
if (!ksad.IsEmpty)
|
||||
{
|
||||
builder.Append(ksad);
|
||||
|
||||
if (nounParams.Quantity > 1)
|
||||
{
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
var ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in English text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounEn.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounEn(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = StartsWithVowelColumnIdx
|
||||
a1->Offsets[3] = PossessivePronounColumnIdx
|
||||
a1->Offsets[4] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn;
|
||||
if (isProperNoun == 0)
|
||||
{
|
||||
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
||||
var startsWithVowel = startsWithVowelColumn >= 0
|
||||
? row.ReadInt8Column(startsWithVowelColumn)
|
||||
: ~startsWithVowelColumn;
|
||||
|
||||
var articleColumn = startsWithVowel + (2 * (startsWithVowel + 1));
|
||||
var grammaticalNumberColumnOffset = nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx;
|
||||
var article = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||
.ReadStringColumn(articleColumn + grammaticalNumberColumnOffset);
|
||||
if (!article.IsEmpty)
|
||||
builder.Append(article);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
}
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
var ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in German text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounDe.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounDe(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = PronounColumnIdx
|
||||
a1->Offsets[3] = AdjectiveColumnIdx
|
||||
a1->Offsets[4] = PossessivePronounColumnIdx
|
||||
a1->Offsets[5] = Unknown5ColumnIdx
|
||||
a1->Offsets[6] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
ReadOnlySeString ross;
|
||||
|
||||
if (nounParams.IsActionSheet)
|
||||
{
|
||||
builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase));
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
||||
var genderIndex = genderIndexColumn >= 0 ? row.ReadInt8Column(genderIndexColumn) : ~genderIndexColumn;
|
||||
|
||||
var articleIndexColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var articleIndex = articleIndexColumn >= 0 ? row.ReadInt8Column(articleIndexColumn) : ~articleIndexColumn;
|
||||
|
||||
var caseColumnOffset = (4 * nounParams.GrammaticalCase) + 8;
|
||||
|
||||
var caseRowOffsetColumn = nounParams.ColumnOffset + (nounParams.Quantity == 1 ? AdjectiveColumnIdx : PossessivePronounColumnIdx);
|
||||
var caseRowOffset = caseRowOffsetColumn >= 0
|
||||
? row.ReadInt8Column(caseRowOffsetColumn)
|
||||
: (sbyte)~caseRowOffsetColumn;
|
||||
|
||||
if (nounParams.Quantity != 1)
|
||||
genderIndex = 3;
|
||||
|
||||
var hasT = false;
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
{
|
||||
hasT = text.ContainsText("[t]"u8);
|
||||
|
||||
if (articleIndex == 0 && !hasT)
|
||||
{
|
||||
var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||
.ReadStringColumn(caseColumnOffset + genderIndex); // Genus
|
||||
if (!grammaticalGender.IsEmpty)
|
||||
builder.Append(grammaticalGender);
|
||||
}
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
builder.Append(text);
|
||||
|
||||
var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26))
|
||||
.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
if (builder.ContainsText("[p]"u8))
|
||||
builder.ReplaceText("[p]"u8, plural);
|
||||
else
|
||||
builder.Append(plural);
|
||||
|
||||
if (hasT)
|
||||
{
|
||||
var article =
|
||||
attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel
|
||||
builder.ReplaceText("[t]"u8, article);
|
||||
}
|
||||
}
|
||||
|
||||
var pa = attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
builder.ReplaceText("[pa]"u8, pa);
|
||||
|
||||
RawRow declensionRow;
|
||||
|
||||
declensionRow = (GermanArticleType)nounParams.ArticleType switch
|
||||
{
|
||||
// Schwache Flexion eines Adjektivs?!
|
||||
GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25),
|
||||
_ when hasT => attributiveSheet.GetRow(25),
|
||||
|
||||
// Starke Deklination
|
||||
GermanArticleType.ZeroArticle => attributiveSheet.GetRow(38),
|
||||
|
||||
// Gemischte Deklination
|
||||
GermanArticleType.Definite => attributiveSheet.GetRow(37),
|
||||
|
||||
// Starke Flexion eines Artikels?!
|
||||
GermanArticleType.Indefinite or GermanArticleType.Negative => attributiveSheet.GetRow(26),
|
||||
_ => attributiveSheet.GetRow(26),
|
||||
};
|
||||
|
||||
var declension = declensionRow.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
builder.ReplaceText("[a]"u8, declension);
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in French text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounFr.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounFr(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = StartsWithVowelColumnIdx
|
||||
a1->Offsets[3] = PronounColumnIdx
|
||||
a1->Offsets[4] = Unknown5ColumnIdx
|
||||
a1->Offsets[5] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
ReadOnlySeString ross;
|
||||
|
||||
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
||||
var startsWithVowel = startsWithVowelColumn >= 0
|
||||
? row.ReadInt8Column(startsWithVowelColumn)
|
||||
: ~startsWithVowelColumn;
|
||||
|
||||
var pronounColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
||||
var pronoun = pronounColumn >= 0 ? row.ReadInt8Column(pronounColumn) : ~pronounColumn;
|
||||
|
||||
var articleColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var article = articleColumn >= 0 ? row.ReadInt8Column(articleColumn) : ~articleColumn;
|
||||
|
||||
var v20 = 4 * (startsWithVowel + 6 + (2 * pronoun));
|
||||
|
||||
if (article != 0)
|
||||
{
|
||||
var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20);
|
||||
if (!v21.IsEmpty)
|
||||
builder.Append(v21);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
if (nounParams.Quantity <= 1)
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx);
|
||||
if (v17 != 0 && (nounParams.Quantity > 1 || v17 == 2))
|
||||
{
|
||||
var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2);
|
||||
if (!v29.IsEmpty)
|
||||
{
|
||||
builder.Append(v29);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3));
|
||||
if (!v27.IsEmpty)
|
||||
builder.Append(v27);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
}
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ using System.Linq;
|
|||
using System.Text;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -73,6 +75,7 @@ public class ItemPayload : Payload
|
|||
/// <summary>
|
||||
/// Kinds of items that can be fetched from this payload.
|
||||
/// </summary>
|
||||
[Api12ToDo("Move this out of ItemPayload. It's used in other classes too.")]
|
||||
public enum ItemKind : uint
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -121,7 +124,7 @@ public class ItemPayload : Payload
|
|||
/// Gets the actual item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ItemId => GetAdjustedId(this.rawItemId).ItemId;
|
||||
public uint ItemId => ItemUtil.GetBaseId(this.rawItemId).ItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw, unadjusted item ID of this payload.
|
||||
|
|
@ -161,7 +164,7 @@ public class ItemPayload : Payload
|
|||
/// <returns>The created item payload.</returns>
|
||||
public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null)
|
||||
{
|
||||
var (id, kind) = GetAdjustedId(rawItemId);
|
||||
var (id, kind) = ItemUtil.GetBaseId(rawItemId);
|
||||
return new ItemPayload(id, kind, displayNameOverride);
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +233,7 @@ public class ItemPayload : Payload
|
|||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.rawItemId = GetInteger(reader);
|
||||
this.Kind = GetAdjustedId(this.rawItemId).Kind;
|
||||
this.Kind = ItemUtil.GetBaseId(this.rawItemId).Kind;
|
||||
|
||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
||||
{
|
||||
|
|
@ -255,15 +258,4 @@ public class ItemPayload : Payload
|
|||
this.displayName = Encoding.UTF8.GetString(itemNameBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId)
|
||||
{
|
||||
return rawItemId switch
|
||||
{
|
||||
> 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible),
|
||||
> 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq),
|
||||
> 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted
|
||||
_ => (rawItemId, ItemKind.Normal),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ using Newtonsoft.Json;
|
|||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a UI foreground color applied to following text payloads.
|
||||
/// An SeString Payload that allows text to have a specific color. The color selected will be determined by the
|
||||
/// <see cref="Lumina.Excel.Sheets.UIColor.Dark"/> theme's coloring, regardless of the active theme.
|
||||
/// </summary>
|
||||
public class UIForegroundPayload : Payload
|
||||
{
|
||||
|
|
@ -74,13 +75,13 @@ public class UIForegroundPayload : Payload
|
|||
/// Gets the Red/Green/Blue/Alpha values for this foreground color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGBA => this.UIColor.Value.UIForeground;
|
||||
public uint RGBA => this.UIColor.Value.Dark;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ABGR value for this foreground color, as ImGui requires it in PushColor.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIForeground);
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Dark);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ using Newtonsoft.Json;
|
|||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a UI glow color applied to following text payloads.
|
||||
/// An SeString Payload that allows text to have a specific edge glow. The color selected will be determined by the
|
||||
/// <see cref="Lumina.Excel.Sheets.UIColor.Light"/> theme's coloring, regardless of the active theme.
|
||||
/// </summary>
|
||||
public class UIGlowPayload : Payload
|
||||
{
|
||||
|
|
@ -71,13 +72,13 @@ public class UIGlowPayload : Payload
|
|||
/// Gets the Red/Green/Blue/Alpha values for this glow color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGBA => this.UIColor.Value.UIGlow;
|
||||
public uint RGBA => this.UIColor.Value.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ABGR value for this glow color, as ImGui requires it in PushColor.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIGlow);
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Light);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow.
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@ using System.Runtime.InteropServices;
|
|||
using System.Text;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.Evaluator;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -187,57 +192,32 @@ public class SeString
|
|||
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
|
||||
public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null)
|
||||
{
|
||||
var data = Service<DataManager>.Get();
|
||||
var clientState = Service<ClientState.ClientState>.Get();
|
||||
var seStringEvaluator = Service<SeStringEvaluator>.Get();
|
||||
|
||||
var displayName = displayNameOverride;
|
||||
var rarity = 1; // default: white
|
||||
if (displayName == null)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case ItemPayload.ItemKind.Normal:
|
||||
case ItemPayload.ItemKind.Collectible:
|
||||
case ItemPayload.ItemKind.Hq:
|
||||
var item = data.GetExcelSheet<Item>()?.GetRowOrDefault(itemId);
|
||||
displayName = item?.Name.ExtractText();
|
||||
rarity = item?.Rarity ?? 1;
|
||||
break;
|
||||
case ItemPayload.ItemKind.EventItem:
|
||||
displayName = data.GetExcelSheet<EventItem>()?.GetRowOrDefault(itemId)?.Name.ExtractText();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||
}
|
||||
}
|
||||
var rawId = ItemUtil.GetRawId(itemId, kind);
|
||||
|
||||
if (displayName == null)
|
||||
{
|
||||
var displayName = displayNameOverride ?? ItemUtil.GetItemName(rawId);
|
||||
if (displayName.IsEmpty)
|
||||
throw new Exception("Invalid item ID specified, could not determine item name.");
|
||||
}
|
||||
|
||||
if (kind == ItemPayload.ItemKind.Hq)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.HighQuality}";
|
||||
}
|
||||
else if (kind == ItemPayload.ItemKind.Collectible)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.Collectible}";
|
||||
}
|
||||
var copyName = ItemUtil.GetItemName(rawId, false).ExtractText();
|
||||
var textColor = ItemUtil.GetItemRarityColorType(rawId);
|
||||
var textEdgeColor = textColor + 1u;
|
||||
|
||||
var textColor = (ushort)(549 + ((rarity - 1) * 2));
|
||||
var textGlowColor = (ushort)(textColor + 1);
|
||||
var sb = LSeStringBuilder.SharedPool.Get();
|
||||
var itemLink = sb
|
||||
.PushColorType(textColor)
|
||||
.PushEdgeColorType(textEdgeColor)
|
||||
.PushLinkItem(rawId, copyName)
|
||||
.Append(displayName)
|
||||
.PopLink()
|
||||
.PopEdgeColorType()
|
||||
.PopColorType()
|
||||
.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(sb);
|
||||
|
||||
// Note: `SeStringBuilder.AddItemLink` uses this function, so don't call it here!
|
||||
return new SeStringBuilder()
|
||||
.AddUiForeground(textColor)
|
||||
.AddUiGlow(textGlowColor)
|
||||
.Add(new ItemPayload(itemId, kind))
|
||||
.Append(TextArrowPayloads)
|
||||
.AddText(displayName)
|
||||
.AddUiGlowOff()
|
||||
.AddUiForegroundOff()
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.Build();
|
||||
return SeString.Parse(seStringEvaluator.EvaluateFromAddon(371, [itemLink], clientState.ClientLanguage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ namespace Dalamud.Interface.Animation;
|
|||
/// <summary>
|
||||
/// Base class facilitating the implementation of easing functions.
|
||||
/// </summary>
|
||||
[Api12ToDo("Re-apply https://github.com/goatcorp/Dalamud/commit/1aada983931d9e45a250eebbc17c8b782d07701b")]
|
||||
public abstract class Easing
|
||||
{
|
||||
// TODO: Use game delta time here instead
|
||||
|
|
@ -46,9 +45,22 @@ public abstract class Easing
|
|||
public bool IsInverse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current value of the animation, from 0 to 1.
|
||||
/// Gets the current value of the animation, following unclamped logic.
|
||||
/// </summary>
|
||||
public double Value
|
||||
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
||||
[Api13ToDo("Map this field to ValueClamped, probably.")]
|
||||
public double Value => this.ValueUnclamped;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the animation, from 0 to 1.
|
||||
/// </summary>
|
||||
public double ValueClamped => Math.Clamp(this.ValueUnclamped, 0, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current value of the animation, not limited to a range of 0 to 1.
|
||||
/// Will return numbers outside of this range if accessed beyond animation time.
|
||||
/// </summary>
|
||||
public double ValueUnclamped
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InCirc : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2));
|
||||
this.ValueUnclamped = 1 - Math.Sqrt(1 - Math.Pow(p, 2));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InCubic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p * p * p;
|
||||
this.ValueUnclamped = p * p * p;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ public class InElastic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant);
|
||||
this.ValueUnclamped = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ public class InOutCirc : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p < 0.5
|
||||
? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2
|
||||
: (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2;
|
||||
this.ValueUnclamped = p < 0.5
|
||||
? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2
|
||||
: (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InOutCubic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2);
|
||||
this.ValueUnclamped = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ public class InOutElastic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: p < 0.5
|
||||
? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2
|
||||
: (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1;
|
||||
this.ValueUnclamped = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: p < 0.5
|
||||
? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2
|
||||
: (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InOutQuint : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2);
|
||||
this.ValueUnclamped = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InOutSine : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = -(Math.Cos(Math.PI * p) - 1) / 2;
|
||||
this.ValueUnclamped = -(Math.Cos(Math.PI * p) - 1) / 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InQuint : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p * p * p * p * p;
|
||||
this.ValueUnclamped = p * p * p * p * p;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class InSine : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = 1 - Math.Cos((p * Math.PI) / 2);
|
||||
this.ValueUnclamped = 1 - Math.Cos((p * Math.PI) / 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class OutCirc : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2));
|
||||
this.ValueUnclamped = Math.Sqrt(1 - Math.Pow(p - 1, 2));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class OutCubic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = 1 - Math.Pow(1 - p, 3);
|
||||
this.ValueUnclamped = 1 - Math.Pow(1 - p, 3);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ public class OutElastic : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1;
|
||||
this.ValueUnclamped = p == 0
|
||||
? 0
|
||||
: p == 1
|
||||
? 1
|
||||
: (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class OutQuint : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = 1 - Math.Pow(1 - p, 5);
|
||||
this.ValueUnclamped = 1 - Math.Pow(1 - p, 5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ public class OutSine : Easing
|
|||
public override void Update()
|
||||
{
|
||||
var p = this.Progress;
|
||||
this.Value = Math.Sin((p * Math.PI) / 2);
|
||||
this.ValueUnclamped = Math.Sin((p * Math.PI) / 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ internal sealed partial class ActiveNotification
|
|||
var opacity =
|
||||
Math.Clamp(
|
||||
(float)(this.hideEasing.IsRunning
|
||||
? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.Value)
|
||||
: (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.Value)),
|
||||
? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.ValueClamped)
|
||||
: (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.ValueClamped)),
|
||||
0f,
|
||||
1f);
|
||||
if (opacity <= 0)
|
||||
|
|
@ -106,7 +106,7 @@ internal sealed partial class ActiveNotification
|
|||
}
|
||||
else if (this.expandoEasing.IsRunning)
|
||||
{
|
||||
var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.Value;
|
||||
var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.ValueClamped;
|
||||
if (this.underlyingNotification.Minimized)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity * (1f - easedValue));
|
||||
else
|
||||
|
|
@ -295,8 +295,8 @@ internal sealed partial class ActiveNotification
|
|||
{
|
||||
relativeOpacity =
|
||||
this.underlyingNotification.Minimized
|
||||
? 1f - (float)this.expandoEasing.Value
|
||||
: (float)this.expandoEasing.Value;
|
||||
? 1f - (float)this.expandoEasing.ValueClamped
|
||||
: (float)this.expandoEasing.ValueClamped;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -543,7 +543,7 @@ internal sealed partial class ActiveNotification
|
|||
float barL, barR;
|
||||
if (this.DismissReason is not null)
|
||||
{
|
||||
var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.Value;
|
||||
var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.ValueClamped;
|
||||
var midpoint = (this.prevProgressL + this.prevProgressR) / 2f;
|
||||
var length = (this.prevProgressR - this.prevProgressL) / 2f;
|
||||
barL = midpoint - (length * v);
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ internal sealed partial class ActiveNotification : IActiveNotification
|
|||
if (Math.Abs(underlyingProgress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone || ReducedMotions)
|
||||
return underlyingProgress;
|
||||
|
||||
var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.Value, 0f, 1f);
|
||||
var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.ValueClamped, 0f, 1f);
|
||||
return this.progressBefore + (state * (underlyingProgress - this.progressBefore));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ internal sealed class SeStringColorStackSet
|
|||
foreach (var row in uiColor)
|
||||
{
|
||||
// Contains ABGR.
|
||||
this.colorTypes[row.RowId, 0] = row.UIForeground;
|
||||
this.colorTypes[row.RowId, 1] = row.UIGlow;
|
||||
this.colorTypes[row.RowId, 2] = row.Unknown0;
|
||||
this.colorTypes[row.RowId, 3] = row.Unknown1;
|
||||
this.colorTypes[row.RowId, 0] = row.Dark;
|
||||
this.colorTypes[row.RowId, 1] = row.Light;
|
||||
this.colorTypes[row.RowId, 2] = row.ClassicFF;
|
||||
this.colorTypes[row.RowId, 3] = row.ClearBlue;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using BitFaster.Caching.Lru;
|
|||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
|
@ -44,9 +43,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
/// of this placeholder. On its own, usually displayed like <c>[OBJ]</c>.</summary>
|
||||
private const int ObjectReplacementCharacter = '\uFFFC';
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameConfig gameConfig = Service<GameConfig>.Get();
|
||||
|
||||
/// <summary>Cache of compiled SeStrings from <see cref="CompileAndCache"/>.</summary>
|
||||
private readonly ConcurrentLru<string, ReadOnlySeString> cache = new(1024);
|
||||
|
||||
|
|
@ -570,70 +566,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
|||
// Apply gamepad key mapping to icons.
|
||||
case MacroCode.Icon2
|
||||
when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId):
|
||||
var configName = (BitmapFontIcon)iconId switch
|
||||
ref var iconMapping = ref RaptureAtkModule.Instance()->AtkFontManager.Icon2RemapTable;
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
ControllerShoulderLeft => SystemConfigOption.PadButton_L1,
|
||||
ControllerShoulderRight => SystemConfigOption.PadButton_R1,
|
||||
ControllerTriggerLeft => SystemConfigOption.PadButton_L2,
|
||||
ControllerTriggerRight => SystemConfigOption.PadButton_R2,
|
||||
ControllerButton3 => SystemConfigOption.PadButton_Triangle,
|
||||
ControllerButton1 => SystemConfigOption.PadButton_Cross,
|
||||
ControllerButton0 => SystemConfigOption.PadButton_Circle,
|
||||
ControllerButton2 => SystemConfigOption.PadButton_Square,
|
||||
ControllerStart => SystemConfigOption.PadButton_Start,
|
||||
ControllerBack => SystemConfigOption.PadButton_Select,
|
||||
ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS,
|
||||
ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS,
|
||||
ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS,
|
||||
ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS,
|
||||
ControllerAnalogRightStick => SystemConfigOption.PadButton_RS,
|
||||
ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS,
|
||||
ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS,
|
||||
ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS,
|
||||
_ => (SystemConfigOption?)null,
|
||||
};
|
||||
|
||||
if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb))
|
||||
return (BitmapFontIcon)iconId;
|
||||
|
||||
return pb switch
|
||||
{
|
||||
PadButtonValue.Autorun_Support => ControllerShoulderLeft,
|
||||
PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight,
|
||||
PadButtonValue.XHB_Left_Start => ControllerTriggerLeft,
|
||||
PadButtonValue.XHB_Right_Start => ControllerTriggerRight,
|
||||
PadButtonValue.Jump => ControllerButton3,
|
||||
PadButtonValue.Accept => ControllerButton0,
|
||||
PadButtonValue.Cancel => ControllerButton1,
|
||||
PadButtonValue.Map_Sub => ControllerButton2,
|
||||
PadButtonValue.MainCommand => ControllerStart,
|
||||
PadButtonValue.HUD_Select => ControllerBack,
|
||||
PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch
|
||||
if (iconMapping[i].IconId == iconId)
|
||||
{
|
||||
ControllerAnalogLeftStick => ControllerAnalogLeftStick,
|
||||
ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn,
|
||||
ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown,
|
||||
ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight,
|
||||
ControllerAnalogRightStick => ControllerAnalogLeftStick,
|
||||
ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn,
|
||||
ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown,
|
||||
ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight,
|
||||
_ => (BitmapFontIcon)iconId,
|
||||
},
|
||||
PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch
|
||||
{
|
||||
ControllerAnalogLeftStick => ControllerAnalogRightStick,
|
||||
ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn,
|
||||
ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown,
|
||||
ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight,
|
||||
ControllerAnalogRightStick => ControllerAnalogRightStick,
|
||||
ControllerAnalogRightStickIn => ControllerAnalogRightStickIn,
|
||||
ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown,
|
||||
ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight,
|
||||
_ => (BitmapFontIcon)iconId,
|
||||
},
|
||||
_ => (BitmapFontIcon)iconId,
|
||||
};
|
||||
return (BitmapFontIcon)iconMapping[i].RemappedIconId;
|
||||
}
|
||||
}
|
||||
|
||||
return (BitmapFontIcon)iconId;
|
||||
}
|
||||
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ internal unsafe class UiDebug
|
|||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button($"Decode##{(ulong)textNode:X}"))
|
||||
textNode->NodeText.SetString(new ReadOnlySeStringSpan(textNode->NodeText.StringPtr).ToString());
|
||||
textNode->NodeText.SetString(textNode->NodeText.StringPtr.AsReadOnlySeStringSpan().ToString());
|
||||
|
||||
ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}");
|
||||
int b = textNode->AlignmentFontType;
|
||||
|
|
|
|||
|
|
@ -76,14 +76,13 @@ public unsafe partial class AddonTree
|
|||
case ValueType.String8:
|
||||
case ValueType.String:
|
||||
{
|
||||
if (atkValue->String == null)
|
||||
if (atkValue->String.Value == null)
|
||||
{
|
||||
ImGui.TextDisabled("null");
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String));
|
||||
Util.ShowStruct(str, (ulong)atkValue);
|
||||
Util.ShowStruct(atkValue->String.ToString(), (ulong)atkValue);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree
|
|||
var seStringBytes = new byte[utf8String.BufUsed];
|
||||
for (var i = 0L; i < utf8String.BufUsed; i++)
|
||||
{
|
||||
seStringBytes[i] = utf8String.StringPtr[i];
|
||||
seStringBytes[i] = utf8String.StringPtr.Value[i];
|
||||
}
|
||||
|
||||
var seString = SeString.Parse(seStringBytes);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ internal sealed class ComponentDemoWindow : Window
|
|||
ImGui.Bullet();
|
||||
|
||||
ImGui.SetCursorPos(cursor + new Vector2(0, 10));
|
||||
ImGui.Text($"{easing.GetType().Name} ({easing.Value})");
|
||||
ImGui.Text($"{easing.GetType().Name} ({easing.ValueClamped})");
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,13 @@ internal class DataWindow : Window, IDisposable
|
|||
new KeyStateWidget(),
|
||||
new MarketBoardWidget(),
|
||||
new NetworkMonitorWidget(),
|
||||
new NounProcessorWidget(),
|
||||
new ObjectTableWidget(),
|
||||
new PartyListWidget(),
|
||||
new PluginIpcWidget(),
|
||||
new SeFontTestWidget(),
|
||||
new ServicesWidget(),
|
||||
new SeStringCreatorWidget(),
|
||||
new SeStringRendererTestWidget(),
|
||||
new StartInfoWidget(),
|
||||
new TargetWidget(),
|
||||
|
|
@ -68,6 +70,7 @@ internal class DataWindow : Window, IDisposable
|
|||
private bool isExcept;
|
||||
private bool selectionCollapsed;
|
||||
private IDataWindowWidget currentWidget;
|
||||
private bool isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataWindow"/> class.
|
||||
|
|
@ -81,8 +84,6 @@ internal class DataWindow : Window, IDisposable
|
|||
this.RespectCloseHotkey = false;
|
||||
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
||||
this.currentWidget = this.orderedModules.First();
|
||||
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -91,6 +92,7 @@ internal class DataWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
{
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -183,6 +185,7 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync))
|
||||
{
|
||||
this.isLoaded = false;
|
||||
this.Load();
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +239,11 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
private void Load()
|
||||
{
|
||||
if (this.isLoaded)
|
||||
return;
|
||||
|
||||
this.isLoaded = true;
|
||||
|
||||
foreach (var widget in this.modules)
|
||||
{
|
||||
widget.Load();
|
||||
|
|
|
|||
34
Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs
Normal file
34
Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Common utilities used in Widgets.
|
||||
/// </summary>
|
||||
internal class WidgetUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws text that can be copied on click.
|
||||
/// </summary>
|
||||
/// <param name="text">The text shown and to be copied.</param>
|
||||
/// <param name="tooltipText">The text in the tooltip.</param>
|
||||
internal static void DrawCopyableText(string text, string tooltipText = "Copy")
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,24 +57,6 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
this.DrawExtendArrayTab();
|
||||
}
|
||||
|
||||
private static void DrawCopyableText(string text, string tooltipText)
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawArrayList(Type? arrayType, int arrayCount, short* arrayKeys, AtkArrayData** arrays, ref int selectedIndex)
|
||||
{
|
||||
using var table = ImRaii.Table("ArkArrayTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Borders, new Vector2(300, -1));
|
||||
|
|
@ -162,7 +144,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Address: ");
|
||||
ImGui.SameLine(0, 0);
|
||||
DrawCopyableText($"0x{(nint)array:X}", "Copy address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array:X}", "Copy address");
|
||||
|
||||
if (array->SubscribedAddonsCount > 0)
|
||||
{
|
||||
|
|
@ -238,22 +220,22 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
var ptr = &array->IntArray[i];
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address");
|
||||
|
||||
ImGui.TableNextColumn(); // Integer
|
||||
DrawCopyableText((*ptr).ToString(), "Copy value");
|
||||
WidgetUtil.DrawCopyableText((*ptr).ToString(), "Copy value");
|
||||
|
||||
ImGui.TableNextColumn(); // Short
|
||||
DrawCopyableText((*(short*)ptr).ToString(), "Copy as short");
|
||||
WidgetUtil.DrawCopyableText((*(short*)ptr).ToString(), "Copy as short");
|
||||
|
||||
ImGui.TableNextColumn(); // Byte
|
||||
DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte");
|
||||
WidgetUtil.DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte");
|
||||
|
||||
ImGui.TableNextColumn(); // Float
|
||||
DrawCopyableText((*(float*)ptr).ToString(), "Copy as float");
|
||||
WidgetUtil.DrawCopyableText((*(float*)ptr).ToString(), "Copy as float");
|
||||
|
||||
ImGui.TableNextColumn(); // Hex
|
||||
DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex");
|
||||
WidgetUtil.DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,11 +315,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
if (this.showTextAddress)
|
||||
{
|
||||
if (!isNull)
|
||||
DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address");
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn(); // Managed
|
||||
|
|
@ -351,7 +333,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
{
|
||||
if (this.showMacroString)
|
||||
{
|
||||
DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text");
|
||||
WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -408,11 +390,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted($"#{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address");
|
||||
|
||||
ImGui.TableNextColumn(); // Pointer
|
||||
if (!isNull)
|
||||
DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted($"#{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address");
|
||||
WidgetUtil.DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address");
|
||||
|
||||
ImGui.TableNextColumn(); // FateId
|
||||
DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)");
|
||||
WidgetUtil.DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)");
|
||||
|
||||
ImGui.TableNextColumn(); // State
|
||||
ImGui.TextUnformatted(fate.State.ToString());
|
||||
|
|
@ -140,7 +140,7 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
|
||||
ImGui.TableNextColumn(); // Name
|
||||
|
||||
DrawCopyableText(fate.Name.ToString(), "Click to copy Name");
|
||||
WidgetUtil.DrawCopyableText(fate.Name.ToString(), "Click to copy Name");
|
||||
|
||||
ImGui.TableNextColumn(); // Progress
|
||||
ImGui.TextUnformatted($"{fate.Progress}%");
|
||||
|
|
@ -156,28 +156,10 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted(fate.HasBonus.ToString());
|
||||
|
||||
ImGui.TableNextColumn(); // Position
|
||||
DrawCopyableText(fate.Position.ToString(), "Click to copy Position");
|
||||
WidgetUtil.DrawCopyableText(fate.Position.ToString(), "Click to copy Position");
|
||||
|
||||
ImGui.TableNextColumn(); // Radius
|
||||
DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawCopyableText(string text, string tooltipText)
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
WidgetUtil.DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,19 +42,19 @@ internal class GamepadWidget : IDataWindowWidget
|
|||
|
||||
this.DrawHelper(
|
||||
"Buttons Raw",
|
||||
gamepadState.ButtonsRaw,
|
||||
(uint)gamepadState.ButtonsRaw,
|
||||
gamepadState.Raw);
|
||||
this.DrawHelper(
|
||||
"Buttons Pressed",
|
||||
gamepadState.ButtonsPressed,
|
||||
(uint)gamepadState.ButtonsPressed,
|
||||
gamepadState.Pressed);
|
||||
this.DrawHelper(
|
||||
"Buttons Repeat",
|
||||
gamepadState.ButtonsRepeat,
|
||||
(uint)gamepadState.ButtonsRepeat,
|
||||
gamepadState.Repeat);
|
||||
this.DrawHelper(
|
||||
"Buttons Released",
|
||||
gamepadState.ButtonsReleased,
|
||||
(uint)gamepadState.ButtonsReleased,
|
||||
gamepadState.Released);
|
||||
ImGui.Text($"LeftStick {gamepadState.LeftStick}");
|
||||
ImGui.Text($"RightStick {gamepadState.RightStick}");
|
||||
|
|
|
|||
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