namespace AcDream.Plugins.MossTank.Tests; public sealed class VtankMetaProfileSerializerTests { [Fact] public void LoadsKnownTypedCondActRecord() { const string source = "1\r\nCondAct\r\n5\r\nCType\r\nAType\r\n" + "CData\r\nAData\r\nState\r\nn\r\nn\r\nn\r\nn\r\nn\r\n1\r\n" + "i\r\n1\r\ni\r\n2\r\ni\r\n0\r\ns\r\n/say ready\r\n" + "s\r\nDefault\r\n"; Assert.True(VtankMetaProfileSerializer.TryLoad( source, out MetaProfile profile, out string error), error); MetaRule rule = Assert.Single(profile.Rules); Assert.Equal(MetaConditionKind.Always, rule.Condition.Kind); Assert.Equal(MetaActionKind.ChatCommand, rule.Action.Kind); Assert.Equal("/say ready", rule.Action.Text); Assert.Equal("Default", rule.State); Assert.Equal(source, VtankMetaProfileSerializer.Save(profile)); } [Fact] public void RoundTripPreservesEveryVtankConditionActionAndEmbeddedNav() { MetaCondition[] conditions = BuildConditions(); MetaAction[] actions = BuildActions(); var profile = new MetaProfile(); for (int index = 0; index < conditions.Length; index++) { profile.Rules.Add(new MetaRule { State = $"State {index}", Condition = conditions[index], Action = actions[index % actions.Length], }); } for (int index = conditions.Length; index < actions.Length; index++) { profile.Rules.Add(new MetaRule { State = $"Action {index}", Condition = MetaCondition.Always(), Action = actions[index], }); } string first = VtankMetaProfileSerializer.Save(profile); Assert.True(VtankMetaProfileSerializer.TryLoad( first, out MetaProfile loaded, out string error), error); string second = VtankMetaProfileSerializer.Save(loaded); Assert.Equal(first, second); Assert.Equal(profile.Rules.Count, loaded.Rules.Count); MetaCondition priority = loaded.Rules.Single(rule => rule.Condition.Kind == MetaConditionKind.MonsterPriorityCountWithinDistance) .Condition; Assert.Equal(2, priority.Number); Assert.Equal(18.5, priority.SecondaryNumber); Assert.Equal(7, priority.TertiaryNumber); MetaAction embedded = loaded.Rules.Select(static rule => rule.Action) .First(action => action.Kind == MetaActionKind.LoadEmbeddedNavigationRoute); Assert.Equal("One point", embedded.SecondaryText); Assert.Equal("uTank2 NAV 1.2\r\n4\r\n1\r\n0\r\n1\r\n2\r\n3\r\n0\r\n", embedded.Text); Assert.Contains("s\r\n", first, StringComparison.Ordinal); } [Fact] public void DisabledNativeRuleIsNotExportedAsExecutableLegacyRule() { var profile = new MetaProfile { Rules = [ new MetaRule { Enabled = false, Condition = MetaCondition.Always(), Action = new MetaAction { Kind = MetaActionKind.ChatCommand, Text = "/say must not run", }, }, ], }; string source = VtankMetaProfileSerializer.Save(profile); Assert.DoesNotContain("must not run", source, StringComparison.Ordinal); Assert.True(VtankMetaProfileSerializer.TryLoad( source, out MetaProfile loaded, out string error), error); Assert.Empty(loaded.Rules); } private static MetaCondition[] BuildConditions() => [ C(MetaConditionKind.Never), C(MetaConditionKind.Always), C(MetaConditionKind.All, children: [C(MetaConditionKind.Always)]), C(MetaConditionKind.Any, children: [C(MetaConditionKind.Never)]), C(MetaConditionKind.ChatMessage, "^ready$"), C(MetaConditionKind.PackSlotsLessThanOrEqual, number: 7), C(MetaConditionKind.SecondsInStateGreaterThanOrEqual, number: 12), C(MetaConditionKind.NavigationRouteEmpty), C(MetaConditionKind.CharacterDeath), C(MetaConditionKind.AnyVendorOpen), C(MetaConditionKind.VendorClosed), C(MetaConditionKind.InventoryItemCountLessThanOrEqual, "Prismatic Taper", 5), C(MetaConditionKind.InventoryItemCountGreaterThanOrEqual, "Pyreal", 10), C(MetaConditionKind.MonsterNameCountWithinDistance, "Olthoi.*", 3, 22.25), C(MetaConditionKind.MonsterPriorityCountWithinDistance, number: 2, secondaryNumber: 18.5, tertiaryNumber: 7), C(MetaConditionKind.NeedToBuff), C(MetaConditionKind.NoMonstersWithinDistance, number: 9.5), C(MetaConditionKind.LandblockEquals, number: unchecked((int)0x8B370000u)), C(MetaConditionKind.LandcellEquals, number: unchecked((int)0x8B37E3A1u)), C(MetaConditionKind.PortalspaceEntered), C(MetaConditionKind.PortalspaceExited), C(MetaConditionKind.Not, children: [C(MetaConditionKind.Never)]), C(MetaConditionKind.PersistentSecondsInStateGreaterThanOrEqual, number: 30), C(MetaConditionKind.TimeLeftOnSpellGreaterThanOrEqual, number: 3179, secondaryNumber: 45), C(MetaConditionKind.BurdenPercentGreaterThanOrEqual, number: 110), C(MetaConditionKind.DistanceFromAnyRoutePointGreaterThanOrEqual, number: 13.75), C(MetaConditionKind.Expression, "getvar['go']==1"), C(MetaConditionKind.ChatMessageCapture, "^(?.+)$", secondary: "2;4"), ]; private static MetaAction[] BuildActions() => [ A(MetaActionKind.None), A(MetaActionKind.SetMetaState, "Hunt"), A(MetaActionKind.ChatCommand, "/say hello"), A(MetaActionKind.All, children: [ A(MetaActionKind.ChatCommand, "/say first"), A(MetaActionKind.ExpressionAction, "setvar['done',1]"), ]), A(MetaActionKind.LoadEmbeddedNavigationRoute, "uTank2 NAV 1.2\r\n4\r\n1\r\n0\r\n1\r\n2\r\n3\r\n0\r\n", "One point"), A(MetaActionKind.CallMetaState, "Worker", "ReturnHere"), A(MetaActionKind.ReturnFromCall), A(MetaActionKind.ExpressionAction, "setvar['x',2]"), A(MetaActionKind.ChatExpression, "cstr[getvar['x']]"), A(MetaActionKind.SetWatchdog, "Recover", number: 12.5, secondaryNumber: 4.75), A(MetaActionKind.ClearWatchdog), A(MetaActionKind.GetVtankOption, "OpenDoors", "doors"), A(MetaActionKind.SetVtankOption, "OpenDoors", "istrue[1]"), A(MetaActionKind.CreateView, "myview", ""), A(MetaActionKind.DestroyView, "myview"), A(MetaActionKind.DestroyAllViews), ]; private static MetaCondition C( MetaConditionKind kind, string text = "", double number = 0, double secondaryNumber = 0, double tertiaryNumber = 0, string secondary = "", List? children = null) => new() { Kind = kind, Text = text, SecondaryText = secondary, Number = number, SecondaryNumber = secondaryNumber, TertiaryNumber = tertiaryNumber, Children = children ?? [], }; private static MetaAction A( MetaActionKind kind, string text = "", string secondary = "", double number = 0, double secondaryNumber = 0, List? children = null) => new() { Kind = kind, Text = text, SecondaryText = secondary, Number = number, SecondaryNumber = secondaryNumber, Children = children ?? [], }; }