Merge branch 'api12'

This commit is contained in:
goaaats 2025-03-26 20:44:48 +01:00
commit f94f03e114
137 changed files with 6870 additions and 944 deletions

View file

@ -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>

View file

@ -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" />

View file

@ -178,20 +178,18 @@ public sealed class EntryPoint
throw new Exception("Working directory was invalid");
Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory);
// 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);
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
Util.GetScmVersion(),
Util.GetGitHashClientStructs(),
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
Util.GetScmVersion(),
Util.GetGitHashClientStructs(),
FFXIVClientStructs.ThisAssembly.Git.Commits);
dalamud.WaitForUnload();

View 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,
}

View file

@ -11,9 +11,9 @@ namespace Dalamud.Game.Addon.Events;
internal unsafe class AddonEventListener : IDisposable
{
private ReceiveEventDelegate? receiveEventDelegate;
private AtkEventListener* eventListener;
/// <summary>
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
/// </summary>
@ -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);
}
@ -38,17 +38,17 @@ internal unsafe class AddonEventListener : IDisposable
/// <param name="eventPtr">Pointer to the AtkEvent.</param>
/// <param name="eventDataPtr">Pointer to the AtkEventData.</param>
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr);
/// <summary>
/// Gets the address of this listener.
/// </summary>
public nint Address => (nint)this.eventListener;
/// <inheritdoc />
public void Dispose()
{
if (this.eventListener is null) return;
Marshal.FreeHGlobal((nint)this.eventListener->VirtualTable);
Marshal.FreeHGlobal((nint)this.eventListener);
@ -88,7 +88,7 @@ internal unsafe class AddonEventListener : IDisposable
node->RemoveEvent(eventType, param, this.eventListener, false);
});
}
[UnmanagedCallersOnly]
private static void NullSub()
{

View file

@ -13,19 +13,19 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
/// This is called for a majority of all addon OnSetup's.
/// </summary>
public nint AddonSetup { get; private set; }
/// <summary>
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
/// This seems to be called rarely for specific addons.
/// </summary>
public nint AddonSetup2 { get; private set; }
/// <summary>
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
/// </summary>
public nint AddonFinalize { get; private set; }
/// <summary>
/// Gets the address of the addon draw hook invoked by virtual function call.
/// </summary>
@ -35,7 +35,7 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
/// Gets the address of the addon update hook invoked by virtual function call.
/// </summary>
public nint AddonUpdate { get; private set; }
/// <summary>
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
/// </summary>
@ -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");
}
}

View file

@ -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
}
}

View file

@ -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;
}

View file

@ -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,25 +36,21 @@ 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>
public IntPtr GamepadInputAddress { get; private set; }
/// <inheritdoc/>
public Vector2 LeftStick =>
public Vector2 LeftStick =>
new(this.leftStickX, this.leftStickY);
/// <inheritdoc/>
public Vector2 RightStick =>
public Vector2 RightStick =>
new(this.rightStickX, this.rightStickY);
/// <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;
}

View file

@ -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,
}

View file

@ -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,
}

View 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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View 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,
}

View file

@ -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,
}

View file

@ -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,
};
}
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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.

View file

@ -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()

View file

@ -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.

View file

@ -52,7 +52,7 @@ public class GameConfigSection
/// <summary>
/// Event which is fired when a game config option is changed within the section.
/// </summary>
internal event EventHandler<ConfigChangeEvent>? Changed;
internal event EventHandler<ConfigChangeEvent>? Changed;
/// <summary>
/// Gets the number of config entries contained within the section.
@ -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);
@ -544,7 +544,7 @@ public class GameConfigSection
this.Changed?.InvokeSafely(this, eventArgs);
return eventArgs;
}
private unsafe bool TryGetIndex(string name, out uint index)
{
if (this.indexMap.TryGetValue(name, out index))
@ -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);

View file

@ -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,
}

View file

@ -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,
}

View file

@ -30,7 +30,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
private const uint BaseNodeId = 1000;
private static readonly ModuleLog Log = new("DtrBar");
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
@ -58,7 +58,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
private ImmutableList<IReadOnlyDtrBarEntry>? entriesReadOnlyCopy;
private Utf8String* emptyString;
private uint runningNodeIds = BaseNodeId;
private float entryStartPos = float.NaN;
@ -72,7 +72,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
this.addonLifecycle.RegisterListener(this.dtrPostDrawListener);
this.addonLifecycle.RegisterListener(this.dtrPostRequestedUpdateListener);
this.addonLifecycle.RegisterListener(this.dtrPreFinalizeListener);
this.framework.Update += this.Update;
this.configuration.DtrOrder ??= [];
@ -522,7 +522,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler),
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler),
});
var lastChild = dtr->RootNode->ChildNode;
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
@ -590,7 +590,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
if (this.emptyString == null)
this.emptyString = Utf8String.FromString(" ");
newTextNode->SetText(this.emptyString->StringPtr);
newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 };
@ -609,7 +609,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
return newTextNode;
}
private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode)
{
var addon = (AtkUnitBase*)atkUnitBase;
@ -632,7 +632,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
case AddonEventType.MouseOver:
AtkStage.Instance()->TooltipManager.ShowTooltip(addon->Id, node, dtrBarEntry.Tooltip.Encode());
break;
case AddonEventType.MouseOut:
AtkStage.Instance()->TooltipManager.HideTooltip(addon->Id);
break;
@ -646,11 +646,11 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
case AddonEventType.MouseOver:
this.uiEventManager.SetCursor(AddonCursorType.Clickable);
break;
case AddonEventType.MouseOut:
this.uiEventManager.ResetCursor();
break;
case AddonEventType.MouseClick:
dtrBarEntry.OnClick.Invoke();
break;

View file

@ -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,
}

View file

@ -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;

View file

@ -53,7 +53,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
this.hookAgentHudOpenSystemMenu.Enable();
this.hookUiModuleExecuteMainCommand.Enable();
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
@ -180,7 +180,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
// about hooking the exd reader, thank god
var firstStringEntry = &atkValueArgs[5 + 18];
firstStringEntry->ChangeType(ValueType.String);
var secondStringEntry = &atkValueArgs[6 + 18];
secondStringEntry->ChangeType(ValueType.String);
@ -193,7 +193,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
.Append($"{SeIconChar.BoxedLetterD.ToIconString()} ")
.Append(new UIForegroundPayload(0))
.Append(this.locDalamudSettings).Encode();
firstStringEntry->SetManagedString(strPlugins);
secondStringEntry->SetManagedString(strSettings);

View file

@ -17,7 +17,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
/// </summary>
[FieldOffset(0)]
internal readonly InventoryItem InternalItem;
/// <summary>
/// The view of the backing data, in <see cref="ulong"/>.
/// </summary>
@ -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.

View file

@ -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)

View file

@ -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);
}

View 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;
}
}

View 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,
}

View 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;
}
}

File diff suppressed because it is too large Load diff

View 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);
}

View 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,
}

View 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,
}

View 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,
}

View 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,
}

View 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,
};
}

View 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;
}
}

View file

@ -201,7 +201,7 @@ public abstract partial class Payload
case SeStringChunkType.Icon:
payload = new IconPayload();
break;
default:
// Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
break;
@ -306,7 +306,7 @@ public abstract partial class Payload
/// See the <see cref="NewLinePayload"/>.
/// </summary>
NewLine = 0x10,
/// <summary>
/// See the <see cref="IconPayload"/> class.
/// </summary>

View file

@ -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),
};
}
}

View file

@ -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()

View file

@ -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.

View file

@ -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>
@ -301,7 +281,7 @@ public class SeString
public static SeString CreateMapLink(
uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) =>
CreateMapLinkWithInstance(territoryId, mapId, null, xCoord, yCoord, fudgeFactor);
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log.
/// </summary>
@ -340,7 +320,7 @@ public class SeString
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) =>
CreateMapLinkWithInstance(placeName, null, xCoord, yCoord, fudgeFactor);
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name.
/// Returns null if no corresponding PlaceName was found.
@ -511,7 +491,7 @@ public class SeString
{
messageBytes.AddRange(p.Encode());
}
// Add Null Terminator
messageBytes.Add(0);
@ -526,7 +506,7 @@ public class SeString
{
return this.TextValue;
}
private static string GetMapLinkNameString(string placeName, int? instance, string coordinateString)
{
var instanceString = string.Empty;
@ -534,7 +514,7 @@ public class SeString
{
instanceString = (SeIconChar.Instance1 + instance.Value - 1).ToIconString();
}
return $"{placeName}{instanceString} {coordinateString}";
}
}

View file

@ -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
{

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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;
@ -418,27 +418,27 @@ internal unsafe class UiDebug
ImGui.Text("InputBase Text1: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText1);
ImGui.Text("InputBase Text2: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText2);
ImGui.Text("Text1: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText01);
ImGui.Text("Text2: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText02);
ImGui.Text("Text3: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText03);
ImGui.Text("Text4: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText04);
ImGui.Text("Text5: ");
ImGui.SameLine();
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText05);

View file

@ -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;

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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();

View 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);
}
}
}

View file

@ -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");
}
}
}

View file

@ -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");
}
}
}

View file

@ -12,9 +12,9 @@ internal class GamepadWidget : IDataWindowWidget
{
/// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "gamepad", "controller" };
/// <inheritdoc/>
public string DisplayName { get; init; } = "Gamepad";
public string DisplayName { get; init; } = "Gamepad";
/// <inheritdoc/>
public bool Ready { get; set; }
@ -42,24 +42,24 @@ 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}");
}
private void DrawHelper(string text, uint mask, Func<GamepadButtons, float> resolve)
{
ImGui.Text($"{text} {mask:X4}");

View file

@ -219,7 +219,7 @@ internal class InventoryWidget : IDataWindowWidget
if (!this.IsEventItem(item.ItemId))
{
AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.Spiritbond.ToString());
AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.SpiritbondOrCollectability.ToString());
if (item.CrafterContentId != 0)
AddKeyValueRow("CrafterContentId", item.CrafterContentId.ToString());
@ -380,7 +380,7 @@ internal class InventoryWidget : IDataWindowWidget
var rowId = this.GetItemRarityColorType(item, isEdgeColor);
return this.dataManager.Excel.GetSheet<UIColor>().TryGetRow(rowId, out var color)
? BinaryPrimitives.ReverseEndianness(color.UIForeground) | 0xFF000000
? BinaryPrimitives.ReverseEndianness(color.Dark) | 0xFF000000
: 0xFFFFFFFF;
}

View file

@ -0,0 +1,207 @@
using System.Linq;
using System.Text;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.Text.Noun;
using Dalamud.Game.Text.Noun.Enums;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using Lumina.Data;
using Lumina.Excel;
using Lumina.Excel.Sheets;
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
/// <summary>
/// Widget for the NounProcessor service.
/// </summary>
internal class NounProcessorWidget : IDataWindowWidget
{
/// <summary>A list of German grammatical cases.</summary>
internal static readonly string[] GermanCases = ["Nominative", "Genitive", "Dative", "Accusative"];
private static readonly Type[] NounSheets = [
typeof(Aetheryte),
typeof(BNpcName),
typeof(BeastTribe),
typeof(DeepDungeonEquipment),
typeof(DeepDungeonItem),
typeof(DeepDungeonMagicStone),
typeof(DeepDungeonDemiclone),
typeof(ENpcResident),
typeof(EObjName),
typeof(EurekaAetherItem),
typeof(EventItem),
typeof(GCRankGridaniaFemaleText),
typeof(GCRankGridaniaMaleText),
typeof(GCRankLimsaFemaleText),
typeof(GCRankLimsaMaleText),
typeof(GCRankUldahFemaleText),
typeof(GCRankUldahMaleText),
typeof(GatheringPointName),
typeof(Glasses),
typeof(GlassesStyle),
typeof(HousingPreset),
typeof(Item),
typeof(MJIName),
typeof(Mount),
typeof(Ornament),
typeof(TripleTriadCard),
];
private ClientLanguage[] languages = [];
private string[] languageNames = [];
private int selectedSheetNameIndex = 0;
private int selectedLanguageIndex = 0;
private int rowId = 1;
private int amount = 1;
/// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "noun" };
/// <inheritdoc/>
public string DisplayName { get; init; } = "Noun Processor";
/// <inheritdoc/>
public bool Ready { get; set; }
/// <inheritdoc/>
public void Load()
{
this.languages = Enum.GetValues<ClientLanguage>();
this.languageNames = Enum.GetNames<ClientLanguage>();
this.selectedLanguageIndex = (int)Service<ClientState>.Get().ClientLanguage;
this.Ready = true;
}
/// <inheritdoc/>
public void Draw()
{
var nounProcessor = Service<NounProcessor>.Get();
var dataManager = Service<DataManager>.Get();
var clientState = Service<ClientState>.Get();
var sheetType = NounSheets.ElementAt(this.selectedSheetNameIndex);
var language = this.languages[this.selectedLanguageIndex];
ImGui.SetNextItemWidth(300);
if (ImGui.Combo("###SelectedSheetName", ref this.selectedSheetNameIndex, NounSheets.Select(t => t.Name).ToArray(), NounSheets.Length))
{
this.rowId = 1;
}
ImGui.SameLine();
ImGui.SetNextItemWidth(120);
if (ImGui.Combo("###SelectedLanguage", ref this.selectedLanguageIndex, this.languageNames, this.languageNames.Length))
{
language = this.languages[this.selectedLanguageIndex];
this.rowId = 1;
}
ImGui.SetNextItemWidth(120);
var sheet = dataManager.Excel.GetSheet<RawRow>(Language.English, sheetType.Name);
var minRowId = (int)sheet.FirstOrDefault().RowId;
var maxRowId = (int)sheet.LastOrDefault().RowId;
if (ImGui.InputInt("RowId###RowId", ref this.rowId, 1, 10, ImGuiInputTextFlags.AutoSelectAll))
{
if (this.rowId < minRowId)
this.rowId = minRowId;
if (this.rowId >= maxRowId)
this.rowId = maxRowId;
}
ImGui.SameLine();
ImGui.TextUnformatted($"(Range: {minRowId} - {maxRowId})");
ImGui.SetNextItemWidth(120);
if (ImGui.InputInt("Amount###Amount", ref this.amount, 1, 10, ImGuiInputTextFlags.AutoSelectAll))
{
if (this.amount <= 0)
this.amount = 1;
}
var articleTypeEnumType = language switch
{
ClientLanguage.Japanese => typeof(JapaneseArticleType),
ClientLanguage.German => typeof(GermanArticleType),
ClientLanguage.French => typeof(FrenchArticleType),
_ => typeof(EnglishArticleType),
};
var numCases = language == ClientLanguage.German ? 4 : 1;
#if DEBUG
if (ImGui.Button("Copy as self-test entry"))
{
var sb = new StringBuilder();
foreach (var articleType in Enum.GetValues(articleTypeEnumType))
{
for (var grammaticalCase = 0; grammaticalCase < numCases; grammaticalCase++)
{
var nounParams = new NounParams()
{
SheetName = sheetType.Name,
RowId = (uint)this.rowId,
Language = language,
Quantity = this.amount,
ArticleType = (int)articleType,
GrammaticalCase = grammaticalCase,
};
var output = nounProcessor.ProcessNoun(nounParams).ExtractText().Replace("\"", "\\\"");
var caseParam = language == ClientLanguage.German ? $"(int)GermanCases.{GermanCases[grammaticalCase]}" : "1";
sb.AppendLine($"new(nameof(LSheets.{sheetType.Name}), {this.rowId}, ClientLanguage.{language}, {this.amount}, (int){articleTypeEnumType.Name}.{Enum.GetName(articleTypeEnumType, articleType)}, {caseParam}, \"{output}\"),");
}
}
ImGui.SetClipboardText(sb.ToString());
}
#endif
using var table = ImRaii.Table("TextDecoderTable", 1 + numCases, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings);
if (!table) return;
ImGui.TableSetupColumn("ArticleType", ImGuiTableColumnFlags.WidthFixed, 150);
for (var i = 0; i < numCases; i++)
ImGui.TableSetupColumn(language == ClientLanguage.German ? GermanCases[i] : "Text");
ImGui.TableSetupScrollFreeze(6, 1);
ImGui.TableHeadersRow();
foreach (var articleType in Enum.GetValues(articleTypeEnumType))
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableHeader(articleType.ToString());
for (var currentCase = 0; currentCase < numCases; currentCase++)
{
ImGui.TableNextColumn();
try
{
var nounParams = new NounParams()
{
SheetName = sheetType.Name,
RowId = (uint)this.rowId,
Language = language,
Quantity = this.amount,
ArticleType = (int)articleType,
GrammaticalCase = currentCase,
};
ImGui.TextUnformatted(nounProcessor.ProcessNoun(nounParams).ExtractText());
}
catch (Exception ex)
{
ImGui.TextUnformatted(ex.ToString());
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -93,34 +93,34 @@ internal class UiColorWidget : IDataWindowWidget
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.PushID($"row{id}_col1");
if (this.DrawColorColumn(row.UIForeground) &&
ImGui.PushID($"row{id}_dark");
if (this.DrawColorColumn(row.Dark) &&
adjacentRow.HasValue)
DrawEdgePreview(id, row.UIForeground, adjacentRow.Value.UIForeground);
DrawEdgePreview(id, row.Dark, adjacentRow.Value.Dark);
ImGui.PopID();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.PushID($"row{id}_col2");
if (this.DrawColorColumn(row.UIGlow) &&
ImGui.PushID($"row{id}_light");
if (this.DrawColorColumn(row.Light) &&
adjacentRow.HasValue)
DrawEdgePreview(id, row.UIGlow, adjacentRow.Value.UIGlow);
DrawEdgePreview(id, row.Light, adjacentRow.Value.Light);
ImGui.PopID();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.PushID($"row{id}_col3");
if (this.DrawColorColumn(row.Unknown0) &&
ImGui.PushID($"row{id}_classic");
if (this.DrawColorColumn(row.ClassicFF) &&
adjacentRow.HasValue)
DrawEdgePreview(id, row.Unknown0, adjacentRow.Value.Unknown0);
DrawEdgePreview(id, row.ClassicFF, adjacentRow.Value.ClassicFF);
ImGui.PopID();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.PushID($"row{id}_col4");
if (this.DrawColorColumn(row.Unknown1) &&
ImGui.PushID($"row{id}_blue");
if (this.DrawColorColumn(row.ClearBlue) &&
adjacentRow.HasValue)
DrawEdgePreview(id, row.Unknown1, adjacentRow.Value.Unknown1);
DrawEdgePreview(id, row.ClearBlue, adjacentRow.Value.ClearBlue);
ImGui.PopID();
}
}

View file

@ -110,7 +110,7 @@ internal class ContextMenuAgingStep : IAgingStep
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
@ -244,7 +244,7 @@ internal class ContextMenuAgingStep : IAgingStep
b.AppendLine($"Container: {item.ContainerType}");
b.AppendLine($"Slot: {item.InventorySlot}");
b.AppendLine($"Quantity: {item.Quantity}");
b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.Spiritbond}");
b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.SpiritbondOrCollectability}");
b.AppendLine($"Condition: {item.Condition / 300f:0.00}% ({item.Condition})");
b.AppendLine($"Is HQ: {item.IsHq}");
b.AppendLine($"Is Company Crest Applied: {item.IsCompanyCrestApplied}");

View file

@ -1,6 +1,11 @@
using Dalamud.Game.ClientState.GamePad;
using System.Linq;
using ImGuiNET;
using Dalamud.Game.ClientState.GamePad;
using Dalamud.Interface.Utility;
using Lumina.Text.Payloads;
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
@ -17,11 +22,34 @@ internal class GamepadStateAgingStep : IAgingStep
{
var gamepadState = Service<GamepadState>.Get();
ImGui.Text("Hold down North, East, L1");
var buttons = new (GamepadButtons Button, uint IconId)[]
{
(GamepadButtons.North, 11),
(GamepadButtons.East, 8),
(GamepadButtons.L1, 12),
};
if (gamepadState.Raw(GamepadButtons.North) == 1
&& gamepadState.Raw(GamepadButtons.East) == 1
&& gamepadState.Raw(GamepadButtons.L1) == 1)
var builder = LSeStringBuilder.SharedPool.Get();
builder.Append("Hold down ");
for (var i = 0; i < buttons.Length; i++)
{
var (button, iconId) = buttons[i];
builder.BeginMacro(MacroCode.Icon).AppendUIntExpression(iconId).EndMacro();
builder.PushColorRgba(gamepadState.Raw(button) == 1 ? 0x0000FF00u : 0x000000FF);
builder.Append(button.ToString());
builder.PopColor();
builder.Append(i < buttons.Length - 1 ? ", " : ".");
}
ImGuiHelpers.SeStringWrapped(builder.ToReadOnlySeString());
LSeStringBuilder.SharedPool.Return(builder);
if (buttons.All(tuple => gamepadState.Raw(tuple.Button) == 1))
{
return SelfTestStepResult.Pass;
}

View file

@ -0,0 +1,257 @@
using Dalamud.Game;
using Dalamud.Game.Text.Noun;
using Dalamud.Game.Text.Noun.Enums;
using ImGuiNET;
using LSheets = Lumina.Excel.Sheets;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for NounProcessor.
/// </summary>
internal class NounProcessorAgingStep : IAgingStep
{
private NounTestEntry[] tests =
[
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その蜂蜜酒の運び人"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "蜂蜜酒の運び人"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの蜂蜜酒の運び人"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの蜂蜜酒の運び人"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a mead-porting Midlander"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the mead-porting Midlander"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "mead-porting Midlanders"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "mead-porting Midlanders"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppendem Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses Met schleppenden Wiesländers"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppender Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppende Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen Met schleppenden Wiesländern"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese Met schleppenden Wiesländer"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un livreur d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le livreur d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon livreur d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton livreur d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son livreur d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des livreurs d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les livreurs d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes livreurs d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes livreurs d'hydromel"),
new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses livreurs d'hydromel"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その酔いどれのネル"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "酔いどれのネル"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "Nell Half-full"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "Nell Half-full"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "Nell der Beschwipsten"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "Nell die Beschwipste"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "Nell la Boit-sans-soif"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "Nell la Boit-sans-soif"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "ma Nell la Boit-sans-soif"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ta Nell la Boit-sans-soif"),
new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "sa Nell la Boit-sans-soif"),
new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その希少トームストーン:幻想"),
new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "希少トームストーン:幻想"),
new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの希少トームストーン:幻想"),
new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの希少トームストーン:幻想"),
new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "an irregular tomestone of phantasmagoria"),
new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the irregular tomestone of phantasmagoria"),
new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "irregular tomestones of phantasmagoria"),
new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "irregular tomestones of phantasmagoria"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein ungewöhnlicher Allagischer Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der ungewöhnlicher Allagischer Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein ungewöhnliche Allagische Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein ungewöhnlicher Allagischer Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnlicher Allagischer Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichem Allagischem Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser ungewöhnliche Allagische Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses ungewöhnlichen Allagischen Steins der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen ungewöhnlichen Allagischen Stein der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die ungewöhnliche Allagische Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der ungewöhnlicher Allagischer Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die ungewöhnliche Allagische Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnliche Allagische Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlicher Allagischer Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnliche Allagische Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen ungewöhnlichen Allagischen Steinen der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un mémoquartz inhabituel fantasmagorique"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le mémoquartz inhabituel fantasmagorique"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon mémoquartz inhabituel fantasmagorique"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton mémoquartz inhabituel fantasmagorique"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son mémoquartz inhabituel fantasmagorique"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des mémoquartz inhabituels fantasmagoriques"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les mémoquartz inhabituels fantasmagoriques"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"),
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"),
];
private enum GermanCases
{
Nominative,
Genitive,
Dative,
Accusative,
}
/// <inheritdoc/>
public string Name => "Test NounProcessor";
/// <inheritdoc/>
public unsafe SelfTestStepResult RunStep()
{
var nounProcessor = Service<NounProcessor>.Get();
for (var i = 0; i < this.tests.Length; i++)
{
var e = this.tests[i];
var nounParams = new NounParams()
{
SheetName = e.SheetName,
RowId = e.RowId,
Language = e.Language,
Quantity = e.Quantity,
ArticleType = e.ArticleType,
GrammaticalCase = e.GrammaticalCase,
};
var output = nounProcessor.ProcessNoun(nounParams);
if (e.ExpectedResult != output)
{
ImGui.TextUnformatted($"Mismatch detected (Test #{i}):");
ImGui.TextUnformatted($"Got: {output}");
ImGui.TextUnformatted($"Expected: {e.ExpectedResult}");
if (ImGui.Button("Continue"))
return SelfTestStepResult.Fail;
return SelfTestStepResult.Waiting;
}
}
return SelfTestStepResult.Pass;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
private record struct NounTestEntry(
string SheetName,
uint RowId,
ClientLanguage Language,
int Quantity,
int ArticleType,
int GrammaticalCase,
string ExpectedResult);
}

View file

@ -0,0 +1,92 @@
using Dalamud.Game.ClientState;
using Dalamud.Game.Text.Evaluator;
using ImGuiNET;
using Lumina.Text.ReadOnly;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for SeStringEvaluator.
/// </summary>
internal class SeStringEvaluatorAgingStep : IAgingStep
{
private int step = 0;
/// <inheritdoc/>
public string Name => "Test SeStringEvaluator";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
var seStringEvaluator = Service<SeStringEvaluator>.Get();
switch (this.step)
{
case 0:
ImGui.TextUnformatted("Is this the current time, and is it ticking?");
// This checks that EvaluateFromAddon fetches the correct Addon row,
// that MacroDecoder.GetMacroTime()->SetTime() has been called
// and that local and global parameters have been read correctly.
ImGui.TextUnformatted(seStringEvaluator.EvaluateFromAddon(31, [(uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds()]).ExtractText());
if (ImGui.Button("Yes"))
this.step++;
ImGui.SameLine();
if (ImGui.Button("No"))
return SelfTestStepResult.Fail;
break;
case 1:
ImGui.TextUnformatted("Checking pcname macro using the local player name...");
// This makes sure that NameCache.Instance()->TryGetCharacterInfoByEntityId() has been called,
// that it returned the local players name by using its EntityId,
// and that it didn't include the world name by checking the HomeWorldId against AgentLobby.Instance()->LobbyData.HomeWorldId.
var clientState = Service<ClientState>.Get();
var localPlayer = clientState.LocalPlayer;
if (localPlayer is null)
{
ImGui.TextUnformatted("You need to be logged in for this step.");
if (ImGui.Button("Skip"))
return SelfTestStepResult.NotRan;
return SelfTestStepResult.Waiting;
}
var evaluatedPlayerName = seStringEvaluator.Evaluate(ReadOnlySeString.FromMacroString("<pcname(lnum1)>"), [localPlayer.EntityId]).ExtractText();
var localPlayerName = localPlayer.Name.TextValue;
if (evaluatedPlayerName != localPlayerName)
{
ImGui.TextUnformatted("The player name doesn't match:");
ImGui.TextUnformatted($"Evaluated Player Name (got): {evaluatedPlayerName}");
ImGui.TextUnformatted($"Local Player Name (expected): {localPlayerName}");
if (ImGui.Button("Continue"))
return SelfTestStepResult.Fail;
return SelfTestStepResult.Waiting;
}
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
this.step = 0;
}
}

View file

@ -0,0 +1,130 @@
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Game.Text.Evaluator.Internal;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for SheetRedirectResolver.
/// </summary>
internal class SheetRedirectResolverAgingStep : IAgingStep
{
private RedirectEntry[] redirects =
[
new("Item", 10, SheetRedirectFlags.Item),
new("ItemHQ", 10, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality),
new("ItemMP", 10, SheetRedirectFlags.Item | SheetRedirectFlags.Collectible),
new("Item", 35588, SheetRedirectFlags.Item),
new("Item", 1035588, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality),
new("Item", 2000217, SheetRedirectFlags.Item | SheetRedirectFlags.EventItem),
new("ActStr", 10, SheetRedirectFlags.Action), // Trait
new("ActStr", 1000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action
new("ActStr", 2000010, SheetRedirectFlags.Action), // Item
new("ActStr", 3000010, SheetRedirectFlags.Action), // EventItem
new("ActStr", 4000010, SheetRedirectFlags.Action), // EventAction
new("ActStr", 5000010, SheetRedirectFlags.Action), // GeneralAction
new("ActStr", 6000010, SheetRedirectFlags.Action), // BuddyAction
new("ActStr", 7000010, SheetRedirectFlags.Action), // MainCommand
new("ActStr", 8000010, SheetRedirectFlags.Action), // Companion
new("ActStr", 9000010, SheetRedirectFlags.Action), // CraftAction
new("ActStr", 10000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action
new("ActStr", 11000010, SheetRedirectFlags.Action), // PetAction
new("ActStr", 12000010, SheetRedirectFlags.Action), // CompanyAction
new("ActStr", 13000010, SheetRedirectFlags.Action), // Mount
// new("ActStr", 14000010, RedirectFlags.Action),
// new("ActStr", 15000010, RedirectFlags.Action),
// new("ActStr", 16000010, RedirectFlags.Action),
// new("ActStr", 17000010, RedirectFlags.Action),
// new("ActStr", 18000010, RedirectFlags.Action),
new("ActStr", 19000010, SheetRedirectFlags.Action), // BgcArmyAction
new("ActStr", 20000010, SheetRedirectFlags.Action), // Ornament
new("ObjStr", 10), // BNpcName
new("ObjStr", 1000010), // ENpcResident
new("ObjStr", 2000010), // Treasure
new("ObjStr", 3000010), // Aetheryte
new("ObjStr", 4000010), // GatheringPointName
new("ObjStr", 5000010), // EObjName
new("ObjStr", 6000010), // Mount
new("ObjStr", 7000010), // Companion
// new("ObjStr", 8000010),
// new("ObjStr", 9000010),
new("ObjStr", 10000010), // Item
new("EObj", 2003479), // EObj => EObjName
new("Treasure", 1473), // Treasure (without name, falls back to rowId 0)
new("Treasure", 1474), // Treasure (with name)
new("WeatherPlaceName", 0),
new("WeatherPlaceName", 28),
new("WeatherPlaceName", 40),
new("WeatherPlaceName", 52),
new("WeatherPlaceName", 2300),
];
private unsafe delegate SheetRedirectFlags ResolveSheetRedirect(RaptureTextModule* thisPtr, Utf8String* sheetName, uint* rowId, uint* flags);
/// <inheritdoc/>
public string Name => "Test SheetRedirectResolver";
/// <inheritdoc/>
public unsafe SelfTestStepResult RunStep()
{
// Client::UI::Misc::RaptureTextModule_ResolveSheetRedirect
if (!Service<TargetSigScanner>.Get().TryScanText("E8 ?? ?? ?? ?? 44 8B E8 A8 10", out var addr))
return SelfTestStepResult.Fail;
var sheetRedirectResolver = Service<SheetRedirectResolver>.Get();
var resolveSheetRedirect = Marshal.GetDelegateForFunctionPointer<ResolveSheetRedirect>(addr);
var utf8SheetName = Utf8String.CreateEmpty();
try
{
for (var i = 0; i < this.redirects.Length; i++)
{
var redirect = this.redirects[i];
utf8SheetName->SetString(redirect.SheetName);
var rowId1 = redirect.RowId;
uint colIndex1 = ushort.MaxValue;
var flags1 = resolveSheetRedirect(RaptureTextModule.Instance(), utf8SheetName, &rowId1, &colIndex1);
var sheetName2 = redirect.SheetName;
var rowId2 = redirect.RowId;
uint colIndex2 = ushort.MaxValue;
var flags2 = sheetRedirectResolver.Resolve(ref sheetName2, ref rowId2, ref colIndex2);
if (utf8SheetName->ToString() != sheetName2 || rowId1 != rowId2 || colIndex1 != colIndex2 || flags1 != flags2)
{
ImGui.TextUnformatted($"Mismatch detected (Test #{i}):");
ImGui.TextUnformatted($"Input: {redirect.SheetName}#{redirect.RowId}");
ImGui.TextUnformatted($"Game: {utf8SheetName->ToString()}#{rowId1}-{colIndex1} ({flags1})");
ImGui.TextUnformatted($"Evaluated: {sheetName2}#{rowId2}-{colIndex2} ({flags2})");
if (ImGui.Button("Continue"))
return SelfTestStepResult.Fail;
return SelfTestStepResult.Waiting;
}
}
return SelfTestStepResult.Pass;
}
finally
{
utf8SheetName->Dtor(true);
}
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
private record struct RedirectEntry(string SheetName, uint RowId, SheetRedirectFlags Flags = SheetRedirectFlags.None);
}

View file

@ -50,6 +50,9 @@ internal class SelfTestWindow : Window
new DutyStateAgingStep(),
new GameConfigAgingStep(),
new MarketBoardAgingStep(),
new SheetRedirectResolverAgingStep(),
new NounProcessorAgingStep(),
new SeStringEvaluatorAgingStep(),
new LogoutEventAgingStep(),
};

View file

@ -223,7 +223,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
moveEasing.Update();
var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale;
var pos = moveEasing.Value * finalPos;
var pos = moveEasing.ValueClamped * finalPos;
// FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
if (moveEasing.IsDone)
@ -270,7 +270,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
this.fadeOutEasing.Update();
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)Math.Max(this.fadeOutEasing.Value, 0)))
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.ValueClamped))
{
var i = 0;
foreach (var entry in entries)
@ -353,7 +353,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
var initialCursor = ImGui.GetCursorPos();
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value))
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.ValueClamped))
{
var texture = this.shadeTexture.Value;
ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height) * scale);
@ -403,7 +403,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
if (overrideAlpha)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.ValueClamped);
}
else if (isFirst)
{
@ -430,7 +430,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
if (overrideAlpha)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)Math.Min(logoEasing.Value, 1) : 0f);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.ValueClamped : 0f);
}
// Drop shadow
@ -480,7 +480,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
textNode->TextFlags |= (byte)TextFlags.MultiLine;
textNode->AlignmentType = AlignmentType.TopLeft;
var containsDalamudVersionString = textNode->OriginalTextPointer == textNode->NodeText.StringPtr;
var containsDalamudVersionString = textNode->OriginalTextPointer.Value == textNode->NodeText.StringPtr.Value;
if (!this.configuration.ShowTsm || !this.showTsm.Value)
{
if (containsDalamudVersionString)
@ -498,7 +498,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
this.lastLoadedPluginCount = count;
var lssb = LSeStringBuilder.SharedPool.Get();
lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String)).Append("\n\n");
lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String.Value)).Append("\n\n");
lssb.PushEdgeColorType(701).PushColorType(539)
.Append(SeIconChar.BoxedLetterD.ToIconChar())
.PopColorType().PopEdgeColorType();

View file

@ -9,7 +9,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.TerraFxCom;
using Lumina.Data.Files;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
@ -24,32 +24,30 @@ internal sealed partial class TextureManager
(nint)this.ConvertToKernelTexture(wrap, leaveWrapOpen);
/// <inheritdoc cref="ITextureProvider.ConvertToKernelTexture"/>
public unsafe FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture* ConvertToKernelTexture(
IDalamudTextureWrap wrap,
bool leaveWrapOpen = false)
public unsafe Texture* ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false)
{
using var wrapAux = new WrapAux(wrap, leaveWrapOpen);
var flags = TexFile.Attribute.TextureType2D;
var flags = TextureFlags.TextureType2D;
if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_IMMUTABLE)
flags |= TexFile.Attribute.Immutable;
flags |= TextureFlags.Immutable;
if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_DYNAMIC)
flags |= TexFile.Attribute.ReadWrite;
flags |= TextureFlags.ReadWrite;
if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) != 0)
flags |= TexFile.Attribute.CpuRead;
flags |= TextureFlags.CpuRead;
if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET) != 0)
flags |= TexFile.Attribute.TextureRenderTarget;
flags |= TextureFlags.TextureRenderTarget;
if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_DEPTH_STENCIL) != 0)
flags |= TexFile.Attribute.TextureDepthStencil;
flags |= TextureFlags.TextureDepthStencil;
if (wrapAux.Desc.ArraySize != 1)
throw new NotSupportedException("TextureArray2D is currently not supported.");
var gtex = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture.CreateTexture2D(
var gtex = Texture.CreateTexture2D(
(int)wrapAux.Desc.Width,
(int)wrapAux.Desc.Height,
(byte)wrapAux.Desc.MipLevels,
(uint)TexFile.TextureFormat.Null, // instructs the game to skip preprocessing it seems
(uint)flags,
0, // instructs the game to skip preprocessing it seems
flags,
0);
// Kernel::Texture owns these resources. We're passing the ownership to them.
@ -57,28 +55,27 @@ internal sealed partial class TextureManager
wrapAux.SrvPtr->AddRef();
// Not sure this is needed
var ltf = wrapAux.Desc.Format switch
gtex->TextureFormat = wrapAux.Desc.Format switch
{
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TexFile.TextureFormat.R32G32B32A32F,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TexFile.TextureFormat.R16G16B16A16F,
DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TexFile.TextureFormat.R32G32F,
DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TexFile.TextureFormat.R16G16F,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TexFile.TextureFormat.R32F,
DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TexFile.TextureFormat.D24S8,
DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TexFile.TextureFormat.D16,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TexFile.TextureFormat.A8,
DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TexFile.TextureFormat.BC1,
DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TexFile.TextureFormat.BC2,
DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TexFile.TextureFormat.BC3,
DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TexFile.TextureFormat.BC5,
DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TexFile.TextureFormat.B4G4R4A4,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TexFile.TextureFormat.B5G5R5A1,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TexFile.TextureFormat.B8G8R8A8,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TexFile.TextureFormat.B8G8R8X8,
DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TexFile.TextureFormat.BC7,
_ => TexFile.TextureFormat.Null,
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_FLOAT,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_FLOAT,
DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TextureFormat.R32G32_FLOAT,
DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TextureFormat.R16G16_FLOAT,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TextureFormat.R32_FLOAT,
DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TextureFormat.D24_UNORM_S8_UINT,
DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TextureFormat.D16_UNORM,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TextureFormat.A8_UNORM,
DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TextureFormat.BC1_UNORM,
DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TextureFormat.BC2_UNORM,
DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TextureFormat.BC3_UNORM,
DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TextureFormat.BC5_UNORM,
DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TextureFormat.B4G4R4A4_UNORM,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TextureFormat.B5G5R5A1_UNORM,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNORM,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TextureFormat.B8G8R8X8_UNORM,
DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TextureFormat.BC7_UNORM,
_ => 0,
};
gtex->TextureFormat = (FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.TextureFormat)ltf;
gtex->D3D11Texture2D = wrapAux.TexPtr;
gtex->D3D11ShaderResourceView = wrapAux.SrvPtr;

View file

@ -210,8 +210,6 @@ public static class ImGuiHelpers
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
/// <returns>Interaction result of the rendered text.</returns>
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
public static SeStringDrawResult SeStringWrapped(
ReadOnlySpan<byte> sss,
scoped in SeStringDrawParams style = default,
@ -226,8 +224,6 @@ public static class ImGuiHelpers
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
/// <returns>Interaction result of the rendered text.</returns>
/// <remarks>This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel.
/// The function definition is stable; only in the next API version a function may be removed.</remarks>
public static SeStringDrawResult CompileSeStringWrapped(
string text,
scoped in SeStringDrawParams style = default,

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -216,7 +216,12 @@ internal class PluginManagementCommandHandler : IInternalDisposableService
this.chat.Print(onSuccess);
}
if (operation == PluginCommandOperation.Toggle)
{
operation = plugin.State == PluginState.Loaded ? PluginCommandOperation.Disable : PluginCommandOperation.Enable;
}
switch (operation)
{
case PluginCommandOperation.Enable:
@ -235,14 +240,6 @@ internal class PluginManagementCommandHandler : IInternalDisposableService
Loc.Localize("PluginCommandsDisableFailed", "Failed to disable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name)))
.ConfigureAwait(false);
break;
case PluginCommandOperation.Toggle:
this.chat.Print(Loc.Localize("PluginCommandsToggling", "Toggling plugin \"{0}\"...").Format(plugin.Name));
Task.Run(() => plugin.State == PluginState.Loaded ? plugin.UnloadAsync() : plugin.LoadAsync(PluginLoadReason.Installer))
.ContinueWith(t => Continuation(t,
Loc.Localize("PluginCommandsToggleSuccess", "Plugin \"{0}\" toggled.").Format(plugin.Name),
Loc.Localize("PluginCommandsToggleFailed", "Failed to toggle plugin \"{0}\". Please check the console for errors.").Format(plugin.Name)))
.ConfigureAwait(false);
break;
default:
throw new ArgumentOutOfRangeException(nameof(operation), operation, null);
}

View file

@ -3,32 +3,31 @@ namespace Dalamud.Plugin;
/// <summary>
/// This enum reflects reasons for loading a plugin.
/// </summary>
[Flags]
public enum PluginLoadReason
{
/// <summary>
/// We don't know why this plugin was loaded.
/// </summary>
Unknown,
Unknown = 1 << 0,
/// <summary>
/// This plugin was loaded because it was installed with the plugin installer.
/// </summary>
Installer,
Installer = 1 << 1,
/// <summary>
/// This plugin was loaded because it was just updated.
/// </summary>
Update,
Update = 1 << 2,
/// <summary>
/// This plugin was loaded because it was told to reload.
/// </summary>
Reload,
Reload = 1 << 3,
/// <summary>
/// This plugin was loaded because the game was started or Dalamud was reinjected.
/// </summary>
Boot,
Boot = 1 << 4,
}
// TODO(api9): This should be a mask, so that we can combine Installer | ProfileLoaded

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