diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 2ccb3ff47..85c342e22 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -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(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(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(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(context.Language).TryGetRow(9, out var hqSymbol)) + { + sb.Append(hqSymbol.Text); + } + else if (flags.HasFlag(SheetRedirectFlags.Collectible) + && this.dataManager.GetExcelSheet(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(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; } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs index c45e0cdfb..bff023ff4 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs @@ -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.Get().Evaluate(sb.ToReadOnlySeString())); + var evaluated = Service.Get().Evaluate( + sb.ToReadOnlySeString(), + this.localParameters, + this.language); + + RaptureLogModule.Instance()->PrintString(evaluated); } if (this.entries.Count != 0) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs index 6ab08cd91..b43c045f4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs @@ -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);