From bf4fc7864f134e9759f1109b97628a70882927d7 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 00:33:23 +0200 Subject: [PATCH 01/11] Handle multiple col- entries in Completion.LookupTable --- .../Game/Text/Evaluator/SeStringEvaluator.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 8b6a2bed8..cd981ae75 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1629,7 +1629,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator return true; var isNoun = false; - var col = 0; + + Span cols = stackalloc int[2]; + cols.Clear(); if (ranges.StartsWith("noun")) { @@ -1637,11 +1639,24 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator } else if (ranges.StartsWith("col")) { - var colRangeEnd = ranges.IndexOf(','); - if (colRangeEnd == -1) - colRangeEnd = ranges.Length; + var i = 0; + while (i < cols.Length && ranges.StartsWith("col-", StringComparison.Ordinal)) + { + // find the end of the current "col-#" token + var colRangeEnd = ranges.IndexOf(','); + if (colRangeEnd == -1) + colRangeEnd = ranges.Length; - col = int.Parse(ranges[4..colRangeEnd]); + // parse the column index + cols[i++] = int.Parse(ranges.AsSpan(4, colRangeEnd - 4)); + + // if it's the end of the string, we're done + if (colRangeEnd == ranges.Length) + break; + + // move to the next entry + ranges = ranges[(colRangeEnd + 1)..].TrimStart(); + } } else if (ranges.StartsWith("tail")) { @@ -1663,7 +1678,17 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator } else if (this.dataManager.GetExcelSheet(context.Language, sheetName).TryGetRow(rowId, out var row)) { - context.Builder.Append(row.ReadStringColumn(col)); + // the game uses a priority system for columns here. + // if the first column is empty (for example MainCommand.Alias), then the next one is used (MainCommand.Command) + for (var i = 0; i < cols.Length; i++) + { + var text = row.ReadStringColumn(cols[i]); + if (!text.IsEmpty) + { + context.Builder.Append(text); + break; + } + } } return true; From 327ebf3bb3973971f37349202d2b149b62e27594 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 01:54:47 +0200 Subject: [PATCH 02/11] Pass correct length to DecodeImpl --- Dalamud/Game/Text/SeStringHandling/Payload.cs | 2 +- Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index 7131a88a7..3eb1cf317 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -208,7 +208,7 @@ public abstract partial class Payload } payload ??= new RawPayload((byte)chunkType); - payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1); + payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen); // read through the rest of the packet var readBytes = (uint)(reader.BaseStream.Position - packetStart); diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs index a7e41cbc6..02a7c113e 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs @@ -95,14 +95,12 @@ public class RawPayload : Payload /// protected override byte[] EncodeImpl() { - var chunkLen = this.data.Length + 1; - var bytes = new List() { START_BYTE, this.chunkType, - (byte)chunkLen, }; + bytes.AddRange(MakeInteger((uint)this.data.Length)); // chunkLen bytes.AddRange(this.data); bytes.Add(END_BYTE); @@ -113,6 +111,6 @@ public class RawPayload : Payload /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); + this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); } } From 93a44842ed6b39be12f8acdd1ca3011a05b2061b Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 01:55:06 +0200 Subject: [PATCH 03/11] Skip to payload end instead of reading remaining bytes --- Dalamud/Game/Text/SeStringHandling/Payload.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index 3eb1cf317..c797c4a91 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -2,11 +2,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Plugin.Services; -using Newtonsoft.Json; using Serilog; // TODOs: @@ -117,7 +114,7 @@ public abstract partial class Payload var chunkType = (SeStringChunkType)reader.ReadByte(); var chunkLen = GetInteger(reader); - var packetStart = reader.BaseStream.Position; + var expressionsStart = reader.BaseStream.Position; // any unhandled payload types will be turned into a RawPayload with the exact same binary data switch (chunkType) @@ -210,9 +207,8 @@ public abstract partial class Payload payload ??= new RawPayload((byte)chunkType); payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen); - // read through the rest of the packet - var readBytes = (uint)(reader.BaseStream.Position - packetStart); - reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker + // skip to the end of the payload, in case the specific payload handler didn't read everything + reader.BaseStream.Seek(expressionsStart + chunkLen + 1, SeekOrigin.Begin); // +1 for the END_BYTE marker return payload; } From 3f037e5d2026ba80c47abd9a21d8a106563c2e58 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 02:10:01 +0200 Subject: [PATCH 04/11] Use SeStringEvaluator in AutoTranslatePayload --- .../Payloads/AutoTranslatePayload.cs | 116 +++++------------- 1 file changed, 29 insertions(+), 87 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index b038deb6f..8035cdd74 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -1,14 +1,11 @@ -using System.Collections.Generic; using System.IO; -using System.Linq; -using Dalamud.Data; +using Dalamud.Game.Text.Evaluator; -using Lumina.Excel.Sheets; +using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; using Newtonsoft.Json; -using Serilog; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -18,6 +15,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; public class AutoTranslatePayload : Payload, ITextProvider { private string? text; + private ReadOnlySeString payload; /// /// Initializes a new instance of the class. @@ -34,6 +32,14 @@ public class AutoTranslatePayload : Payload, ITextProvider // TODO: friendlier ctor? not sure how to handle that given how weird the tables are this.Group = group; this.Key = key; + + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + this.payload = ssb.BeginMacro(MacroCode.Fixed) + .AppendUIntExpression(group - 1) + .AppendUIntExpression(key) + .EndMacro() + .ToReadOnlySeString(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); } /// @@ -41,6 +47,7 @@ public class AutoTranslatePayload : Payload, ITextProvider /// internal AutoTranslatePayload() { + this.payload = default; // parsed by DecodeImpl } /// @@ -68,8 +75,13 @@ public class AutoTranslatePayload : Payload, ITextProvider { get { + if (this.Group is 100 or 200) + { + return this.text ??= Service.Get().Evaluate(this.payload).ToString(); + } + // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads - return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; + return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {Service.Get().Evaluate(this.payload)} {(char)SeIconChar.AutoTranslateClose}"; } } @@ -85,95 +97,25 @@ public class AutoTranslatePayload : Payload, ITextProvider /// protected override byte[] EncodeImpl() { - var keyBytes = MakeInteger(this.Key); - - var chunkLen = keyBytes.Length + 2; - var bytes = new List() - { - START_BYTE, - (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, - (byte)this.Group, - }; - bytes.AddRange(keyBytes); - bytes.Add(END_BYTE); - - return bytes.ToArray(); + return this.payload.Data.ToArray(); } /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - // this seems to always be a bare byte, and not following normal integer encoding - // the values in the table are all <70 so this is presumably ok - this.Group = reader.ReadByte(); + var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Fixed, body.AsSpan()); - this.Key = GetInteger(reader); - } + var span = rosps.EnvelopeByteLength <= 512 ? stackalloc byte[rosps.EnvelopeByteLength] : new byte[rosps.EnvelopeByteLength]; + rosps.WriteEnvelopeTo(span); + this.payload = new ReadOnlySeString(span); - private static ReadOnlySeString ResolveTextCommand(TextCommand command) - { - // TextCommands prioritize the `Alias` field, if it not empty - // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat - return !command.Alias.IsEmpty ? command.Alias : command.Command; - } - - private string Resolve() - { - string value = null; - - var excelModule = Service.Get().Excel; - var completionSheet = excelModule.GetSheet(); - - // try to get the row in the Completion table itself, because this is 'easiest' - // The row may not exist at all (if the Key is for another table), or it could be the wrong row - // (again, if it's meant for another table) - - if (completionSheet.GetRowOrDefault(this.Key) is { } completion && completion.Group == this.Group) + if (rosps.TryGetExpression(out var expr1, out var expr2) + && expr1.TryGetUInt(out var group) + && expr2.TryGetUInt(out var key)) { - // if the row exists in this table and the group matches, this is actually the correct data - value = completion.Text.ExtractText(); + this.Group = group; + this.Key = key; } - else - { - try - { - // we need to get the linked table and do the lookup there instead - // in this case, there will only be one entry for this group id - var row = completionSheet.First(r => r.Group == this.Group); - // many of the names contain valid id ranges after the table name, but we don't need those - var actualTableName = row.LookupTable.ExtractText().Split('[')[0]; - - var name = actualTableName switch - { - "Action" => excelModule.GetSheet().GetRow(this.Key).Name, - "ActionComboRoute" => excelModule.GetSheet().GetRow(this.Key).Name, - "BuddyAction" => excelModule.GetSheet().GetRow(this.Key).Name, - "ClassJob" => excelModule.GetSheet().GetRow(this.Key).Name, - "Companion" => excelModule.GetSheet().GetRow(this.Key).Singular, - "CraftAction" => excelModule.GetSheet().GetRow(this.Key).Name, - "GeneralAction" => excelModule.GetSheet().GetRow(this.Key).Name, - "GuardianDeity" => excelModule.GetSheet().GetRow(this.Key).Name, - "MainCommand" => excelModule.GetSheet().GetRow(this.Key).Name, - "Mount" => excelModule.GetSheet().GetRow(this.Key).Singular, - "Pet" => excelModule.GetSheet().GetRow(this.Key).Name, - "PetAction" => excelModule.GetSheet().GetRow(this.Key).Name, - "PetMirage" => excelModule.GetSheet().GetRow(this.Key).Name, - "PlaceName" => excelModule.GetSheet().GetRow(this.Key).Name, - "Race" => excelModule.GetSheet().GetRow(this.Key).Masculine, - "TextCommand" => AutoTranslatePayload.ResolveTextCommand(excelModule.GetSheet().GetRow(this.Key)), - "Tribe" => excelModule.GetSheet().GetRow(this.Key).Masculine, - "Weather" => excelModule.GetSheet().GetRow(this.Key).Name, - _ => throw new Exception(actualTableName), - }; - - value = name.ExtractText(); - } - catch (Exception e) - { - Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.Group}, Key: {this.Key}"); - } - } - - return value; } } From d8555f207ed44da310280f4501bdcde24161c236 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 03:53:26 +0200 Subject: [PATCH 05/11] Rework range check - Loops through all entries - Bumped amount of cols up to 8 for future proofing - Use Ordinal search - Actually parse ranges and check if the RowId is allowed --- .../Game/Text/Evaluator/SeStringEvaluator.cs | 87 ++++++++++++------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index cd981ae75..424b2769a 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1630,37 +1630,56 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator var isNoun = false; - Span cols = stackalloc int[2]; + var colIndex = 0; + Span cols = stackalloc int[8]; cols.Clear(); + var isInRange = false; - if (ranges.StartsWith("noun")) + while (!string.IsNullOrWhiteSpace(ranges)) { - isNoun = true; - } - else if (ranges.StartsWith("col")) - { - var i = 0; - while (i < cols.Length && ranges.StartsWith("col-", StringComparison.Ordinal)) + // find the end of the current entry + var entryEnd = ranges.IndexOf(','); + if (entryEnd == -1) + entryEnd = ranges.Length; + + if (ranges.StartsWith("noun", StringComparison.Ordinal)) { - // find the end of the current "col-#" token - var colRangeEnd = ranges.IndexOf(','); - if (colRangeEnd == -1) - colRangeEnd = ranges.Length; - - // parse the column index - cols[i++] = int.Parse(ranges.AsSpan(4, colRangeEnd - 4)); - - // if it's the end of the string, we're done - if (colRangeEnd == ranges.Length) - break; - - // move to the next entry - ranges = ranges[(colRangeEnd + 1)..].TrimStart(); + isNoun = true; } + else if (ranges.StartsWith("col", StringComparison.Ordinal) && colIndex < cols.Length) + { + cols[colIndex++] = int.Parse(ranges.AsSpan(4, entryEnd - 4)); + } + else if (ranges.StartsWith("tail", StringComparison.Ordinal)) + { + // currently not supported, since there are no known uses + context.Builder.Append(payload); + return false; + } + else + { + var dash = ranges.IndexOf('-'); + if (dash == -1) + { + isInRange |= int.Parse(ranges.AsSpan(0, entryEnd)) == rowId; + } + else + { + isInRange |= rowId >= int.Parse(ranges.AsSpan(0, dash)) + && rowId <= int.Parse(ranges.AsSpan(dash + 1, entryEnd - dash - 1)); + } + } + + // if it's the end of the string, we're done + if (entryEnd == ranges.Length) + break; + + // else, move to the next entry + ranges = ranges[(entryEnd + 1)..].TrimStart(); } - else if (ranges.StartsWith("tail")) + + if (!isInRange) { - // couldn't find any, so we don't handle them :p context.Builder.Append(payload); return false; } @@ -1678,15 +1697,21 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator } else if (this.dataManager.GetExcelSheet(context.Language, sheetName).TryGetRow(rowId, out var row)) { - // the game uses a priority system for columns here. - // if the first column is empty (for example MainCommand.Alias), then the next one is used (MainCommand.Command) - for (var i = 0; i < cols.Length; i++) + if (colIndex == 0) { - var text = row.ReadStringColumn(cols[i]); - if (!text.IsEmpty) + context.Builder.Append(row.ReadStringColumn(0)); + return true; + } + else + { + for (var i = 0; i < colIndex; i++) { - context.Builder.Append(text); - break; + var text = row.ReadStringColumn(cols[i]); + if (!text.IsEmpty) + { + context.Builder.Append(text); + break; + } } } } From c264fb134edcf6299d212f77031030566325bfa7 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 20 Sep 2025 04:09:23 +0200 Subject: [PATCH 06/11] Only check range if it has ranges --- Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 424b2769a..6ad58ccd8 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1633,6 +1633,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator var colIndex = 0; Span cols = stackalloc int[8]; cols.Clear(); + var hasRanges = false; var isInRange = false; while (!string.IsNullOrWhiteSpace(ranges)) @@ -1659,6 +1660,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator else { var dash = ranges.IndexOf('-'); + + hasRanges |= true; + if (dash == -1) { isInRange |= int.Parse(ranges.AsSpan(0, entryEnd)) == rowId; @@ -1678,7 +1682,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator ranges = ranges[(entryEnd + 1)..].TrimStart(); } - if (!isInRange) + if (hasRanges && !isInRange) { context.Builder.Append(payload); return false; From c03e7ecfe6ea01c556d015d427df60dc0e1739d4 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 21 Sep 2025 02:16:09 +0200 Subject: [PATCH 07/11] Fix Group being off by 1 --- .../Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index 8035cdd74..7ca1609fa 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -114,7 +114,7 @@ public class AutoTranslatePayload : Payload, ITextProvider && expr1.TryGetUInt(out var group) && expr2.TryGetUInt(out var key)) { - this.Group = group; + this.Group = group + 1; this.Key = key; } } From 0bb87d87b730e3cc0e0ac87b299837ad31bf1b34 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 21 Sep 2025 02:18:55 +0200 Subject: [PATCH 08/11] Add AutoTranslatePayload re-encode test --- .../Text/SeStringHandling/SeStringTests.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs b/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs index 9a48a6615..171031db6 100644 --- a/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs +++ b/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs @@ -1,6 +1,10 @@ -using System; +using System; +using System.IO; + using Dalamud.Configuration; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; + using Xunit; namespace Dalamud.Test.Game.Text.SeStringHandling @@ -50,19 +54,44 @@ namespace Dalamud.Test.Game.Text.SeStringHandling var config = new MockConfig { Text = seString }; PluginConfigurations.SerializeConfig(config); } - + [Fact] public void TestConfigDeserializable() { var builder = new SeStringBuilder(); var seString = builder.AddText("Some text").Build(); var config = new MockConfig { Text = seString }; - + // This relies on the type information being maintained, which is why we're using these // static methods instead of default serialization/deserialization. var configSerialized = PluginConfigurations.SerializeConfig(config); var configDeserialized = (MockConfig)PluginConfigurations.DeserializeConfig(configSerialized); Assert.Equal(config, configDeserialized); } + + [Theory] + [InlineData(49, 209)] + [InlineData(71, 7)] + [InlineData(62, 116)] + public void TestAutoTranslatePayloadReencode(uint group, uint key) + { + var payload = new AutoTranslatePayload(group, key); + + Assert.Equal(group, payload.Group); + Assert.Equal(key, payload.Key); + + var encoded = payload.Encode(); + using var stream = new MemoryStream(encoded); + using var reader = new BinaryReader(stream); + var decodedPayload = Payload.Decode(reader) as AutoTranslatePayload; + + Assert.Equal(group, decodedPayload.Group); + Assert.Equal(key, decodedPayload.Key); + + Assert.Equal(payload.Group, decodedPayload.Group); + Assert.Equal(payload.Key, decodedPayload.Key); + + Assert.Equal(encoded, decodedPayload.Encode()); + } } } From 9b224857f1118c9e3e314e2c86de45aed4ee2dcd Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 21 Sep 2025 03:08:28 +0200 Subject: [PATCH 09/11] Remove redundant checks --- Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs b/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs index 171031db6..2a19d6216 100644 --- a/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs +++ b/Dalamud.Test/Game/Text/SeStringHandling/SeStringTests.cs @@ -88,9 +88,6 @@ namespace Dalamud.Test.Game.Text.SeStringHandling Assert.Equal(group, decodedPayload.Group); Assert.Equal(key, decodedPayload.Key); - Assert.Equal(payload.Group, decodedPayload.Group); - Assert.Equal(payload.Key, decodedPayload.Key); - Assert.Equal(encoded, decodedPayload.Encode()); } } From 374f9fcbd0c25231eaaf7745d4a822862f238801 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 21 Sep 2025 03:10:00 +0200 Subject: [PATCH 10/11] Add some AutoTranslatePayload.Text self-tests --- .../Steps/SeStringEvaluatorSelfTestStep.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs index 8e66dd5cf..9853e31d4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs @@ -1,6 +1,9 @@ using Dalamud.Bindings.ImGui; +using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState; using Dalamud.Game.Text.Evaluator; +using Dalamud.Game.Text.SeStringHandling.Payloads; + using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; @@ -75,6 +78,55 @@ internal class SeStringEvaluatorSelfTestStep : ISelfTestStep return SelfTestStepResult.Waiting; } + this.step++; + break; + + case 2: + ImGui.Text("Checking AutoTranslatePayload.Text results..."u8); + + var config = Service.Get(); + var originalLanguageOverride = config.LanguageOverride; + + Span<(string Language, uint Group, uint Key, string ExpectedText)> tests = [ + ("en", 49u, 209u, " albino karakul "), // Mount + ("en", 62u, 116u, " /echo "), // TextCommand - testing Command + ("en", 62u, 143u, " /dutyfinder "), // TextCommand - testing Alias over Command + ("en", 65u, 67u, " Minion of Light "), // Companion - testing noun handling for the german language (special case) + ("en", 71u, 7u, " Phantom Geomancer "), // MKDSupportJob + + ("de", 49u, 209u, " Albino-Karakul "), // Mount + ("de", 62u, 115u, " /freiegesellschaft "), // TextCommand - testing Alias over Command + ("de", 62u, 116u, " /echo "), // TextCommand - testing Command + ("de", 65u, 67u, " Begleiter des Lichts "), // Companion - testing noun handling for the german language (special case) + ("de", 71u, 7u, " Phantom-Geomant "), // MKDSupportJob + ]; + + try + { + foreach (var (language, group, key, expectedText) in tests) + { + config.LanguageOverride = language; + + var payload = new AutoTranslatePayload(group, key); + + if (payload.Text != expectedText) + { + ImGui.Text($"Test failed for Group {group}, Key {key}"); + ImGui.Text($"Expected: {expectedText}"); + ImGui.Text($"Got: {payload.Text}"); + + if (ImGui.Button("Continue"u8)) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + } + } + finally + { + config.LanguageOverride = originalLanguageOverride; + } + return SelfTestStepResult.Pass; } From ce1faa50cfc226463903e0cab63c8f3fd96b4ba2 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 21 Sep 2025 14:00:23 +0200 Subject: [PATCH 11/11] Remove AutoTranslatePayload text cache - Lumina lookups aren't that taxing anymore - Cache wasn't invalidated when the language override changes --- .../Text/SeStringHandling/Payloads/AutoTranslatePayload.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index 7ca1609fa..470e942c3 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -14,7 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class AutoTranslatePayload : Payload, ITextProvider { - private string? text; private ReadOnlySeString payload; /// @@ -77,11 +76,11 @@ public class AutoTranslatePayload : Payload, ITextProvider { if (this.Group is 100 or 200) { - return this.text ??= Service.Get().Evaluate(this.payload).ToString(); + return Service.Get().Evaluate(this.payload).ToString(); } - // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads - return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {Service.Get().Evaluate(this.payload)} {(char)SeIconChar.AutoTranslateClose}"; + // wrap the text in the colored brackets that are used in-game, since those are not actually part of any of the fixed macro payload + return $"{(char)SeIconChar.AutoTranslateOpen} {Service.Get().Evaluate(this.payload)} {(char)SeIconChar.AutoTranslateClose}"; } }