mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Add SeStringEvaluator service (#2188)
* Add SeStringEvaluator service * Move DrawCopyableText into WidgetUtil * Use Icon2RemapTable in SeStringRenderer * Beautify some code * Make sure to use the correct language * Add SeString Creator widget * Fix getting local parameters * Update expressionNames * misc changes * Use InvariantCulture in TryResolveSheet * Add SeStringEvaluatorAgingStep * Fix item id comparisons * Add SheetRedirectResolverAgingStep * Add NounProcessorAgingStep * Update SeString.CreateItemLink This also adds the internal ItemUtil class. * Fix name of SeStringCreator widget * Add Global Parameters tab to SeStringCreatorWidget * Load widgets on demand * Update SeStringCreatorWidget * Resizable SeStringCreatorWidget panels * Update GamepadStateAgingStep * Experimental status was removed in #2144 * Update SheetRedirectResolver, rewrite Noun params * Fixes for 4 am code * Remove incorrect column offset I have no idea how that happened. * Draw names of linked things --------- Co-authored-by: Soreepeong <3614868+Soreepeong@users.noreply.github.com> Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>
This commit is contained in:
parent
7cac19ce81
commit
fdbfdbb2cd
36 changed files with 5831 additions and 196 deletions
89
Dalamud/Game/ActionKind.cs
Normal file
89
Dalamud/Game/ActionKind.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing possible action kinds.
|
||||
/// </summary>
|
||||
public enum ActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// A Trait.
|
||||
/// </summary>
|
||||
Trait = 0,
|
||||
|
||||
/// <summary>
|
||||
/// An Action.
|
||||
/// </summary>
|
||||
Action = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A usable Item.
|
||||
/// </summary>
|
||||
Item = 2, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// A usable EventItem.
|
||||
/// </summary>
|
||||
EventItem = 3, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// An EventAction.
|
||||
/// </summary>
|
||||
EventAction = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A GeneralAction.
|
||||
/// </summary>
|
||||
GeneralAction = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A BuddyAction.
|
||||
/// </summary>
|
||||
BuddyAction = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A MainCommand.
|
||||
/// </summary>
|
||||
MainCommand = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A Companion.
|
||||
/// </summary>
|
||||
Companion = 8, // unresolved?!
|
||||
|
||||
/// <summary>
|
||||
/// A CraftAction.
|
||||
/// </summary>
|
||||
CraftAction = 9,
|
||||
|
||||
/// <summary>
|
||||
/// An Action (again).
|
||||
/// </summary>
|
||||
Action2 = 10, // what's the difference?
|
||||
|
||||
/// <summary>
|
||||
/// A PetAction.
|
||||
/// </summary>
|
||||
PetAction = 11,
|
||||
|
||||
/// <summary>
|
||||
/// A CompanyAction.
|
||||
/// </summary>
|
||||
CompanyAction = 12,
|
||||
|
||||
/// <summary>
|
||||
/// A Mount.
|
||||
/// </summary>
|
||||
Mount = 13,
|
||||
|
||||
// 14-18 unused
|
||||
|
||||
/// <summary>
|
||||
/// A BgcArmyAction.
|
||||
/// </summary>
|
||||
BgcArmyAction = 19,
|
||||
|
||||
/// <summary>
|
||||
/// An Ornament.
|
||||
/// </summary>
|
||||
Ornament = 20,
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Lumina.Text;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps payloads in an open and close icon, for example the Auto Translation open/close brackets.
|
||||
/// </summary>
|
||||
internal readonly struct SeStringBuilderIconWrap : IDisposable
|
||||
{
|
||||
private readonly SeStringBuilder builder;
|
||||
private readonly uint iconClose;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringBuilderIconWrap"/> struct.<br/>
|
||||
/// Appends an icon macro with <paramref name="iconOpen"/> on creation, and an icon macro with
|
||||
/// <paramref name="iconClose"/> on disposal.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to use.</param>
|
||||
/// <param name="iconOpen">The open icon id.</param>
|
||||
/// <param name="iconClose">The close icon id.</param>
|
||||
public SeStringBuilderIconWrap(SeStringBuilder builder, uint iconOpen, uint iconClose)
|
||||
{
|
||||
this.builder = builder;
|
||||
this.iconClose = iconClose;
|
||||
this.builder.AppendIcon(iconOpen);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() => this.builder.AppendIcon(this.iconClose);
|
||||
}
|
||||
83
Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs
Normal file
83
Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System.Globalization;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A context wrapper used in <see cref="SeStringEvaluator"/>.
|
||||
/// </summary>
|
||||
internal readonly ref struct SeStringContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SeStringBuilder"/> to append text and macros to.
|
||||
/// </summary>
|
||||
internal readonly SeStringBuilder Builder;
|
||||
|
||||
/// <summary>
|
||||
/// A list of local parameters.
|
||||
/// </summary>
|
||||
internal readonly Span<SeStringParameter> LocalParameters;
|
||||
|
||||
/// <summary>
|
||||
/// The target language, used for sheet lookups.
|
||||
/// </summary>
|
||||
internal readonly ClientLanguage Language;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringContext"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="SeStringBuilder"/> to append text and macros to.</param>
|
||||
/// <param name="localParameters">A list of local parameters.</param>
|
||||
/// <param name="language">The target language, used for sheet lookups.</param>
|
||||
internal SeStringContext(SeStringBuilder builder, Span<SeStringParameter> localParameters, ClientLanguage language)
|
||||
{
|
||||
this.Builder = builder;
|
||||
this.LocalParameters = localParameters;
|
||||
this.Language = language;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Globalization.CultureInfo"/> of the current target <see cref="Language"/>.
|
||||
/// </summary>
|
||||
internal CultureInfo CultureInfo => Localization.GetCultureInfoFromLangCode(this.Language.ToCode());
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a number from the local parameters at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index in the <see cref="LocalParameters"/> list.</param>
|
||||
/// <param name="value">The local parameter number.</param>
|
||||
/// <returns><c>true</c> if the local parameters list contained a parameter at given index, <c>false</c> otherwise.</returns>
|
||||
internal bool TryGetLNum(int index, out uint value)
|
||||
{
|
||||
if (index >= 0 && this.LocalParameters.Length > index)
|
||||
{
|
||||
value = this.LocalParameters[index].UIntValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a string from the local parameters at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index in the <see cref="LocalParameters"/> list.</param>
|
||||
/// <param name="value">The local parameter string.</param>
|
||||
/// <returns><c>true</c> if the local parameters list contained a parameter at given index, <c>false</c> otherwise.</returns>
|
||||
internal bool TryGetLStr(int index, out ReadOnlySeString value)
|
||||
{
|
||||
if (index >= 0 && this.LocalParameters.Length > index)
|
||||
{
|
||||
value = this.LocalParameters[index].StringValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
49
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs
Normal file
49
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
namespace Dalamud.Game.Text.Evaluator.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// An enum providing additional information about the sheet redirect.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum SheetRedirectFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a sheet related with items.
|
||||
/// </summary>
|
||||
Item = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to the EventItem sheet.
|
||||
/// </summary>
|
||||
EventItem = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a high quality item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append Addon#9.
|
||||
/// </remarks>
|
||||
HighQuality = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a collectible item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append Addon#150.
|
||||
/// </remarks>
|
||||
Collectible = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to a sheet related with actions.
|
||||
/// </summary>
|
||||
Action = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Resolved to the Action sheet.
|
||||
/// </summary>
|
||||
ActionSheet = 32,
|
||||
}
|
||||
232
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
Normal file
232
Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
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.Order;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.PartyContent) when colIndex == 2:
|
||||
{
|
||||
sheetName = nameof(LSheets.ContentFinderCondition);
|
||||
colIndex = 43;
|
||||
|
||||
if (this.dataManager.GetExcelSheet<LSheets.PartyContent>().TryGetRow(rowId, out var row))
|
||||
rowId = row.ContentFinderCondition.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.PublicContent) when colIndex == 3:
|
||||
{
|
||||
sheetName = nameof(LSheets.ContentFinderCondition);
|
||||
colIndex = 43;
|
||||
|
||||
if (this.dataManager.GetExcelSheet<LSheets.PublicContent>().TryGetRow(rowId, out var row))
|
||||
rowId = row.ContentFinderCondition.RowId;
|
||||
break;
|
||||
}
|
||||
|
||||
case nameof(LSheets.AkatsukiNote):
|
||||
{
|
||||
sheetName = nameof(LSheets.AkatsukiNoteString);
|
||||
colIndex = 0;
|
||||
|
||||
if (this.dataManager.Excel.GetSubrowSheet<LSheets.AkatsukiNote>().TryGetRow(rowId, out var row))
|
||||
rowId = (uint)row[0].Unknown2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
1995
Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
Normal file
1995
Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
Normal file
File diff suppressed because it is too large
Load diff
79
Dalamud/Game/Text/Evaluator/SeStringParameter.cs
Normal file
79
Dalamud/Game/Text/Evaluator/SeStringParameter.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Globalization;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||
using LSeString = Lumina.Text.SeString;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper for a local parameter, holding either a number or a string.
|
||||
/// </summary>
|
||||
public readonly struct SeStringParameter
|
||||
{
|
||||
private readonly uint num;
|
||||
private readonly ReadOnlySeString str;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a number parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The number value.</param>
|
||||
public SeStringParameter(uint value)
|
||||
{
|
||||
this.num = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a string parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value.</param>
|
||||
public SeStringParameter(ReadOnlySeString value)
|
||||
{
|
||||
this.str = value;
|
||||
this.IsString = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringParameter"/> struct for a string parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value.</param>
|
||||
public SeStringParameter(string value)
|
||||
{
|
||||
this.str = new ReadOnlySeString(value);
|
||||
this.IsString = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the backing type of this parameter is a string.
|
||||
/// </summary>
|
||||
public bool IsString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a numeric value.
|
||||
/// </summary>
|
||||
public uint UIntValue =>
|
||||
!this.IsString
|
||||
? this.num
|
||||
: uint.TryParse(this.str.ExtractText(), out var value) ? value : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string value.
|
||||
/// </summary>
|
||||
public ReadOnlySeString StringValue =>
|
||||
this.IsString ? this.str : new(this.num.ToString("D", CultureInfo.InvariantCulture));
|
||||
|
||||
public static implicit operator SeStringParameter(int value) => new((uint)value);
|
||||
|
||||
public static implicit operator SeStringParameter(uint value) => new(value);
|
||||
|
||||
public static implicit operator SeStringParameter(ReadOnlySeString value) => new(value);
|
||||
|
||||
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
||||
|
||||
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
|
||||
|
||||
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
|
||||
|
||||
public static implicit operator SeStringParameter(string value) => new(value);
|
||||
}
|
||||
17
Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs
Normal file
17
Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.English"/>.
|
||||
/// </summary>
|
||||
public enum EnglishArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Indefinite article (a, an).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Definite article (the).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
}
|
||||
32
Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs
Normal file
32
Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.French"/>.
|
||||
/// </summary>
|
||||
public enum FrenchArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Indefinite article (une, des).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Definite article (le, la, les).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (mon, mes).
|
||||
/// </summary>
|
||||
PossessiveFirstPerson = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (ton, tes).
|
||||
/// </summary>
|
||||
PossessiveSecondPerson = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Possessive article (son, ses).
|
||||
/// </summary>
|
||||
PossessiveThirdPerson = 5,
|
||||
}
|
||||
37
Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs
Normal file
37
Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.German"/>.
|
||||
/// </summary>
|
||||
public enum GermanArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Unbestimmter Artikel (ein, eine, etc.).
|
||||
/// </summary>
|
||||
Indefinite = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Bestimmter Artikel (der, die, das, etc.).
|
||||
/// </summary>
|
||||
Definite = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Possessivartikel "dein" (dein, deine, etc.).
|
||||
/// </summary>
|
||||
Possessive = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Negativartikel "kein" (kein, keine, etc.).
|
||||
/// </summary>
|
||||
Negative = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Nullartikel.
|
||||
/// </summary>
|
||||
ZeroArticle = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrativpronomen "dieser" (dieser, diese, etc.).
|
||||
/// </summary>
|
||||
Demonstrative = 6,
|
||||
}
|
||||
17
Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs
Normal file
17
Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Article types for <see cref="ClientLanguage.Japanese"/>.
|
||||
/// </summary>
|
||||
public enum JapaneseArticleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Near listener (それら).
|
||||
/// </summary>
|
||||
NearListener = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Distant from both speaker and listener (あれら).
|
||||
/// </summary>
|
||||
Distant = 2,
|
||||
}
|
||||
73
Dalamud/Game/Text/Noun/NounParams.cs
Normal file
73
Dalamud/Game/Text/Noun/NounParams.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using Dalamud.Game.Text.Noun.Enums;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSheets = Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Text.Noun;
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for noun processing.
|
||||
/// </summary>
|
||||
internal record struct NounParams()
|
||||
{
|
||||
/// <summary>
|
||||
/// The language of the sheet to be processed.
|
||||
/// </summary>
|
||||
public required ClientLanguage Language;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the sheet containing the row to process.
|
||||
/// </summary>
|
||||
public required string SheetName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The row id within the sheet to process.
|
||||
/// </summary>
|
||||
public required uint RowId;
|
||||
|
||||
/// <summary>
|
||||
/// The quantity of the entity (default is 1). Used to determine grammatical number (e.g., singular or plural).
|
||||
/// </summary>
|
||||
public int Quantity = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The article type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Depending on the <see cref="Language"/>, this has different meanings.<br/>
|
||||
/// See <see cref="JapaneseArticleType"/>, <see cref="GermanArticleType"/>, <see cref="FrenchArticleType"/>, <see cref="EnglishArticleType"/>.
|
||||
/// </remarks>
|
||||
public int ArticleType = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The grammatical case (e.g., Nominative, Genitive, Dative, Accusative) used for German texts (default is 0).
|
||||
/// </summary>
|
||||
public int GrammaticalCase = 0;
|
||||
|
||||
/// <summary>
|
||||
/// An optional string that is placed in front of the text that should be linked, such as item names (default is an empty string; the game uses "//").
|
||||
/// </summary>
|
||||
public ReadOnlySeString LinkMarker = default;
|
||||
|
||||
/// <summary>
|
||||
/// An indicator that this noun will be processed from an Action sheet. Only used for German texts.
|
||||
/// </summary>
|
||||
public bool IsActionSheet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the column offset.
|
||||
/// </summary>
|
||||
public readonly int ColumnOffset => this.SheetName switch
|
||||
{
|
||||
// See "E8 ?? ?? ?? ?? 44 8B 6B 08"
|
||||
nameof(LSheets.BeastTribe) => 10,
|
||||
nameof(LSheets.DeepDungeonItem) => 1,
|
||||
nameof(LSheets.DeepDungeonEquipment) => 1,
|
||||
nameof(LSheets.DeepDungeonMagicStone) => 1,
|
||||
nameof(LSheets.DeepDungeonDemiclone) => 1,
|
||||
nameof(LSheets.Glasses) => 4,
|
||||
nameof(LSheets.GlassesStyle) => 15,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
461
Dalamud/Game/Text/Noun/NounProcessor.cs
Normal file
461
Dalamud/Game/Text/Noun/NounProcessor.cs
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.Noun.Enums;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using LSheets = Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Text.Noun;
|
||||
|
||||
/*
|
||||
Attributive sheet:
|
||||
Japanese:
|
||||
Unknown0 = Singular Demonstrative
|
||||
Unknown1 = Plural Demonstrative
|
||||
English:
|
||||
Unknown2 = Article before a singular noun beginning with a consonant sound
|
||||
Unknown3 = Article before a generic noun beginning with a consonant sound
|
||||
Unknown4 = N/A
|
||||
Unknown5 = Article before a singular noun beginning with a vowel sound
|
||||
Unknown6 = Article before a generic noun beginning with a vowel sound
|
||||
Unknown7 = N/A
|
||||
German:
|
||||
Unknown8 = Nominative Masculine
|
||||
Unknown9 = Nominative Feminine
|
||||
Unknown10 = Nominative Neutral
|
||||
Unknown11 = Nominative Plural
|
||||
Unknown12 = Genitive Masculine
|
||||
Unknown13 = Genitive Feminine
|
||||
Unknown14 = Genitive Neutral
|
||||
Unknown15 = Genitive Plural
|
||||
Unknown16 = Dative Masculine
|
||||
Unknown17 = Dative Feminine
|
||||
Unknown18 = Dative Neutral
|
||||
Unknown19 = Dative Plural
|
||||
Unknown20 = Accusative Masculine
|
||||
Unknown21 = Accusative Feminine
|
||||
Unknown22 = Accusative Neutral
|
||||
Unknown23 = Accusative Plural
|
||||
French (unsure):
|
||||
Unknown24 = Singular Article
|
||||
Unknown25 = Singular Masculine Article
|
||||
Unknown26 = Plural Masculine Article
|
||||
Unknown27 = ?
|
||||
Unknown28 = ?
|
||||
Unknown29 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown30 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown31 = ?
|
||||
Unknown32 = ?
|
||||
Unknown33 = Singular Feminine Article
|
||||
Unknown34 = Plural Feminine Article
|
||||
Unknown35 = ?
|
||||
Unknown36 = ?
|
||||
Unknown37 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown38 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h
|
||||
Unknown39 = ?
|
||||
Unknown40 = ?
|
||||
|
||||
Placeholders:
|
||||
[t] = article or grammatical gender (EN: the, DE: der, die, das)
|
||||
[n] = amount (number)
|
||||
[a] = declension
|
||||
[p] = plural
|
||||
[pa] = ?
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to process texts from sheets containing grammatical placeholders.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NounProcessor : IServiceType
|
||||
{
|
||||
// column names from ExdSchema, most likely incorrect
|
||||
private const int SingularColumnIdx = 0;
|
||||
private const int AdjectiveColumnIdx = 1;
|
||||
private const int PluralColumnIdx = 2;
|
||||
private const int PossessivePronounColumnIdx = 3;
|
||||
private const int StartsWithVowelColumnIdx = 4;
|
||||
private const int Unknown5ColumnIdx = 5; // probably used in Chinese texts
|
||||
private const int PronounColumnIdx = 6;
|
||||
private const int ArticleColumnIdx = 7;
|
||||
|
||||
private static readonly ModuleLog Log = new("NounProcessor");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private readonly ConcurrentDictionary<NounParams, ReadOnlySeString> cache = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NounProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a specific row from a sheet and generates a formatted string based on grammatical and language-specific rules.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
public ReadOnlySeString ProcessNoun(NounParams nounParams)
|
||||
{
|
||||
if (nounParams.GrammaticalCase < 0 || nounParams.GrammaticalCase > 5)
|
||||
return default;
|
||||
|
||||
if (this.cache.TryGetValue(nounParams, out var value))
|
||||
return value;
|
||||
|
||||
var output = nounParams.Language switch
|
||||
{
|
||||
ClientLanguage.Japanese => this.ResolveNounJa(nounParams),
|
||||
ClientLanguage.English => this.ResolveNounEn(nounParams),
|
||||
ClientLanguage.German => this.ResolveNounDe(nounParams),
|
||||
ClientLanguage.French => this.ResolveNounFr(nounParams),
|
||||
_ => default,
|
||||
};
|
||||
|
||||
this.cache.TryAdd(nounParams, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in Japanese text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounJa.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounJa(NounParams nounParams)
|
||||
{
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
// Ko-So-A-Do
|
||||
var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0);
|
||||
if (!ksad.IsEmpty)
|
||||
{
|
||||
builder.Append(ksad);
|
||||
|
||||
if (nounParams.Quantity > 1)
|
||||
{
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
var ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in English text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounEn.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounEn(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = StartsWithVowelColumnIdx
|
||||
a1->Offsets[3] = PossessivePronounColumnIdx
|
||||
a1->Offsets[4] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn;
|
||||
if (isProperNoun == 0)
|
||||
{
|
||||
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
||||
var startsWithVowel = startsWithVowelColumn >= 0
|
||||
? row.ReadInt8Column(startsWithVowelColumn)
|
||||
: ~startsWithVowelColumn;
|
||||
|
||||
var articleColumn = startsWithVowel + (2 * (startsWithVowel + 1));
|
||||
var grammaticalNumberColumnOffset = nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx;
|
||||
var article = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||
.ReadStringColumn(articleColumn + grammaticalNumberColumnOffset);
|
||||
if (!article.IsEmpty)
|
||||
builder.Append(article);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
}
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
var ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in German text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounDe.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounDe(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = PronounColumnIdx
|
||||
a1->Offsets[3] = AdjectiveColumnIdx
|
||||
a1->Offsets[4] = PossessivePronounColumnIdx
|
||||
a1->Offsets[5] = Unknown5ColumnIdx
|
||||
a1->Offsets[6] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
ReadOnlySeString ross;
|
||||
|
||||
if (nounParams.IsActionSheet)
|
||||
{
|
||||
builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase));
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
||||
var genderIndex = genderIndexColumn >= 0 ? row.ReadInt8Column(genderIndexColumn) : ~genderIndexColumn;
|
||||
|
||||
var articleIndexColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var articleIndex = articleIndexColumn >= 0 ? row.ReadInt8Column(articleIndexColumn) : ~articleIndexColumn;
|
||||
|
||||
var caseColumnOffset = (4 * nounParams.GrammaticalCase) + 8;
|
||||
|
||||
var caseRowOffsetColumn = nounParams.ColumnOffset + (nounParams.Quantity == 1 ? AdjectiveColumnIdx : PossessivePronounColumnIdx);
|
||||
var caseRowOffset = caseRowOffsetColumn >= 0
|
||||
? row.ReadInt8Column(caseRowOffsetColumn)
|
||||
: (sbyte)~caseRowOffsetColumn;
|
||||
|
||||
if (nounParams.Quantity != 1)
|
||||
genderIndex = 3;
|
||||
|
||||
var hasT = false;
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
{
|
||||
hasT = text.ContainsText("[t]"u8);
|
||||
|
||||
if (articleIndex == 0 && !hasT)
|
||||
{
|
||||
var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||
.ReadStringColumn(caseColumnOffset + genderIndex); // Genus
|
||||
if (!grammaticalGender.IsEmpty)
|
||||
builder.Append(grammaticalGender);
|
||||
}
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
builder.Append(text);
|
||||
|
||||
var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26))
|
||||
.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
if (builder.ContainsText("[p]"u8))
|
||||
builder.ReplaceText("[p]"u8, plural);
|
||||
else
|
||||
builder.Append(plural);
|
||||
|
||||
if (hasT)
|
||||
{
|
||||
var article =
|
||||
attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel
|
||||
builder.ReplaceText("[t]"u8, article);
|
||||
}
|
||||
}
|
||||
|
||||
var pa = attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
builder.ReplaceText("[pa]"u8, pa);
|
||||
|
||||
RawRow declensionRow;
|
||||
|
||||
declensionRow = (GermanArticleType)nounParams.ArticleType switch
|
||||
{
|
||||
// Schwache Flexion eines Adjektivs?!
|
||||
GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25),
|
||||
_ when hasT => attributiveSheet.GetRow(25),
|
||||
|
||||
// Starke Deklination
|
||||
GermanArticleType.ZeroArticle => attributiveSheet.GetRow(38),
|
||||
|
||||
// Gemischte Deklination
|
||||
GermanArticleType.Definite => attributiveSheet.GetRow(37),
|
||||
|
||||
// Starke Flexion eines Artikels?!
|
||||
GermanArticleType.Indefinite or GermanArticleType.Negative => attributiveSheet.GetRow(26),
|
||||
_ => attributiveSheet.GetRow(26),
|
||||
};
|
||||
|
||||
var declension = declensionRow.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||
builder.ReplaceText("[a]"u8, declension);
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves noun placeholders in French text.
|
||||
/// </summary>
|
||||
/// <param name="nounParams">Parameters for processing.</param>
|
||||
/// <returns>A ReadOnlySeString representing the processed text.</returns>
|
||||
/// <remarks>
|
||||
/// This is a C# implementation of Component::Text::Localize::NounFr.Resolve.
|
||||
/// </remarks>
|
||||
private ReadOnlySeString ResolveNounFr(NounParams nounParams)
|
||||
{
|
||||
/*
|
||||
a1->Offsets[0] = SingularColumnIdx
|
||||
a1->Offsets[1] = PluralColumnIdx
|
||||
a1->Offsets[2] = StartsWithVowelColumnIdx
|
||||
a1->Offsets[3] = PronounColumnIdx
|
||||
a1->Offsets[4] = Unknown5ColumnIdx
|
||||
a1->Offsets[5] = ArticleColumnIdx
|
||||
*/
|
||||
|
||||
var sheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nounParams.SheetName);
|
||||
if (!sheet.TryGetRow(nounParams.RowId, out var row))
|
||||
{
|
||||
Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId);
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||
|
||||
var builder = LSeStringBuilder.SharedPool.Get();
|
||||
ReadOnlySeString ross;
|
||||
|
||||
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
||||
var startsWithVowel = startsWithVowelColumn >= 0
|
||||
? row.ReadInt8Column(startsWithVowelColumn)
|
||||
: ~startsWithVowelColumn;
|
||||
|
||||
var pronounColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
||||
var pronoun = pronounColumn >= 0 ? row.ReadInt8Column(pronounColumn) : ~pronounColumn;
|
||||
|
||||
var articleColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||
var article = articleColumn >= 0 ? row.ReadInt8Column(articleColumn) : ~articleColumn;
|
||||
|
||||
var v20 = 4 * (startsWithVowel + 6 + (2 * pronoun));
|
||||
|
||||
if (article != 0)
|
||||
{
|
||||
var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20);
|
||||
if (!v21.IsEmpty)
|
||||
builder.Append(v21);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
|
||||
if (nounParams.Quantity <= 1)
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
|
||||
var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx);
|
||||
if (v17 != 0 && (nounParams.Quantity > 1 || v17 == 2))
|
||||
{
|
||||
var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2);
|
||||
if (!v29.IsEmpty)
|
||||
{
|
||||
builder.Append(v29);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3));
|
||||
if (!v27.IsEmpty)
|
||||
builder.Append(v27);
|
||||
|
||||
if (!nounParams.LinkMarker.IsEmpty)
|
||||
builder.Append(nounParams.LinkMarker);
|
||||
|
||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx);
|
||||
if (!text.IsEmpty)
|
||||
builder.Append(text);
|
||||
}
|
||||
|
||||
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||
|
||||
ross = builder.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(builder);
|
||||
return ross;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ using System.Linq;
|
|||
using System.Text;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -73,6 +75,7 @@ public class ItemPayload : Payload
|
|||
/// <summary>
|
||||
/// Kinds of items that can be fetched from this payload.
|
||||
/// </summary>
|
||||
[Api12ToDo("Move this out of ItemPayload. It's used in other classes too.")]
|
||||
public enum ItemKind : uint
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -121,7 +124,7 @@ public class ItemPayload : Payload
|
|||
/// Gets the actual item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ItemId => GetAdjustedId(this.rawItemId).ItemId;
|
||||
public uint ItemId => ItemUtil.GetBaseId(this.rawItemId).ItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw, unadjusted item ID of this payload.
|
||||
|
|
@ -161,7 +164,7 @@ public class ItemPayload : Payload
|
|||
/// <returns>The created item payload.</returns>
|
||||
public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null)
|
||||
{
|
||||
var (id, kind) = GetAdjustedId(rawItemId);
|
||||
var (id, kind) = ItemUtil.GetBaseId(rawItemId);
|
||||
return new ItemPayload(id, kind, displayNameOverride);
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +233,7 @@ public class ItemPayload : Payload
|
|||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.rawItemId = GetInteger(reader);
|
||||
this.Kind = GetAdjustedId(this.rawItemId).Kind;
|
||||
this.Kind = ItemUtil.GetBaseId(this.rawItemId).Kind;
|
||||
|
||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
||||
{
|
||||
|
|
@ -255,15 +258,4 @@ public class ItemPayload : Payload
|
|||
this.displayName = Encoding.UTF8.GetString(itemNameBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId)
|
||||
{
|
||||
return rawItemId switch
|
||||
{
|
||||
> 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible),
|
||||
> 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq),
|
||||
> 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted
|
||||
_ => (rawItemId, ItemKind.Normal),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -47,11 +47,13 @@ internal class DataWindow : Window, IDisposable
|
|||
new KeyStateWidget(),
|
||||
new MarketBoardWidget(),
|
||||
new NetworkMonitorWidget(),
|
||||
new NounProcessorWidget(),
|
||||
new ObjectTableWidget(),
|
||||
new PartyListWidget(),
|
||||
new PluginIpcWidget(),
|
||||
new SeFontTestWidget(),
|
||||
new ServicesWidget(),
|
||||
new SeStringCreatorWidget(),
|
||||
new SeStringRendererTestWidget(),
|
||||
new StartInfoWidget(),
|
||||
new TargetWidget(),
|
||||
|
|
@ -68,6 +70,7 @@ internal class DataWindow : Window, IDisposable
|
|||
private bool isExcept;
|
||||
private bool selectionCollapsed;
|
||||
private IDataWindowWidget currentWidget;
|
||||
private bool isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataWindow"/> class.
|
||||
|
|
@ -81,8 +84,6 @@ internal class DataWindow : Window, IDisposable
|
|||
this.RespectCloseHotkey = false;
|
||||
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
||||
this.currentWidget = this.orderedModules.First();
|
||||
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -91,6 +92,7 @@ internal class DataWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
{
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -183,6 +185,7 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync))
|
||||
{
|
||||
this.isLoaded = false;
|
||||
this.Load();
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +239,11 @@ internal class DataWindow : Window, IDisposable
|
|||
|
||||
private void Load()
|
||||
{
|
||||
if (this.isLoaded)
|
||||
return;
|
||||
|
||||
this.isLoaded = true;
|
||||
|
||||
foreach (var widget in this.modules)
|
||||
{
|
||||
widget.Load();
|
||||
|
|
|
|||
34
Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs
Normal file
34
Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Common utilities used in Widgets.
|
||||
/// </summary>
|
||||
internal class WidgetUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws text that can be copied on click.
|
||||
/// </summary>
|
||||
/// <param name="text">The text shown and to be copied.</param>
|
||||
/// <param name="tooltipText">The text in the tooltip.</param>
|
||||
internal static void DrawCopyableText(string text, string tooltipText = "Copy")
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,24 +57,6 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
this.DrawExtendArrayTab();
|
||||
}
|
||||
|
||||
private static void DrawCopyableText(string text, string tooltipText)
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawArrayList(Type? arrayType, int arrayCount, short* arrayKeys, AtkArrayData** arrays, ref int selectedIndex)
|
||||
{
|
||||
using var table = ImRaii.Table("ArkArrayTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Borders, new Vector2(300, -1));
|
||||
|
|
@ -162,7 +144,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Address: ");
|
||||
ImGui.SameLine(0, 0);
|
||||
DrawCopyableText($"0x{(nint)array:X}", "Copy address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array:X}", "Copy address");
|
||||
|
||||
if (array->SubscribedAddonsCount > 0)
|
||||
{
|
||||
|
|
@ -238,22 +220,22 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
var ptr = &array->IntArray[i];
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address");
|
||||
|
||||
ImGui.TableNextColumn(); // Integer
|
||||
DrawCopyableText((*ptr).ToString(), "Copy value");
|
||||
WidgetUtil.DrawCopyableText((*ptr).ToString(), "Copy value");
|
||||
|
||||
ImGui.TableNextColumn(); // Short
|
||||
DrawCopyableText((*(short*)ptr).ToString(), "Copy as short");
|
||||
WidgetUtil.DrawCopyableText((*(short*)ptr).ToString(), "Copy as short");
|
||||
|
||||
ImGui.TableNextColumn(); // Byte
|
||||
DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte");
|
||||
WidgetUtil.DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte");
|
||||
|
||||
ImGui.TableNextColumn(); // Float
|
||||
DrawCopyableText((*(float*)ptr).ToString(), "Copy as float");
|
||||
WidgetUtil.DrawCopyableText((*(float*)ptr).ToString(), "Copy as float");
|
||||
|
||||
ImGui.TableNextColumn(); // Hex
|
||||
DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex");
|
||||
WidgetUtil.DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,11 +315,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
if (this.showTextAddress)
|
||||
{
|
||||
if (!isNull)
|
||||
DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address");
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn(); // Managed
|
||||
|
|
@ -351,7 +333,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
{
|
||||
if (this.showMacroString)
|
||||
{
|
||||
DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text");
|
||||
WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -408,11 +390,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted($"#{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address");
|
||||
|
||||
ImGui.TableNextColumn(); // Pointer
|
||||
if (!isNull)
|
||||
DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address");
|
||||
WidgetUtil.DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted($"#{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Address
|
||||
DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address");
|
||||
WidgetUtil.DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address");
|
||||
|
||||
ImGui.TableNextColumn(); // FateId
|
||||
DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)");
|
||||
WidgetUtil.DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)");
|
||||
|
||||
ImGui.TableNextColumn(); // State
|
||||
ImGui.TextUnformatted(fate.State.ToString());
|
||||
|
|
@ -140,7 +140,7 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
|
||||
ImGui.TableNextColumn(); // Name
|
||||
|
||||
DrawCopyableText(fate.Name.ToString(), "Click to copy Name");
|
||||
WidgetUtil.DrawCopyableText(fate.Name.ToString(), "Click to copy Name");
|
||||
|
||||
ImGui.TableNextColumn(); // Progress
|
||||
ImGui.TextUnformatted($"{fate.Progress}%");
|
||||
|
|
@ -156,28 +156,10 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted(fate.HasBonus.ToString());
|
||||
|
||||
ImGui.TableNextColumn(); // Position
|
||||
DrawCopyableText(fate.Position.ToString(), "Click to copy Position");
|
||||
WidgetUtil.DrawCopyableText(fate.Position.ToString(), "Click to copy Position");
|
||||
|
||||
ImGui.TableNextColumn(); // Radius
|
||||
DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawCopyableText(string text, string tooltipText)
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(text);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(tooltipText);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
WidgetUtil.DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
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"),
|
||||
|
||||
new(nameof(LSheets.Action), 45, ClientLanguage.German, 1, (int)FrenchArticleType.Indefinite, 1, "Blumenflüsterer IV"),
|
||||
];
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -50,6 +50,9 @@ internal class SelfTestWindow : Window
|
|||
new DutyStateAgingStep(),
|
||||
new GameConfigAgingStep(),
|
||||
new MarketBoardAgingStep(),
|
||||
new SheetRedirectResolverAgingStep(),
|
||||
new NounProcessorAgingStep(),
|
||||
new SeStringEvaluatorAgingStep(),
|
||||
new LogoutEventAgingStep(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
79
Dalamud/Plugin/Services/ISeStringEvaluator.cs
Normal file
79
Dalamud/Plugin/Services/ISeStringEvaluator.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.Text.Evaluator;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a service for retrieving localized text for various in-game entities.
|
||||
/// </summary>
|
||||
[Experimental("SeStringEvaluator")]
|
||||
public interface ISeStringEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates macros in a <see cref="ReadOnlySeString"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">The string containing macros.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString Evaluate(ReadOnlySeString str, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in a <see cref="ReadOnlySeStringSpan"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">The string containing macros.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString Evaluate(ReadOnlySeStringSpan str, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in text from the Addon sheet.
|
||||
/// </summary>
|
||||
/// <param name="addonId">The row id of the Addon sheet.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString EvaluateFromAddon(uint addonId, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in text from the Lobby sheet.
|
||||
/// </summary>
|
||||
/// <param name="lobbyId">The row id of the Lobby sheet.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString EvaluateFromLobby(uint lobbyId, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in text from the LogMessage sheet.
|
||||
/// </summary>
|
||||
/// <param name="logMessageId">The row id of the LogMessage sheet.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString EvaluateFromLogMessage(uint logMessageId, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates ActStr from the given ActionKind and id.
|
||||
/// </summary>
|
||||
/// <param name="actionKind">The ActionKind.</param>
|
||||
/// <param name="id">The action id.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>The name of the action.</returns>
|
||||
string EvaluateActStr(ActionKind actionKind, uint id, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates ObjStr from the given ObjectKind and id.
|
||||
/// </summary>
|
||||
/// <param name="objectKind">The ObjectKind.</param>
|
||||
/// <param name="id">The object id.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>The singular name of the object.</returns>
|
||||
string EvaluateObjStr(ObjectKind objectKind, uint id, ClientLanguage? language = null);
|
||||
}
|
||||
26
Dalamud/Utility/ActionKindExtensions.cs
Normal file
26
Dalamud/Utility/ActionKindExtensions.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using Dalamud.Game;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ActionKind"/> enum.
|
||||
/// </summary>
|
||||
public static class ActionKindExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the id of an ActionKind to the id used in the ActStr sheet redirect.
|
||||
/// </summary>
|
||||
/// <param name="actionKind">The ActionKind this id is for.</param>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>An id that can be used in the ActStr sheet redirect.</returns>
|
||||
public static uint GetActStrId(this ActionKind actionKind, uint id)
|
||||
{
|
||||
// See "83 F9 0D 76 0B"
|
||||
var idx = (uint)actionKind;
|
||||
|
||||
if (idx is <= 13 or 19 or 20)
|
||||
return id + (1000000 * idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,40 @@ public static class ClientLanguageExtensions
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(language)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language code from a ClientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="value">The ClientLanguage to convert.</param>
|
||||
/// <returns>The language code (ja, en, de, fr).</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">An exception that is thrown when no valid ClientLanguage was given.</exception>
|
||||
public static string ToCode(this ClientLanguage value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
ClientLanguage.Japanese => "ja",
|
||||
ClientLanguage.English => "en",
|
||||
ClientLanguage.German => "de",
|
||||
ClientLanguage.French => "fr",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(value)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientLanguage from a language code.
|
||||
/// </summary>
|
||||
/// <param name="value">The language code to convert (ja, en, de, fr).</param>
|
||||
/// <returns>The ClientLanguage.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">An exception that is thrown when no valid language code was given.</exception>
|
||||
public static ClientLanguage ToClientLanguage(this string value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
"ja" => ClientLanguage.Japanese,
|
||||
"en" => ClientLanguage.English,
|
||||
"de" => ClientLanguage.German,
|
||||
"fr" => ClientLanguage.French,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(value)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
159
Dalamud/Utility/ItemUtil.cs
Normal file
159
Dalamud/Utility/ItemUtil.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using static Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload;
|
||||
using Lumina.Text;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utilities related to Items.
|
||||
/// </summary>
|
||||
internal static class ItemUtil
|
||||
{
|
||||
private static int? eventItemRowCount;
|
||||
|
||||
/// <summary>Converts raw item ID to item ID with its classification.</summary>
|
||||
/// <param name="rawItemId">Raw item ID.</param>
|
||||
/// <returns>Item ID and its classification.</returns>
|
||||
internal static (uint ItemId, ItemKind Kind) GetBaseId(uint rawItemId)
|
||||
{
|
||||
if (IsEventItem(rawItemId)) return (rawItemId, ItemKind.EventItem); // EventItem IDs are NOT adjusted
|
||||
if (IsHighQuality(rawItemId)) return (rawItemId - 1_000_000, ItemKind.Hq);
|
||||
if (IsCollectible(rawItemId)) return (rawItemId - 500_000, ItemKind.Collectible);
|
||||
return (rawItemId, ItemKind.Normal);
|
||||
}
|
||||
|
||||
/// <summary>Converts item ID with its classification to raw item ID.</summary>
|
||||
/// <param name="itemId">Item ID.</param>
|
||||
/// <param name="kind">Item classification.</param>
|
||||
/// <returns>Raw Item ID.</returns>
|
||||
internal static uint GetRawId(uint itemId, ItemKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
ItemKind.Collectible when itemId < 500_000 => itemId + 500_000,
|
||||
ItemKind.Hq when itemId < 1_000_000 => itemId + 1_000_000,
|
||||
ItemKind.EventItem => itemId, // EventItem IDs are not adjusted
|
||||
_ => itemId,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the item id belongs to a normal item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id to check.</param>
|
||||
/// <returns><c>true</c> when the item id belongs to a normal item.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsNormalItem(uint itemId)
|
||||
{
|
||||
return itemId < 500_000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the item id belongs to a collectible item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id to check.</param>
|
||||
/// <returns><c>true</c> when the item id belongs to a collectible item.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsCollectible(uint itemId)
|
||||
{
|
||||
return itemId is >= 500_000 and < 1_000_000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the item id belongs to a high quality item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id to check.</param>
|
||||
/// <returns><c>true</c> when the item id belongs to a high quality item.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsHighQuality(uint itemId)
|
||||
{
|
||||
return itemId is >= 1_000_000 and < 2_000_000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the item id belongs to an event item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id to check.</param>
|
||||
/// <returns><c>true</c> when the item id belongs to an event item.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsEventItem(uint itemId)
|
||||
{
|
||||
return itemId >= 2_000_000 && itemId - 2_000_000 < (eventItemRowCount ??= Service<DataManager>.Get().GetExcelSheet<EventItem>().Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of an item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The raw item id.</param>
|
||||
/// <param name="includeIcon">Whether to include the High Quality or Collectible icon.</param>
|
||||
/// <param name="language">An optional client language override.</param>
|
||||
/// <returns>The item name.</returns>
|
||||
internal static ReadOnlySeString GetItemName(uint itemId, bool includeIcon = true, ClientLanguage? language = null)
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
if (IsEventItem(itemId))
|
||||
{
|
||||
return dataManager
|
||||
.GetExcelSheet<EventItem>(language)
|
||||
.TryGetRow(itemId, out var eventItem)
|
||||
? eventItem.Name
|
||||
: default;
|
||||
}
|
||||
|
||||
var (baseId, kind) = GetBaseId(itemId);
|
||||
|
||||
if (!dataManager
|
||||
.GetExcelSheet<Item>(language)
|
||||
.TryGetRow(baseId, out var item))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!includeIcon || kind is not (ItemKind.Hq or ItemKind.Collectible))
|
||||
return item.Name;
|
||||
|
||||
var builder = SeStringBuilder.SharedPool.Get();
|
||||
|
||||
builder.Append(item.Name);
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case ItemPayload.ItemKind.Hq:
|
||||
builder.Append($" {(char)SeIconChar.HighQuality}");
|
||||
break;
|
||||
case ItemPayload.ItemKind.Collectible:
|
||||
builder.Append($" {(char)SeIconChar.Collectible}");
|
||||
break;
|
||||
}
|
||||
|
||||
var itemName = builder.ToReadOnlySeString();
|
||||
SeStringBuilder.SharedPool.Return(builder);
|
||||
return itemName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color row id for an item name.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The raw item Id.</param>
|
||||
/// <param name="isEdgeColor">Wheather this color is used as edge color.</param>
|
||||
/// <returns>The Color row id.</returns>
|
||||
internal static uint GetItemRarityColorType(uint itemId, bool isEdgeColor = false)
|
||||
{
|
||||
var rarity = 1u;
|
||||
|
||||
if (!IsEventItem(itemId) && Service<DataManager>.Get().GetExcelSheet<Item>().TryGetRow(GetBaseId(itemId).ItemId, out var item))
|
||||
rarity = item.Rarity;
|
||||
|
||||
return (isEdgeColor ? 548u : 547u) + (rarity * 2u);
|
||||
}
|
||||
}
|
||||
33
Dalamud/Utility/ObjectKindExtensions.cs
Normal file
33
Dalamud/Utility/ObjectKindExtensions.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ObjectKind"/> enum.
|
||||
/// </summary>
|
||||
public static class ObjectKindExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the id of an ObjectKind to the id used in the ObjStr sheet redirect.
|
||||
/// </summary>
|
||||
/// <param name="objectKind">The ObjectKind this id is for.</param>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>An id that can be used in the ObjStr sheet redirect.</returns>
|
||||
public static uint GetObjStrId(this ObjectKind objectKind, uint id)
|
||||
{
|
||||
// See "8D 41 FE 83 F8 0C 77 4D"
|
||||
return objectKind switch
|
||||
{
|
||||
ObjectKind.BattleNpc => id < 1000000 ? id : id - 900000,
|
||||
ObjectKind.EventNpc => id,
|
||||
ObjectKind.Treasure or
|
||||
ObjectKind.Aetheryte or
|
||||
ObjectKind.GatheringPoint or
|
||||
ObjectKind.Companion or
|
||||
ObjectKind.Housing => id + (1000000 * (uint)objectKind) - 2000000,
|
||||
ObjectKind.EventObj => id + (1000000 * (uint)objectKind) - 4000000,
|
||||
ObjectKind.CardStand => id + 3000000,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Linq;
|
||||
|
||||
using Lumina.Text.Parse;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
@ -74,4 +76,154 @@ public static class SeStringExtensions
|
|||
/// <param name="value">character name to validate.</param>
|
||||
/// <returns>indicator if character is name is valid.</returns>
|
||||
public static bool IsValidCharacterName(this DSeString value) => value.ToString().IsValidCharacterName();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ReadOnlySeString"/> contains only text payloads.
|
||||
/// </summary>
|
||||
/// <param name="ross">The <see cref="ReadOnlySeString"/> to check.</param>
|
||||
/// <returns><c>true</c> if the string contains only text payloads; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsTextOnly(this ReadOnlySeString ross)
|
||||
{
|
||||
return ross.AsSpan().IsTextOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ReadOnlySeStringSpan"/> contains only text payloads.
|
||||
/// </summary>
|
||||
/// <param name="rosss">The <see cref="ReadOnlySeStringSpan"/> to check.</param>
|
||||
/// <returns><c>true</c> if the span contains only text payloads; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsTextOnly(this ReadOnlySeStringSpan rosss)
|
||||
{
|
||||
foreach (var payload in rosss)
|
||||
{
|
||||
if (payload.Type != ReadOnlySePayloadType.Text)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ReadOnlySeString"/> contains the specified text.
|
||||
/// </summary>
|
||||
/// <param name="ross">The <see cref="ReadOnlySeString"/> to search.</param>
|
||||
/// <param name="needle">The text to find.</param>
|
||||
/// <returns><c>true</c> if the text is found; otherwise, <c>false</c>.</returns>
|
||||
public static bool ContainsText(this ReadOnlySeString ross, ReadOnlySpan<byte> needle)
|
||||
{
|
||||
return ross.AsSpan().ContainsText(needle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ReadOnlySeStringSpan"/> contains the specified text.
|
||||
/// </summary>
|
||||
/// <param name="rosss">The <see cref="ReadOnlySeStringSpan"/> to search.</param>
|
||||
/// <param name="needle">The text to find.</param>
|
||||
/// <returns><c>true</c> if the text is found; otherwise, <c>false</c>.</returns>
|
||||
public static bool ContainsText(this ReadOnlySeStringSpan rosss, ReadOnlySpan<byte> needle)
|
||||
{
|
||||
foreach (var payload in rosss)
|
||||
{
|
||||
if (payload.Type != ReadOnlySePayloadType.Text)
|
||||
continue;
|
||||
|
||||
if (payload.Body.IndexOf(needle) != -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="LSeStringBuilder"/> contains the specified text.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to search.</param>
|
||||
/// <param name="needle">The text to find.</param>
|
||||
/// <returns><c>true</c> if the text is found; otherwise, <c>false</c>.</returns>
|
||||
public static bool ContainsText(this LSeStringBuilder builder, ReadOnlySpan<byte> needle)
|
||||
{
|
||||
return builder.ToReadOnlySeString().ContainsText(needle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces occurrences of a specified text in a <see cref="ReadOnlySeString"/> with another text.
|
||||
/// </summary>
|
||||
/// <param name="ross">The original string.</param>
|
||||
/// <param name="toFind">The text to find.</param>
|
||||
/// <param name="replacement">The replacement text.</param>
|
||||
/// <returns>A new <see cref="ReadOnlySeString"/> with the replacements made.</returns>
|
||||
public static ReadOnlySeString ReplaceText(
|
||||
this ReadOnlySeString ross,
|
||||
ReadOnlySpan<byte> toFind,
|
||||
ReadOnlySpan<byte> replacement)
|
||||
{
|
||||
if (ross.IsEmpty)
|
||||
return ross;
|
||||
|
||||
var sb = LSeStringBuilder.SharedPool.Get();
|
||||
|
||||
foreach (var payload in ross)
|
||||
{
|
||||
if (payload.Type == ReadOnlySePayloadType.Invalid)
|
||||
continue;
|
||||
|
||||
if (payload.Type != ReadOnlySePayloadType.Text)
|
||||
{
|
||||
sb.Append(payload);
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = payload.Body.Span.IndexOf(toFind);
|
||||
if (index == -1)
|
||||
{
|
||||
sb.Append(payload);
|
||||
continue;
|
||||
}
|
||||
|
||||
var lastIndex = 0;
|
||||
while (index != -1)
|
||||
{
|
||||
sb.Append(payload.Body.Span[lastIndex..index]);
|
||||
|
||||
if (!replacement.IsEmpty)
|
||||
{
|
||||
sb.Append(replacement);
|
||||
}
|
||||
|
||||
lastIndex = index + toFind.Length;
|
||||
index = payload.Body.Span[lastIndex..].IndexOf(toFind);
|
||||
|
||||
if (index != -1)
|
||||
index += lastIndex;
|
||||
}
|
||||
|
||||
sb.Append(payload.Body.Span[lastIndex..]);
|
||||
}
|
||||
|
||||
var output = sb.ToReadOnlySeString();
|
||||
LSeStringBuilder.SharedPool.Return(sb);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces occurrences of a specified text in an <see cref="LSeStringBuilder"/> with another text.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to modify.</param>
|
||||
/// <param name="toFind">The text to find.</param>
|
||||
/// <param name="replacement">The replacement text.</param>
|
||||
public static void ReplaceText(
|
||||
this LSeStringBuilder builder,
|
||||
ReadOnlySpan<byte> toFind,
|
||||
ReadOnlySpan<byte> replacement)
|
||||
{
|
||||
if (toFind.IsEmpty)
|
||||
return;
|
||||
|
||||
var str = builder.ToReadOnlySeString();
|
||||
if (str.IsEmpty)
|
||||
return;
|
||||
|
||||
var replaced = ReplaceText(new ReadOnlySeString(builder.GetViewAsMemory()), toFind, replacement);
|
||||
builder.Clear().Append(replaced);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
|
|
@ -43,4 +44,48 @@ public static class StringExtensions
|
|||
if (!UIGlobals.IsValidPlayerCharacterName(value)) return false;
|
||||
return includeLegacy || value.Length <= 21;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the first character of the string to uppercase while leaving the rest of the string unchanged.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="culture"><inheritdoc cref="string.ToLower(CultureInfo)" path="/param[@name='cultureInfo']"/></param>
|
||||
/// <returns>A new string with the first character converted to uppercase.</returns>
|
||||
[return: NotNullIfNotNull("input")]
|
||||
public static string? FirstCharToUpper(this string? input, CultureInfo? culture = null) =>
|
||||
string.IsNullOrWhiteSpace(input)
|
||||
? input
|
||||
: $"{char.ToUpper(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}";
|
||||
|
||||
/// <summary>
|
||||
/// Converts the first character of the string to lowercase while leaving the rest of the string unchanged.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="culture"><inheritdoc cref="string.ToLower(CultureInfo)" path="/param[@name='cultureInfo']"/></param>
|
||||
/// <returns>A new string with the first character converted to lowercase.</returns>
|
||||
[return: NotNullIfNotNull("input")]
|
||||
public static string? FirstCharToLower(this string? input, CultureInfo? culture = null) =>
|
||||
string.IsNullOrWhiteSpace(input)
|
||||
? input
|
||||
: $"{char.ToLower(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}";
|
||||
|
||||
/// <summary>
|
||||
/// Removes soft hyphen characters (U+00AD) from the input string.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string to remove soft hyphen characters from.</param>
|
||||
/// <returns>A string with all soft hyphens removed.</returns>
|
||||
public static string StripSoftHyphen(this string input) => input.Replace("\u00AD", string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Truncates the given string to the specified maximum number of characters,
|
||||
/// appending an ellipsis if truncation occurs.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to truncate.</param>
|
||||
/// <param name="maxChars">The maximum allowed length of the string.</param>
|
||||
/// <param name="ellipses">The string to append if truncation occurs (defaults to "...").</param>
|
||||
/// <returns>The truncated string, or the original string if no truncation is needed.</returns>
|
||||
public static string? Truncate(this string input, int maxChars, string ellipses = "...")
|
||||
{
|
||||
return string.IsNullOrEmpty(input) || input.Length <= maxChars ? input : input[..maxChars] + ellipses;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue