fix(vt): B typed MetaAction.EmbeddedRoute replaces the binary nav blob
Item B (slice-1 fix round). A LoadEmbeddedNavigationRoute action carried a synthesized "uTank2 NAV 1.2" TEXT blob in MetaAction.Text that both MetaEngine and the runtime consumer (MossTankPanel) had to re-parse on every load — an unnecessary re-parse of already-typed data, and the reason proof (3)'s AssertActionsEqual skipped comparing embedded routes entirely (the blob's exact byte shape wasn't a meaningful comparison target). - MetaAction: new EmbeddedRoute (NavigationSettings?) property replaces the blob. Null only for a genuinely unresolved/never-defined Nav tag. - MetaServices.LoadEmbeddedNavigationRoute: Action<string> -> Action<NavigationSettings?>; MetaEngine's dispatch passes action.EmbeddedRoute directly. - MetafSerializer.ResolveEmbeddedNavs (.af importer) builds the NavigationSettings directly via the existing ApplyNavBody helper (renamed SynthesizeNavBlob -> BuildNavSettings) instead of serializing it back into text. WriteBinaryNavBlob/WriteBinaryWaypoint/ FormatBinaryDouble are deleted outright (the only caller was the now-removed blob synthesis). - SaveMeta had its own now-dead re-parse of action.Text via VtankNavRouteSerializer.TryLoad to rebuild each embedded NAV: block on save; this silently started producing EMPTY NAV: blocks once Text stopped carrying the blob (Text is now always cleared for this action kind), caught immediately by the full suite: MetaParseWriteParseIsIdentical started failing "Expected: Once / Actual: Circular" (a re-parsed route falling back to NavigationSettings' default Mode because its NAV: block vanished). Fixed by writing straight from action.EmbeddedRoute. - VtankMetaProfileSerializer (.met importer): TryLoad gained an ISpellCatalog overload (threaded through ReadAction/ReadEmbeddedNavigation); ReadEmbeddedNavigation now parses its reassembled blob text through VtankNavRouteSerializer.TryLoad into a real NavigationSettings instead of handing the raw text to the caller. The existing 2-arg TryLoad overload defers to MetafSerializer.NoOpSpells.Instance (promoted from private to internal) so both test call sites and MossTankMetaProfileStore (which now passes _host.Automation.Spells) keep working. - VtankNavRouteSerializer.Apply promoted from private to internal so MossTankPanel.LoadEmbeddedNavigationRoute can copy an already-typed NavigationSettings into the live _navigationSettings instance directly, replacing its own VtankNavRouteSerializer.TryLoad(string, ...) re-parse. - MetafSerializerTests: AssertActionsEqual now asserts EmbeddedRoute is non-null on both sides and calls AssertNavigationEqual on them (waypoint-by-waypoint) for LoadEmbeddedNavigationRoute actions, instead of skipping the comparison. PtlNodeKeepsBothCoordinateTriplesDistinct's FindNavWaypoint helper reads action.EmbeddedRoute directly instead of re-parsing action.Text (which is now empty). Full MossTank suite: 568/568 passing (net zero change in count — this is a representation change, not new coverage, though the AssertActionsEqual tightening now exercises real waypoint comparisons on every fixture with an EmbedNav action that it previously skipped). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0219c6e03d
commit
e5bc5c6a3f
7 changed files with 84 additions and 146 deletions
|
|
@ -77,6 +77,17 @@ internal sealed class MetaAction
|
|||
public double Number { get; set; }
|
||||
public double SecondaryNumber { get; set; }
|
||||
public List<MetaAction> Children { get; set; } = [];
|
||||
/// <summary>
|
||||
/// The parsed route for a <see cref="MetaActionKind.LoadEmbeddedNavigationRoute"/>
|
||||
/// action. Replaces a prior port's binary "uTank2 NAV 1.2" text blob
|
||||
/// carried in <see cref="Text"/>: both the <c>.af</c> importer
|
||||
/// (<c>MetafSerializer.ResolveEmbeddedNavs</c>) and the <c>.met</c>
|
||||
/// importer (<c>VtankMetaProfileSerializer.ReadEmbeddedNavigation</c>)
|
||||
/// now produce this typed model directly, and
|
||||
/// <c>MetaEngine.LoadEmbeddedNavigationRoute</c> consumes it without a
|
||||
/// re-parse. Null only for an unresolved/never-defined tag.
|
||||
/// </summary>
|
||||
public NavigationSettings? EmbeddedRoute { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class MetaRule
|
||||
|
|
@ -102,7 +113,7 @@ internal sealed class MetaServices
|
|||
static () => double.PositiveInfinity;
|
||||
public Func<int, double, int> CountMonstersByPriority { get; init; } =
|
||||
static (_, _) => 0;
|
||||
public Action<string> LoadEmbeddedNavigationRoute { get; init; } = static _ => { };
|
||||
public Action<NavigationSettings?> LoadEmbeddedNavigationRoute { get; init; } = static _ => { };
|
||||
public Func<string, ExpressionValue> GetOption { get; init; } =
|
||||
static _ => ExpressionValue.Zero;
|
||||
public Func<string, ExpressionValue, bool> SetOption { get; init; } =
|
||||
|
|
@ -379,7 +390,7 @@ internal sealed class MetaEngine
|
|||
}
|
||||
return true;
|
||||
case MetaActionKind.LoadEmbeddedNavigationRoute:
|
||||
_services.LoadEmbeddedNavigationRoute(action.Text);
|
||||
_services.LoadEmbeddedNavigationRoute(action.EmbeddedRoute);
|
||||
return true;
|
||||
case MetaActionKind.CallMetaState:
|
||||
if (_callStack.Count >= MaximumCallDepth)
|
||||
|
|
|
|||
|
|
@ -234,12 +234,8 @@ internal static class MetafSerializer
|
|||
}
|
||||
foreach ((MetaAction action, string tag) in embedTags)
|
||||
{
|
||||
var nav = new NavigationSettings();
|
||||
if (!string.IsNullOrEmpty(action.Text)
|
||||
&& VtankNavRouteSerializer.TryLoad(action.Text, nav, NoOpSpells.Instance, out _))
|
||||
{
|
||||
if (action.EmbeddedRoute is { } nav)
|
||||
WriteNavBlock(lines, tag, nav);
|
||||
}
|
||||
}
|
||||
return string.Join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
|
|
@ -262,7 +258,8 @@ internal static class MetafSerializer
|
|||
AssignEmbedTags(child, tags, ref counter);
|
||||
}
|
||||
|
||||
private sealed class NoOpSpells : ISpellCatalog
|
||||
/// <summary>Internal (not private) so other one-shot import readers can share this no-op.</summary>
|
||||
internal sealed class NoOpSpells : ISpellCatalog
|
||||
{
|
||||
public static NoOpSpells Instance { get; } = new();
|
||||
public IReadOnlyList<PluginSpellInfo> KnownSelfBuffs => [];
|
||||
|
|
@ -534,11 +531,10 @@ internal static class MetafSerializer
|
|||
/// EmbedNav stores the referenced Nav's tag in <see cref="MetaAction.SecondaryText"/>
|
||||
/// (see <see cref="ReadAction"/>) until every NAV: block has been parsed;
|
||||
/// this walks the rule tree once more and replaces it with the real
|
||||
/// display name plus a synthesized self-contained "uTank2 NAV 1.2" blob
|
||||
/// in <see cref="MetaAction.Text"/> — exactly the shape
|
||||
/// <see cref="VtankNavRouteSerializer.TryLoad"/> already expects from a
|
||||
/// binary-imported profile's embedded route (MetaEngine's
|
||||
/// LoadEmbeddedNavigationRoute service feeds this straight through).
|
||||
/// display name plus the typed <see cref="MetaAction.EmbeddedRoute"/>
|
||||
/// (an unresolved tag gets an empty <see cref="NavigationSettings"/>
|
||||
/// rather than null, matching the prior blob path's "0 waypoints"
|
||||
/// fallback).
|
||||
/// </summary>
|
||||
private static void ResolveEmbeddedNavs(
|
||||
MetaProfile profile,
|
||||
|
|
@ -557,15 +553,16 @@ internal static class MetafSerializer
|
|||
string tag = action.SecondaryText;
|
||||
string displayName = action.Text;
|
||||
action.SecondaryText = displayName;
|
||||
action.Text = navs.TryGetValue(tag, out var nav)
|
||||
? SynthesizeNavBlob(nav.NavType, nav.Nodes, nav.FollowTargetId, nav.FollowTargetName)
|
||||
: EmptyNavBlob();
|
||||
action.Text = string.Empty;
|
||||
action.EmbeddedRoute = navs.TryGetValue(tag, out var nav)
|
||||
? BuildNavSettings(nav.NavType, nav.Nodes, nav.FollowTargetId, nav.FollowTargetName)
|
||||
: new NavigationSettings();
|
||||
}
|
||||
foreach (MetaAction child in action.Children)
|
||||
ResolveEmbeddedNavs(child, navs);
|
||||
}
|
||||
|
||||
private static string SynthesizeNavBlob(
|
||||
private static NavigationSettings BuildNavSettings(
|
||||
string navType,
|
||||
List<RouteWaypoint> nodes,
|
||||
uint followTargetId,
|
||||
|
|
@ -573,109 +570,9 @@ internal static class MetafSerializer
|
|||
{
|
||||
var settings = new NavigationSettings();
|
||||
ApplyNavBody(settings, navType, nodes, followTargetId, followTargetName);
|
||||
return WriteBinaryNavBlob(settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static string EmptyNavBlob() => "uTank2 NAV 1.2\r\n1\r\n0\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// Writes the "uTank2 NAV 1.2" binary-format TEXT blob
|
||||
/// <see cref="MetaAction.LoadEmbeddedNavigationRoute"/> already expects
|
||||
/// in <see cref="MetaAction.Text"/> (per <c>MetaEngine</c>'s existing
|
||||
/// contract, previously produced by
|
||||
/// <c>VtankMetaProfileSerializer.ReadEmbeddedNavigation</c> when
|
||||
/// importing a real binary <c>.met</c>). This is a small, deliberate
|
||||
/// duplicate of what used to be <c>VtankNavRouteSerializer.Save</c> —
|
||||
/// that public writer was deleted per the owner's "MossTank does not
|
||||
/// implement .met and does not author .nav" direction (only the reader
|
||||
/// stays, as a one-shot import), but an EmbedNav action still needs
|
||||
/// this exact in-memory blob shape, which is a MossTank runtime
|
||||
/// contract, not a VTank file on disk.
|
||||
/// </summary>
|
||||
private static string WriteBinaryNavBlob(NavigationSettings source)
|
||||
{
|
||||
var lines = new List<string> { "uTank2 NAV 1.2" };
|
||||
lines.Add(source.Mode switch
|
||||
{
|
||||
RouteMode.Circular => "1",
|
||||
RouteMode.Linear => "2",
|
||||
RouteMode.Target => "3",
|
||||
RouteMode.Once => "4",
|
||||
_ => throw new InvalidOperationException("Unknown navigation type."),
|
||||
});
|
||||
if (source.Mode == RouteMode.Target)
|
||||
{
|
||||
lines.Add(source.FollowTargetName ?? string.Empty);
|
||||
lines.Add(unchecked((int)source.FollowTargetObjectId)
|
||||
.ToString(CultureInfo.InvariantCulture));
|
||||
return string.Join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
lines.Add(source.Waypoints.Count.ToString(CultureInfo.InvariantCulture));
|
||||
foreach (RouteWaypoint waypoint in source.Waypoints)
|
||||
WriteBinaryWaypoint(lines, waypoint);
|
||||
return string.Join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
|
||||
private static void WriteBinaryWaypoint(List<string> lines, RouteWaypoint waypoint)
|
||||
{
|
||||
lines.Add(((int)waypoint.Type).ToString(CultureInfo.InvariantCulture));
|
||||
lines.Add(FormatBinaryDouble(waypoint.Position.EastWest));
|
||||
lines.Add(FormatBinaryDouble(waypoint.Position.NorthSouth));
|
||||
lines.Add(FormatBinaryDouble(waypoint.Position.Elevation));
|
||||
lines.Add("0");
|
||||
switch (waypoint.Type)
|
||||
{
|
||||
case RouteWaypointType.Point:
|
||||
case RouteWaypointType.Checkpoint:
|
||||
break;
|
||||
case RouteWaypointType.Portal:
|
||||
lines.Add(unchecked((int)waypoint.ObjectId).ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case RouteWaypointType.Recall:
|
||||
lines.Add(waypoint.RecallSpellId.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case RouteWaypointType.Pause:
|
||||
lines.Add(waypoint.DurationMilliseconds.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case RouteWaypointType.ChatCommand:
|
||||
lines.Add(waypoint.Text ?? string.Empty);
|
||||
break;
|
||||
case RouteWaypointType.OpenVendor:
|
||||
lines.Add(unchecked((int)waypoint.ObjectId).ToString(CultureInfo.InvariantCulture));
|
||||
lines.Add(waypoint.ObjectName ?? string.Empty);
|
||||
break;
|
||||
case RouteWaypointType.PortalByName:
|
||||
case RouteWaypointType.UseNpc:
|
||||
lines.Add(waypoint.ObjectName ?? string.Empty);
|
||||
int objectClass = waypoint.LegacyObjectClass != 0
|
||||
? waypoint.LegacyObjectClass
|
||||
: waypoint.Type == RouteWaypointType.PortalByName ? 14 : 37;
|
||||
lines.Add(objectClass.ToString(CultureInfo.InvariantCulture));
|
||||
lines.Add(waypoint.LegacyReferenceValid.ToString(CultureInfo.InvariantCulture));
|
||||
lines.Add(FormatBinaryDouble(waypoint.ReferencePosition.EastWest));
|
||||
lines.Add(FormatBinaryDouble(waypoint.ReferencePosition.NorthSouth));
|
||||
lines.Add(FormatBinaryDouble(waypoint.ReferencePosition.Elevation));
|
||||
break;
|
||||
case RouteWaypointType.Jump:
|
||||
lines.Add(FormatBinaryDouble(waypoint.JumpHeadingDegrees));
|
||||
lines.Add(waypoint.JumpRun.ToString(CultureInfo.InvariantCulture));
|
||||
string suffix = waypoint.JumpDirection switch
|
||||
{
|
||||
RouteJumpDirection.StrafeLeft => "4",
|
||||
RouteJumpDirection.StrafeRight => "5",
|
||||
_ => "3",
|
||||
};
|
||||
lines.Add(waypoint.JumpChargeMilliseconds.ToString(
|
||||
"0.0000", CultureInfo.InvariantCulture) + suffix);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown waypoint type {waypoint.Type}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatBinaryDouble(double value) =>
|
||||
Convert.ToString(value, CultureInfo.InvariantCulture) ?? "0";
|
||||
|
||||
// ==================================================================
|
||||
// NAV: blocks (both inside a full .af meta and in a nav-only file).
|
||||
// ==================================================================
|
||||
|
|
|
|||
|
|
@ -139,7 +139,8 @@ internal sealed class MossTankMetaProfileStore
|
|||
notice = $"VTank Meta file '{normalized}.met' was not found in imports.";
|
||||
return false;
|
||||
}
|
||||
if (!VtankMetaProfileSerializer.TryLoad(source, out profile, out string error))
|
||||
if (!VtankMetaProfileSerializer.TryLoad(
|
||||
source, _host.Automation.Spells, out profile, out string error))
|
||||
{
|
||||
notice = $"Could not import {Path.GetFileName(key)}: {error}";
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -2416,19 +2416,16 @@ internal sealed partial class MossTankPanel
|
|||
return nearest;
|
||||
}
|
||||
|
||||
private void LoadEmbeddedNavigationRoute(string source)
|
||||
private void LoadEmbeddedNavigationRoute(NavigationSettings? route)
|
||||
{
|
||||
_navigation.Reset();
|
||||
if (!VtankNavRouteSerializer.TryLoad(
|
||||
source,
|
||||
_navigationSettings,
|
||||
_host.Automation.Spells,
|
||||
out string error))
|
||||
if (route is null)
|
||||
{
|
||||
_routeNotice = $"Embedded route rejected: {error}";
|
||||
_host.Log.Warn($"MossTank Meta embedded route rejected: {error}");
|
||||
_routeNotice = "Embedded route rejected: unresolved Nav tag.";
|
||||
_host.Log.Warn("MossTank Meta embedded route rejected: unresolved Nav tag.");
|
||||
return;
|
||||
}
|
||||
VtankNavRouteSerializer.Apply(route, _navigationSettings);
|
||||
_routeProfiles.SaveCurrent(_navigationSettings);
|
||||
_selectedRouteWaypoint = 0;
|
||||
RefreshRouteEditor();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Globalization;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
|
||||
namespace AcDream.Plugins.MossTank;
|
||||
|
||||
|
|
@ -25,8 +26,16 @@ internal static class VtankMetaProfileSerializer
|
|||
private const int MaximumRules = 100_000;
|
||||
private const int MaximumNesting = 256;
|
||||
|
||||
public static bool TryLoad(string source, out MetaProfile profile, out string error)
|
||||
public static bool TryLoad(string source, out MetaProfile profile, out string error) =>
|
||||
TryLoad(source, MetafSerializer.NoOpSpells.Instance, out profile, out error);
|
||||
|
||||
public static bool TryLoad(
|
||||
string source,
|
||||
ISpellCatalog spells,
|
||||
out MetaProfile profile,
|
||||
out string error)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(spells);
|
||||
try
|
||||
{
|
||||
var reader = new LineReader(source);
|
||||
|
|
@ -43,7 +52,7 @@ internal static class VtankMetaProfileSerializer
|
|||
reader.Expect("i");
|
||||
int actionType = reader.ReadInt();
|
||||
MetaCondition condition = ReadCondition(reader, conditionType, 0);
|
||||
MetaAction action = ReadAction(reader, actionType, 0);
|
||||
MetaAction action = ReadAction(reader, actionType, 0, spells);
|
||||
reader.Expect("s");
|
||||
parsed.Rules.Add(new MetaRule
|
||||
{
|
||||
|
|
@ -160,7 +169,7 @@ internal static class VtankMetaProfileSerializer
|
|||
}
|
||||
}
|
||||
|
||||
private static MetaAction ReadAction(LineReader reader, int type, int depth)
|
||||
private static MetaAction ReadAction(LineReader reader, int type, int depth, ISpellCatalog spells)
|
||||
{
|
||||
CheckDepth(reader, depth);
|
||||
var value = new MetaAction { Kind = ActionKind(type) };
|
||||
|
|
@ -179,11 +188,11 @@ internal static class VtankMetaProfileSerializer
|
|||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
reader.Expect("i");
|
||||
value.Children.Add(ReadAction(reader, reader.ReadInt(), depth + 1));
|
||||
value.Children.Add(ReadAction(reader, reader.ReadInt(), depth + 1, spells));
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
ReadEmbeddedNavigation(reader, value);
|
||||
ReadEmbeddedNavigation(reader, value, spells);
|
||||
break;
|
||||
case 5:
|
||||
reader.Expect(TablePrefix, "2", "s", "st", "s");
|
||||
|
|
@ -235,7 +244,7 @@ internal static class VtankMetaProfileSerializer
|
|||
return value;
|
||||
}
|
||||
|
||||
private static void ReadEmbeddedNavigation(LineReader reader, MetaAction target)
|
||||
private static void ReadEmbeddedNavigation(LineReader reader, MetaAction target, ISpellCatalog spells)
|
||||
{
|
||||
reader.Expect("ba");
|
||||
int serializedCharacters = reader.ReadCount();
|
||||
|
|
@ -243,7 +252,7 @@ internal static class VtankMetaProfileSerializer
|
|||
int statedNodeCount = reader.ReadCount();
|
||||
if (serializedCharacters <= 5)
|
||||
{
|
||||
target.Text = EmptyNavigation();
|
||||
target.EmbeddedRoute = new NavigationSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +279,15 @@ internal static class VtankMetaProfileSerializer
|
|||
}
|
||||
if (actualNodeCount != statedNodeCount)
|
||||
throw reader.Error("Embedded VTank navigation node counts do not match.");
|
||||
target.Text = string.Join("\r\n", lines) + "\r\n";
|
||||
|
||||
// Parse the reassembled "uTank2 NAV 1.2" text through the same
|
||||
// reader a top-level binary .nav import uses (item B: MetaAction
|
||||
// carries a typed NavigationSettings, not a re-parse-me blob).
|
||||
string blob = string.Join("\r\n", lines) + "\r\n";
|
||||
var route = new NavigationSettings();
|
||||
if (!VtankNavRouteSerializer.TryLoad(blob, route, spells, out string navError))
|
||||
throw reader.Error($"Embedded VTank navigation route is invalid: {navError}");
|
||||
target.EmbeddedRoute = route;
|
||||
}
|
||||
|
||||
private static void ReadNavigationNode(LineReader reader, List<string> lines)
|
||||
|
|
@ -292,8 +309,6 @@ internal static class VtankMetaProfileSerializer
|
|||
lines.Add(reader.Read());
|
||||
}
|
||||
|
||||
private static string EmptyNavigation() => "uTank2 NAV 1.2\r\n1\r\n0\r\n";
|
||||
|
||||
private static string NormalizeNewlines(string value) => value
|
||||
.Replace("\r\n", "\n", StringComparison.Ordinal)
|
||||
.Replace('\r', '\n');
|
||||
|
|
|
|||
|
|
@ -198,7 +198,14 @@ internal static class VtankNavRouteSerializer
|
|||
0f,
|
||||
IsOutdoor: true);
|
||||
|
||||
private static void Apply(NavigationSettings source, NavigationSettings target)
|
||||
/// <summary>
|
||||
/// Copies every field <see cref="NavigationSettings"/> owns from
|
||||
/// <paramref name="source"/> into the live <paramref name="target"/>
|
||||
/// instance (internal, not private, so <c>MetaEngine</c>'s embedded-
|
||||
/// route consumer can reuse the exact same copy the top-level import
|
||||
/// path uses instead of re-parsing already-typed data).
|
||||
/// </summary>
|
||||
internal static void Apply(NavigationSettings source, NavigationSettings target)
|
||||
{
|
||||
target.Mode = source.Mode;
|
||||
target.FollowTargetObjectId = source.FollowTargetObjectId;
|
||||
|
|
|
|||
|
|
@ -286,9 +286,8 @@ public sealed class MetafSerializerTests
|
|||
|
||||
/// <summary>
|
||||
/// Walks every <see cref="MetaActionKind.LoadEmbeddedNavigationRoute"/>
|
||||
/// action's synthesized "uTank2 NAV 1.2" blob (<see cref="MetaAction.Text"/>
|
||||
/// — the shape <c>MetaEngine.LoadEmbeddedNavigationRoute</c> already
|
||||
/// expects) looking for a waypoint by object name.
|
||||
/// action's typed <see cref="MetaAction.EmbeddedRoute"/> looking for a
|
||||
/// waypoint by object name.
|
||||
/// </summary>
|
||||
private static RouteWaypoint FindNavWaypoint(MetaProfile profile, string objectName)
|
||||
{
|
||||
|
|
@ -303,11 +302,9 @@ public sealed class MetafSerializerTests
|
|||
|
||||
private static RouteWaypoint? FindNavWaypoint(MetaAction action, string objectName)
|
||||
{
|
||||
if (action.Kind == MetaActionKind.LoadEmbeddedNavigationRoute)
|
||||
if (action.Kind == MetaActionKind.LoadEmbeddedNavigationRoute
|
||||
&& action.EmbeddedRoute is { } nav)
|
||||
{
|
||||
var nav = new NavigationSettings();
|
||||
Assert.True(VtankNavRouteSerializer.TryLoad(
|
||||
action.Text, nav, NoOpSpellCatalog.Instance, out string error), error);
|
||||
foreach (RouteWaypoint waypoint in nav.Waypoints)
|
||||
{
|
||||
if (waypoint.ObjectName == objectName)
|
||||
|
|
@ -454,8 +451,21 @@ public sealed class MetafSerializerTests
|
|||
private static void AssertActionsEqual(MetaAction expected, MetaAction actual)
|
||||
{
|
||||
Assert.Equal(expected.Kind, actual.Kind);
|
||||
if (expected.Kind != MetaActionKind.LoadEmbeddedNavigationRoute)
|
||||
if (expected.Kind == MetaActionKind.LoadEmbeddedNavigationRoute)
|
||||
{
|
||||
// Item B: MetaAction carries a typed EmbeddedRoute, not a
|
||||
// re-parse-me blob in Text (which is now always empty for this
|
||||
// action kind) — compare the routes waypoint-by-waypoint
|
||||
// instead of skipping the comparison entirely.
|
||||
Assert.Equal(expected.Text, actual.Text);
|
||||
Assert.NotNull(expected.EmbeddedRoute);
|
||||
Assert.NotNull(actual.EmbeddedRoute);
|
||||
AssertNavigationEqual(expected.EmbeddedRoute!, actual.EmbeddedRoute!);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expected.Text, actual.Text);
|
||||
}
|
||||
Assert.Equal(expected.SecondaryText, actual.SecondaryText);
|
||||
Assert.Equal(expected.Number, actual.Number, 6);
|
||||
Assert.Equal(expected.SecondaryNumber, actual.SecondaryNumber, 6);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue