Add support for sheet payload links (#2282)

* Fix "Print Evaluated" not evaluating with context

* Add support for sheet payload links
This commit is contained in:
Haselnussbomber 2025-05-29 19:37:48 +02:00 committed by GitHub
parent e52e0b6df9
commit 3f3a1f2be1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 183 additions and 70 deletions

View file

@ -18,7 +18,6 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
@ -36,6 +35,8 @@ using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
using AddonSheet = Lumina.Excel.Sheets.Addon;
using PlayerState = FFXIVClientStructs.FFXIV.Client.Game.UI.PlayerState;
using StatusSheet = Lumina.Excel.Sheets.Status;
namespace Dalamud.Game.Text.Evaluator;
@ -727,84 +728,186 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
this.TryResolveUInt(in context, enu.Current, out eColParamValue);
var resolvedSheetName = this.Evaluate(eSheetNameStr, context.LocalParameters, context.Language).ExtractText();
this.sheetRedirectResolver.Resolve(ref resolvedSheetName, ref eRowIdValue, ref eColIndexValue);
var originalRowIdValue = eRowIdValue;
var flags = this.sheetRedirectResolver.Resolve(ref resolvedSheetName, ref eRowIdValue, ref eColIndexValue);
if (string.IsNullOrEmpty(resolvedSheetName))
return false;
if (!this.dataManager.Excel.SheetNames.Contains(resolvedSheetName))
var text = this.FormatSheetValue(context.Language, resolvedSheetName, eRowIdValue, eColIndexValue, eColParamValue);
if (text.IsEmpty)
return false;
if (!this.dataManager.GetExcelSheet<RawRow>(context.Language, resolvedSheetName)
.TryGetRow(eRowIdValue, out var row))
return false;
this.AddSheetRedirectItemDecoration(context, ref text, flags, eRowIdValue);
if (eColIndexValue >= row.Columns.Count)
return false;
if (resolvedSheetName != "DescriptionString")
eColParamValue = originalRowIdValue;
var column = row.Columns[(int)eColIndexValue];
switch (column.Type)
// Note: The link marker symbol is added by RaptureLogMessage, probably somewhere in it's Update function.
// It is not part of this generated link.
this.CreateSheetLink(context, resolvedSheetName, text, eRowIdValue, eColParamValue);
return true;
}
private ReadOnlySeString FormatSheetValue(ClientLanguage language, string sheetName, uint rowId, uint colIndex, uint colParam)
{
if (!this.dataManager.Excel.SheetNames.Contains(sheetName))
return default;
if (!this.dataManager.GetExcelSheet<RawRow>(language, sheetName)
.TryGetRow(rowId, out var row))
return default;
if (colIndex >= row.Columns.Count)
return default;
var column = row.Columns[(int)colIndex];
return column.Type switch
{
case ExcelColumnDataType.String:
context.Builder.Append(this.Evaluate(row.ReadString(column.Offset), [eColParamValue], context.Language));
return true;
case ExcelColumnDataType.Bool:
context.Builder.Append((row.ReadBool(column.Offset) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.Int8:
context.Builder.Append(row.ReadInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.UInt8:
context.Builder.Append(row.ReadUInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.Int16:
context.Builder.Append(row.ReadInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.UInt16:
context.Builder.Append(row.ReadUInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.Int32:
context.Builder.Append(row.ReadInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.UInt32:
context.Builder.Append(row.ReadUInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.Float32:
context.Builder.Append(row.ReadFloat32(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.Int64:
context.Builder.Append(row.ReadInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.UInt64:
context.Builder.Append(row.ReadUInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool0:
context.Builder.Append((row.ReadPackedBool(column.Offset, 0) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool1:
context.Builder.Append((row.ReadPackedBool(column.Offset, 1) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool2:
context.Builder.Append((row.ReadPackedBool(column.Offset, 2) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool3:
context.Builder.Append((row.ReadPackedBool(column.Offset, 3) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool4:
context.Builder.Append((row.ReadPackedBool(column.Offset, 4) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool5:
context.Builder.Append((row.ReadPackedBool(column.Offset, 5) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool6:
context.Builder.Append((row.ReadPackedBool(column.Offset, 6) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
case ExcelColumnDataType.PackedBool7:
context.Builder.Append((row.ReadPackedBool(column.Offset, 7) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture));
return true;
ExcelColumnDataType.String => this.Evaluate(row.ReadString(column.Offset), [colParam], language),
ExcelColumnDataType.Bool => (row.ReadBool(column.Offset) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.Int8 => row.ReadInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.UInt8 => row.ReadUInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.Int16 => row.ReadInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.UInt16 => row.ReadUInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.Int32 => row.ReadInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.UInt32 => row.ReadUInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.Float32 => row.ReadFloat32(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.Int64 => row.ReadInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.UInt64 => row.ReadUInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool0 => (row.ReadPackedBool(column.Offset, 0) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool1 => (row.ReadPackedBool(column.Offset, 1) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool2 => (row.ReadPackedBool(column.Offset, 2) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool3 => (row.ReadPackedBool(column.Offset, 3) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool4 => (row.ReadPackedBool(column.Offset, 4) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool5 => (row.ReadPackedBool(column.Offset, 5) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool6 => (row.ReadPackedBool(column.Offset, 6) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
ExcelColumnDataType.PackedBool7 => (row.ReadPackedBool(column.Offset, 7) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture),
_ => default,
};
}
private void AddSheetRedirectItemDecoration(in SeStringContext context, ref ReadOnlySeString text, SheetRedirectFlags flags, uint eRowIdValue)
{
if (!flags.HasFlag(SheetRedirectFlags.Item))
return;
var rarity = 1u;
var skipLink = false;
if (flags.HasFlag(SheetRedirectFlags.EventItem))
{
rarity = 8;
skipLink = true;
}
var itemId = eRowIdValue;
if (this.dataManager.GetExcelSheet<Item>(context.Language).TryGetRow(itemId, out var itemRow))
{
rarity = itemRow.Rarity;
if (rarity == 0)
rarity = 1;
if (itemRow.FilterGroup is 38 or 50)
skipLink = true;
}
if (flags.HasFlag(SheetRedirectFlags.Collectible))
{
itemId += 500000;
}
else if (flags.HasFlag(SheetRedirectFlags.HighQuality))
{
itemId += 1000000;
}
var sb = SeStringBuilder.SharedPool.Get();
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language));
if (!skipLink)
sb.PushLink(LinkMacroPayloadType.Item, itemId, rarity, 0u); // arg3 = some LogMessage flag based on LogKind RowId? => "89 5C 24 20 E8 ?? ?? ?? ?? 48 8B 1F"
// there is code here for handling noun link markers (//), but i don't know why
sb.Append(text);
if (flags.HasFlag(SheetRedirectFlags.HighQuality)
&& this.dataManager.GetExcelSheet<AddonSheet>(context.Language).TryGetRow(9, out var hqSymbol))
{
sb.Append(hqSymbol.Text);
}
else if (flags.HasFlag(SheetRedirectFlags.Collectible)
&& this.dataManager.GetExcelSheet<AddonSheet>(context.Language).TryGetRow(150, out var collectibleSymbol))
{
sb.Append(collectibleSymbol.Text);
}
if (!skipLink)
sb.PopLink();
text = sb.ToReadOnlySeString();
SeStringBuilder.SharedPool.Return(sb);
}
private void CreateSheetLink(in SeStringContext context, string resolvedSheetName, ReadOnlySeString text, uint eRowIdValue, uint eColParamValue)
{
switch (resolvedSheetName)
{
case "Achievement":
context.Builder.PushLink(LinkMacroPayloadType.Achievement, eRowIdValue, 0u, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "HowTo":
context.Builder.PushLink(LinkMacroPayloadType.HowTo, eRowIdValue, 0u, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "Status" when this.dataManager.GetExcelSheet<StatusSheet>(context.Language).TryGetRow(eRowIdValue, out var statusRow):
context.Builder.PushLink(LinkMacroPayloadType.Status, eRowIdValue, 0u, 0u, []);
switch (statusRow.StatusCategory)
{
case 1: context.Builder.Append(this.EvaluateFromAddon(376)); break; // buff symbol
case 2: context.Builder.Append(this.EvaluateFromAddon(377)); break; // debuff symbol
}
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "AkatsukiNoteString":
context.Builder.PushLink(LinkMacroPayloadType.AkatsukiNote, eColParamValue, 0u, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "DescriptionString" when eColParamValue > 0:
context.Builder.PushLink((LinkMacroPayloadType)11, eRowIdValue, eColParamValue, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "WKSPioneeringTrailString":
context.Builder.PushLink((LinkMacroPayloadType)12, eRowIdValue, eColParamValue, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
case "MKDLore":
context.Builder.PushLink((LinkMacroPayloadType)13, eRowIdValue, 0u, 0u, text.AsSpan());
context.Builder.Append(text);
context.Builder.PopLink();
return;
default:
return false;
context.Builder.Append(text);
return;
}
}

View file

@ -94,6 +94,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget
{ MacroCode.LowerHead, ["String"] },
{ MacroCode.ColorType, ["ColorType"] },
{ MacroCode.EdgeColorType, ["ColorType"] },
{ MacroCode.Ruby, ["StandardText", "RubyText"] },
{ MacroCode.Digit, ["Value", "TargetLength"] },
{ MacroCode.Ordinal, ["Value"] },
{ MacroCode.Sound, ["IsJingle", "SoundId"] },
@ -477,7 +478,12 @@ internal class SeStringCreatorWidget : IDataWindowWidget
}
}
RaptureLogModule.Instance()->PrintString(Service<SeStringEvaluator>.Get().Evaluate(sb.ToReadOnlySeString()));
var evaluated = Service<SeStringEvaluator>.Get().Evaluate(
sb.ToReadOnlySeString(),
this.localParameters,
this.language);
RaptureLogModule.Instance()->PrintString(evaluated);
}
if (this.entries.Count != 0)

View file

@ -63,6 +63,10 @@ internal class SheetRedirectResolverSelfTestStep : ISelfTestStep
new("WeatherPlaceName", 40),
new("WeatherPlaceName", 52),
new("WeatherPlaceName", 2300),
new("InstanceContent", 1),
new("PartyContent", 2),
new("PublicContent", 1),
new("AkatsukiNote", 1),
];
private unsafe delegate SheetRedirectFlags ResolveSheetRedirect(RaptureTextModule* thisPtr, Utf8String* sheetName, uint* rowId, uint* flags);