diff --git a/code/HellMapManager/Cores/MapDatabase.cs b/code/HellMapManager/Cores/MapDatabase.cs index 42f6eca..5213de7 100644 --- a/code/HellMapManager/Cores/MapDatabase.cs +++ b/code/HellMapManager/Cores/MapDatabase.cs @@ -8,7 +8,7 @@ namespace HellMapManager.Cores; public partial class MapDatabase() { public ReaderWriterLockSlim _lock = new(); - public const int Version = 1004; + public const int Version = 1006; public MapFile? Current; public Settings Settings = new(); diff --git a/code/HellMapManager/Helpers/Mapper.cs b/code/HellMapManager/Helpers/Mapper.cs index 240998a..d79476d 100644 --- a/code/HellMapManager/Helpers/Mapper.cs +++ b/code/HellMapManager/Helpers/Mapper.cs @@ -328,6 +328,20 @@ public int GetExitCost(Exit exit) } return exit.Cost; } + //根据上下文获取房间标签 + public List GetRoomTags(Room room) + { + List result = [.. room.Tags]; + if (Context.RoomTags.TryGetValue(room.Key, out var list)) + { + result.AddRange(list); + } + if (Context.RoomTags.TryGetValue("", out var publiclist)) + { + result.AddRange(publiclist); + } + return result; + } //获取房间出口信息 public List GetRoomExits(Room room) { @@ -344,7 +358,7 @@ public List GetRoomExits(Room room) MapFile.Map.Shortcuts.ForEach(e => { //符合条件则加入列表 - if (ValueTag.ValidateConditions(room.Tags, e.RoomConditions)) + if (ValueTag.ValidateConditions(GetRoomTags(room), e.RoomConditions)) { result.Add(e); } @@ -353,7 +367,7 @@ public List GetRoomExits(Room room) Context.Shortcuts.ForEach(e => { //符合条件则加入列表 - if (ValueTag.ValidateConditions(room.Tags, e.RoomConditions)) + if (ValueTag.ValidateConditions(GetRoomTags(room), e.RoomConditions)) { result.Add(e); } diff --git a/code/HellMapManager/Misc/AppVersion.cs b/code/HellMapManager/Misc/AppVersion.cs index fd9013d..a1a6420 100644 --- a/code/HellMapManager/Misc/AppVersion.cs +++ b/code/HellMapManager/Misc/AppVersion.cs @@ -2,7 +2,7 @@ namespace HellMapManager.Misc; public class AppVersion(int major, int minor, int patch) { - public static AppVersion Current { get; } = new(0, 20260116, 0); + public static AppVersion Current { get; } = new(0, 20260221, 0); public int Major { get; } = major; public int Minor { get; } = minor; public int Patch { get; } = patch; diff --git a/code/HellMapManager/Models/Context.cs b/code/HellMapManager/Models/Context.cs index d42f2e8..a242b2d 100644 --- a/code/HellMapManager/Models/Context.cs +++ b/code/HellMapManager/Models/Context.cs @@ -4,6 +4,14 @@ namespace HellMapManager.Models; +public class RoomTag(string room, string key, int value) +{ + public string Room { get; set; } = room; + public string Key { get; set; } = key; + public int Value { get; set; } = value; +} + + public class Path : Exit { public string From { get; set; } = ""; @@ -31,7 +39,7 @@ public class Environment public List Blacklist = []; public List BlockedLinks = []; public List CommandCosts = []; - + public List RoomTags = []; } //移动规划的上下文 @@ -55,6 +63,7 @@ public class Context public Dictionary> BlockedLinks = []; //临时指令消耗列表 public Dictionary> CommandCosts = []; + public Dictionary> RoomTags = []; public Context ClearTags() { Tags.Clear(); @@ -68,6 +77,26 @@ public Context WithTags(List tags) } return this; } + public Context ClearRoomTags() + { + RoomTags.Clear(); + return this; + } + public Context WithRoomTags(List tags) + { + foreach (var tag in tags) + { + if (RoomTags.TryGetValue(tag.Room, out var roomTags)) + { + RoomTags[tag.Room].Add(new ValueTag(tag.Key, tag.Value)); + } + else + { + RoomTags[tag.Room] = [new ValueTag(tag.Key, tag.Value)]; + } + } + return this; + } public Context ClearRoomConditions() { RoomConditions.Clear(); @@ -201,6 +230,7 @@ public static Context FromEnvironment(Environment? env) context.WithPaths(env.Paths); context.WithBlockedLinks(env.BlockedLinks); context.WithCommandCosts(env.CommandCosts); + context.WithRoomTags(env.RoomTags); } return context; } @@ -231,6 +261,13 @@ public Environment ToEnvironment() env.CommandCosts.Add(new CommandCost(c.Key, t.Key, t.Value)); } } + foreach (var r in RoomTags) + { + foreach (var t in r.Value) + { + env.RoomTags.Add(new RoomTag(r.Key, t.Key, t.Value)); + } + } return env; } public bool HasTag(string key, int value) diff --git a/code/HellMapManager/Models/MapperOption.cs b/code/HellMapManager/Models/MapperOption.cs index 8e10d99..1546916 100644 --- a/code/HellMapManager/Models/MapperOption.cs +++ b/code/HellMapManager/Models/MapperOption.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Reflection.Metadata.Ecma335; namespace HellMapManager.Models; @@ -13,6 +14,8 @@ public class MapperOptions public bool DisableShortcuts = false; public Dictionary CommandWhitelist = new(); + + public List CommandNotContains = new(); public MapperOptions WithMaxExitCost(int cost) { MaxExitCost = cost; @@ -41,9 +44,32 @@ public MapperOptions ClearCommandWhitelist() CommandWhitelist.Clear(); return this; } + public MapperOptions WithCommandNotContains(List list) + { + CommandNotContains = list; + return this; + } + public MapperOptions ClearCommandNotContains() + { + CommandNotContains.Clear(); + return this; + } public bool ValidateCommand(string command) { - if (CommandWhitelist.Count == 0) return true; - return CommandWhitelist.ContainsKey(command); + if (CommandWhitelist.Count != 0 && !CommandWhitelist.ContainsKey(command)) + { + return false; + } + if (CommandNotContains.Count != 0) + { + foreach (var item in CommandNotContains) + { + if (command.Contains(item)) + { + return false; + } + } + } + return true; } } \ No newline at end of file diff --git a/code/HellMapManager/Models/Settings.cs b/code/HellMapManager/Models/Settings.cs index 66047b0..5d013a8 100644 --- a/code/HellMapManager/Models/Settings.cs +++ b/code/HellMapManager/Models/Settings.cs @@ -36,12 +36,12 @@ public static APIConfig From(Settings settings) } public void Apply(Settings settings) { - settings.APIPort = APIPort; + settings.APIPort = APIPort??Settings.DefaultAPIPort; settings.APIUserName = APIUserName; settings.APIPassWord = APIPassWord; settings.APIEnabled = APIEnabled; } - public int APIPort { get; set; } = 0; + public int? APIPort { get; set; } = 0; public string APIUserName { get; set; } = ""; public string APIPassWord { get; set; } = ""; public bool APIEnabled { get; set; } = false; diff --git a/code/HellMapManager/Services/API/Models.cs b/code/HellMapManager/Services/API/Models.cs index 33b118f..048df32 100644 --- a/code/HellMapManager/Services/API/Models.cs +++ b/code/HellMapManager/Services/API/Models.cs @@ -1266,6 +1266,43 @@ public static List ToCommandCostList(List command public int Cost { get; set; } = 0; } +public class RoomTagModel() +{ + public static RoomTagModel From(RoomTag tag) + { + return new RoomTagModel() + { + Room = tag.Room, + Key = tag.Key, + Value = tag.Value, + }; + } + public RoomTag ToRoomTag() + { + return new RoomTag(Room, Key, Value); + } + public static List FromRoomTagList(List tags) + { + var list = new List(); + foreach (var tag in tags.Where(x => x is not null)) + { + list.Add(From(tag)); + } + return list; + } + public static List ToRoomTagList(List tagModels) + { + var list = new List(); + foreach (var tagModel in tagModels.Where(x => x is not null)) + { + list.Add(tagModel.ToRoomTag()); + } + return list; + } + public string Room { get; set; } = ""; + public string Key { get; set; } = ""; + public int Value { get; set; } = 1; +} public class EnvironmentModel() { public static EnvironmentModel From(Models.Environment data) @@ -1281,6 +1318,7 @@ public static EnvironmentModel From(Models.Environment data) Blacklist = [.. data.Blacklist], BlockedLinks = LinkModel.FromLinkList(data.BlockedLinks), CommandCosts = CommandCostModel.FromCommandCostList(data.CommandCosts), + RoomTags = RoomTagModel.FromRoomTagList(data.RoomTags), }; } public Models.Environment ToEnvironment() @@ -1296,6 +1334,7 @@ public Models.Environment ToEnvironment() Blacklist = [.. Blacklist ?? []], BlockedLinks = LinkModel.ToLinkList(BlockedLinks ?? []), CommandCosts = CommandCostModel.ToCommandCostList(CommandCosts ?? []), + RoomTags = RoomTagModel.ToRoomTagList(RoomTags ?? []), }; } public List? Tags { get; set; } = []; @@ -1307,7 +1346,7 @@ public Models.Environment ToEnvironment() public List? Blacklist { get; set; } = []; public List? BlockedLinks { get; set; } = []; public List? CommandCosts { get; set; } = []; - + public List? RoomTags { get; set; } = []; } public class MapperOptionsModel { @@ -1319,6 +1358,7 @@ public static MapperOptionsModel From(MapperOptions options) MaxTotalCost = options.MaxTotalCost, DisableShortcuts = options.DisableShortcuts, CommandWhitelist = [.. options.CommandWhitelist.Keys], + CommandNotContains = [.. options.CommandNotContains], }; } public MapperOptions ToMapperOptions() @@ -1328,13 +1368,14 @@ public MapperOptions ToMapperOptions() MaxExitCost = MaxExitCost ?? 0, MaxTotalCost = MaxTotalCost ?? 0, DisableShortcuts = DisableShortcuts ?? false, - }.WithCommandWhitelist(CommandWhitelist); + }.WithCommandWhitelist(CommandWhitelist).WithCommandNotContains(CommandNotContains); } public int? MaxExitCost { get; set; } = 0; public int? MaxTotalCost { get; set; } = 0; public bool? DisableShortcuts { get; set; } = false; public List CommandWhitelist { get; set; } = []; + public List CommandNotContains { get; set; } = []; } public class InputQueryPathAny() diff --git a/code/HellMapManager/Windows/EditExitWindow/ExitForm.cs b/code/HellMapManager/Windows/EditExitWindow/ExitForm.cs index 1a3c7da..fc64dba 100644 --- a/code/HellMapManager/Windows/EditExitWindow/ExitForm.cs +++ b/code/HellMapManager/Windows/EditExitWindow/ExitForm.cs @@ -29,7 +29,7 @@ public Exit ToExit() Command = Command, To = To, Conditions = [.. Conditions], - Cost = Cost + Cost = Cost??1, }; } public Exit? Raw; @@ -37,7 +37,7 @@ public Exit ToExit() public string Command { get; set; } = ""; public string To { get; set; } = ""; public ObservableCollection Conditions { get; set; } = []; - public int Cost { get; set; } = 1; + public int? Cost { get; set; } = 1; public void Arrange() { diff --git a/code/HellMapManager/Windows/EditShortcutWindow/ShortcutForm.cs b/code/HellMapManager/Windows/EditShortcutWindow/ShortcutForm.cs index 2865669..f636743 100644 --- a/code/HellMapManager/Windows/EditShortcutWindow/ShortcutForm.cs +++ b/code/HellMapManager/Windows/EditShortcutWindow/ShortcutForm.cs @@ -34,7 +34,7 @@ public Shortcut ToShortcut() To = To, RoomConditions = [.. RoomConditions], Conditions = [.. Conditions], - Cost = Cost, + Cost = Cost??1, Group = Group, Desc = Desc, }; @@ -78,7 +78,7 @@ public void Arrange() public ObservableCollection Conditions { get; set; } = []; - public int Cost { get; set; } = 1; + public int? Cost { get; set; } = 1; public string Group { get; set; } = ""; public string Desc { get; set; } = ""; public string Validate() diff --git a/code/HellMapManager/Windows/NewConditionWindow/ConditionForm.cs b/code/HellMapManager/Windows/NewConditionWindow/ConditionForm.cs index 0a10dad..1a15da6 100644 --- a/code/HellMapManager/Windows/NewConditionWindow/ConditionForm.cs +++ b/code/HellMapManager/Windows/NewConditionWindow/ConditionForm.cs @@ -19,12 +19,12 @@ public ConditionForm(ValueCondition model, ExternalValidator checker) } public ValueCondition ToCondition() { - return new ValueCondition(Key, Value, Not); + return new ValueCondition(Key, Value??1, Not); } public ValueCondition? Raw; public ExternalValidator ExternalValidator; public bool Not { get; set; } = false; - public int Value { get; set; } = 1; + public int? Value { get; set; } = 1; public string Key { get; set; } = ""; public string Validate() { diff --git a/code/HellMapManager/Windows/NewTagWindow/TagForm.cs b/code/HellMapManager/Windows/NewTagWindow/TagForm.cs index a058033..1f01126 100644 --- a/code/HellMapManager/Windows/NewTagWindow/TagForm.cs +++ b/code/HellMapManager/Windows/NewTagWindow/TagForm.cs @@ -18,12 +18,12 @@ public TagForm(ValueTag model, ExternalValidator checker) } public ValueTag ToTag() { - return new ValueTag(Key, Value); + return new ValueTag(Key, Value??1); } public ValueTag? Raw; public ExternalValidator ExternalValidator; public string Key { get; set; } = ""; - public int Value { get; set; } = 1; + public int? Value { get; set; } = 1; public string Validate() { var err = ExternalValidator(this); diff --git a/code/TestProject/APIModelTest.cs b/code/TestProject/APIModelTest.cs index 1c388c0..e906fe1 100644 --- a/code/TestProject/APIModelTest.cs +++ b/code/TestProject/APIModelTest.cs @@ -1538,6 +1538,14 @@ public void EnvironmentModelTest() Cost = 20, } ]), + RoomTags = new([ + new RoomTagModel() + { + Room="RoomTagRoom", + Key = "roomTagKey", + Value = 2, + } + ]), }; var json = JsonSerializer.Serialize(model, APIJsonSerializerContext.Default.EnvironmentModel); var deserialized = JsonSerializer.Deserialize(json, APIJsonSerializerContext.Default.EnvironmentModel); @@ -1606,6 +1614,14 @@ public void EnvironmentModelTest() Assert.Equal(model.CommandCosts[i].To, deserialized?.CommandCosts[i].To); Assert.Equal(model.CommandCosts[i].Cost, deserialized?.CommandCosts[i].Cost); } + Assert.Equal(model.RoomTags.Count, deserialized?.RoomTags.Count); + for (int i = 0; i < model.RoomTags.Count; i++) + { + Assert.Equal(model.RoomTags[i].Room, deserialized?.RoomTags[i].Room); + Assert.Equal(model.RoomTags[i].Key, deserialized?.RoomTags[i].Key); + Assert.Equal(model.RoomTags[i].Value, deserialized?.RoomTags[i].Value); + } + var emptyModel = new EnvironmentModel(); var fromEmpty = EnvironmentModel.From(emptyModel.ToEnvironment()); Assert.Empty(fromEmpty.Tags); @@ -1617,6 +1633,8 @@ public void EnvironmentModelTest() Assert.Empty(fromEmpty.Blacklist); Assert.Empty(fromEmpty.BlockedLinks); Assert.Empty(fromEmpty.CommandCosts); + Assert.Empty(fromEmpty.RoomTags); + } [Fact] public void MapperOptionsModelTest() @@ -1626,7 +1644,8 @@ public void MapperOptionsModelTest() MaxExitCost = 500, MaxTotalCost = 2000, DisableShortcuts = true, - CommandWhitelist = ["cmd1", "cmd2"] + CommandWhitelist = ["cmd1", "cmd2"], + CommandNotContains = ["cmd3", "cmd4"] }; model.CommandWhitelist.Sort(); var json = JsonSerializer.Serialize(model, APIJsonSerializerContext.Default.MapperOptionsModel); @@ -1636,6 +1655,8 @@ public void MapperOptionsModelTest() Assert.Equal(model.DisableShortcuts, deserialized?.DisableShortcuts); deserialized?.CommandWhitelist.Sort(); Assert.Equal(model.CommandWhitelist, deserialized?.CommandWhitelist); + deserialized?.CommandNotContains.Sort(); + Assert.Equal(model.CommandNotContains, deserialized?.CommandNotContains); var raw = model.ToMapperOptions(); var fromRaw = MapperOptionsModel.From(raw); Assert.Equal(model.MaxExitCost, fromRaw.MaxExitCost); @@ -1643,12 +1664,15 @@ public void MapperOptionsModelTest() Assert.Equal(model.DisableShortcuts, fromRaw.DisableShortcuts); fromRaw.CommandWhitelist.Sort(); Assert.Equal(model.CommandWhitelist, fromRaw.CommandWhitelist); + fromRaw.CommandNotContains.Sort(); + Assert.Equal(model.CommandNotContains, fromRaw.CommandNotContains); var emptyModel = new MapperOptionsModel(); var fromEmpty = MapperOptionsModel.From(emptyModel.ToMapperOptions()); Assert.Equal(0, fromEmpty.MaxExitCost); Assert.Equal(0, fromEmpty.MaxTotalCost); Assert.False(fromEmpty.DisableShortcuts); Assert.Empty(fromEmpty.CommandWhitelist); + Assert.Empty(fromEmpty.CommandNotContains); } [Fact] public void InputQueryPathAnyTest() diff --git a/code/TestProject/APIServerTest.cs b/code/TestProject/APIServerTest.cs index eed4c09..2e5bd55 100644 --- a/code/TestProject/APIServerTest.cs +++ b/code/TestProject/APIServerTest.cs @@ -37,7 +37,7 @@ public async Task TestVersion() Assert.Equal(MapDatabase.Version, mapDatabase.APIVersion()); var resp = await Post($"http://localhost:{server.Port}" + "/api/version", typeof(string), ""); var result = JsonSerializer.Deserialize(resp, typeof(int), APIJsonSerializerContext.Default) as int?; - Assert.Equal(1004, result); + Assert.Equal(1006, result); await server.Stop(); } [Fact] @@ -1382,6 +1382,12 @@ private static void InitContext(Context ctx) Conditions=[new ValueCondition("noctxpath", 1,true)], Cost=2, }, + new Shortcut(){ + To="key5", + Command="1>5C", + RoomConditions=[new ValueCondition("rt1", 1,false)], + Cost=2, + }, ]); ctx.ClearPaths().WithPaths([ new HellMapManager.Models.Path(){ @@ -2524,7 +2530,7 @@ public async Task TestMap() rooms = JsonSerializer.Deserialize(resp, typeof(List), APIJsonSerializerContext.Default) as List ?? []; rooms.Sort(); Assert.Equal("key1;key3;key6", string.Join(";", rooms)); - opt.WithCommandWhitelist(["1>2","1>3","2>1","2>3","3>1","3>3","3>4","4>3","4>5","5>3","6>3","A>6C"]); + opt.WithCommandWhitelist(["1>2", "1>3", "2>1", "2>3", "3>1", "3>3", "3>4", "4>3", "4>5", "5>3", "6>3", "A>6C"]); resp = await Post($"http://localhost:{server.Port}" + "/api/db/trackexit", typeof(InputTrackExit), new InputTrackExit() { Start = "key6", @@ -2575,6 +2581,219 @@ public async Task TestMap() rooms.Sort(); Assert.Equal("key3;key6", string.Join(";", rooms)); + + //CommandNotContains + opt = new MapperOptions(); + InitContext(ctx); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/trackexit", typeof(InputTrackExit), new InputTrackExit() + { + Start = "key6", + Command = "A>1", + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + exit = JsonSerializer.Deserialize(resp, typeof(string), APIJsonSerializerContext.Default) as string ?? ""; + Assert.Equal("key1", exit); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathall", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>3;3>4;4>5", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathany", typeof(InputQueryPathAny), new InputQueryPathAny() + { + From = ["key6"], + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathordered", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>3;3>4;4>5", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/dilate", typeof(InputDilate), new InputDilate() + { + Source = ["key6"], + Iterations = 1, + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + rooms = JsonSerializer.Deserialize(resp, typeof(List), APIJsonSerializerContext.Default) as List ?? []; + rooms.Sort(); + Assert.Equal("key1;key3;key6", string.Join(";", rooms)); + opt.WithCommandNotContains([">3"]); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/trackexit", typeof(InputTrackExit), new InputTrackExit() + { + Start = "key1", + Command = "1>3", + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + exit = JsonSerializer.Deserialize(resp, typeof(string), APIJsonSerializerContext.Default) as string ?? ""; + Assert.Equal("", exit); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathall", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key1", + Target = ["key4", "key6"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>6C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathany", typeof(InputQueryPathAny), new InputQueryPathAny() + { + From = ["key4"], + Target = ["key6", "key3"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>6C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathordered", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key1", + Target = ["key5", "key6"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>6C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/dilate", typeof(InputDilate), new InputDilate() + { + Source = ["key4"], + Iterations = 1, + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + rooms = JsonSerializer.Deserialize(resp, typeof(List), APIJsonSerializerContext.Default) as List ?? []; + rooms.Sort(); + Assert.Equal("key1;key4;key5;key6", string.Join(";", rooms)); + + //RoomTags + opt = new MapperOptions(); + InitContext(ctx); + ctx.WithRoomTags([new RoomTag("key3", "rt1", 1)]); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/trackexit", typeof(InputTrackExit), new InputTrackExit() + { + Start = "key6", + Command = "A>1", + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + exit = JsonSerializer.Deserialize(resp, typeof(string), APIJsonSerializerContext.Default) as string ?? ""; + Assert.Equal("key1", exit); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathall", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>3;1>5C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathany", typeof(InputQueryPathAny), new InputQueryPathAny() + { + From = ["key6"], + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathordered", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>3;1>5C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/dilate", typeof(InputDilate), new InputDilate() + { + Source = ["key6"], + Iterations = 1, + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + rooms = JsonSerializer.Deserialize(resp, typeof(List), APIJsonSerializerContext.Default) as List ?? []; + rooms.Sort(); + Assert.Equal("key1;key3;key6", string.Join(";", rooms)); + + //RoomTags public + opt = new MapperOptions(); + InitContext(ctx); + ctx.WithRoomTags([new RoomTag("", "rt1", 1)]); + + resp = await Post($"http://localhost:{server.Port}" + "/api/db/trackexit", typeof(InputTrackExit), new InputTrackExit() + { + Start = "key6", + Command = "A>1", + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + exit = JsonSerializer.Deserialize(resp, typeof(string), APIJsonSerializerContext.Default) as string ?? ""; + Assert.Equal("key1", exit); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathall", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>5C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathany", typeof(InputQueryPathAny), new InputQueryPathAny() + { + From = ["key6"], + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/querypathordered", typeof(InputQueryPath), new InputQueryPath() + { + Start = "key6", + Target = ["key1", "key5"], + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + queryresult = JsonSerializer.Deserialize(resp, typeof(QueryResultModel), APIJsonSerializerContext.Default) as QueryResultModel; + Assert.NotNull(queryresult); + Assert.Equal("A>1;1>5C", Step.JoinCommands(";", StepModel.ToStepList(queryresult!.Steps))); + resp = await Post($"http://localhost:{server.Port}" + "/api/db/dilate", typeof(InputDilate), new InputDilate() + { + Source = ["key6"], + Iterations = 1, + Environment = EnvironmentModel.From(ctx.ToEnvironment()), + Options = MapperOptionsModel.From(opt), + }); + rooms = JsonSerializer.Deserialize(resp, typeof(List), APIJsonSerializerContext.Default) as List ?? []; + rooms.Sort(); + Assert.Equal("key1;key3;key5;key6", string.Join(";", rooms)); + await server.Stop(); return; } diff --git a/code/TestProject/MapTest.cs b/code/TestProject/MapTest.cs index 8243680..6a21a5a 100644 --- a/code/TestProject/MapTest.cs +++ b/code/TestProject/MapTest.cs @@ -126,6 +126,12 @@ private static void InitContext(Context ctx) Conditions=[new ValueCondition("noctxpath", 1,true)], Cost=2, }, + new Shortcut(){ + To="key5", + Command="1>5C", + RoomConditions=[new ValueCondition("rt1", 1,false)], + Cost=2, + }, ]); ctx.ClearPaths().WithPaths([ new Path(){ @@ -533,6 +539,63 @@ public void TestMap() rooms.Sort(); Assert.Equal("key3;key6", string.Join(";", rooms)); + //CommandNotContains + opt = new MapperOptions(); + InitContext(ctx); + opt.WithCommandNotContains([">3"]); + exit = mapDatabase.APITrackExit("key1", "1>3", ctx, opt); + Assert.Equal("", exit); + qr = mapDatabase.APIQueryPathAll("key1", ["key4", "key6"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>6C", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathAny(["key4"], ["key6", "key3"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>6C", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathOrdered("key1", ["key5", "key6"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>6C", Step.JoinCommands(";", qr.Steps)); + rooms = mapDatabase.APIDilate(["key4"], 1, ctx, opt); + rooms.Sort(); + Assert.Equal("key1;key4;key5;key6", string.Join(";", rooms)); + + //RoomTags + opt = new MapperOptions(); + InitContext(ctx); + ctx.WithRoomTags([new RoomTag("key3", "rt1", 1)]); + exit = mapDatabase.APITrackExit("key6", "A>1", ctx, opt); + Assert.Equal("key1", exit); + qr = mapDatabase.APIQueryPathAll("key6", ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1;1>3;1>5C", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathAny(["key6"], ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathOrdered("key6", ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1;1>3;1>5C", Step.JoinCommands(";", qr.Steps)); + rooms = mapDatabase.APIDilate(["key6"], 1, ctx, opt); + rooms.Sort(); + Assert.Equal("key1;key3;key6", string.Join(";", rooms)); + + //RoomTags public + opt = new MapperOptions(); + InitContext(ctx); + ctx.WithRoomTags([new RoomTag("", "rt1", 1)]); + exit = mapDatabase.APITrackExit("key6", "A>1", ctx, opt); + Assert.Equal("key1", exit); + qr = mapDatabase.APIQueryPathAll("key6", ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1;1>5C", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathAny(["key6"], ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1", Step.JoinCommands(";", qr.Steps)); + qr = mapDatabase.APIQueryPathOrdered("key6", ["key1", "key5"], ctx, opt); + Assert.NotNull(qr); + Assert.Equal("A>1;1>5C", Step.JoinCommands(";", qr.Steps)); + rooms = mapDatabase.APIDilate(["key6"], 1, ctx, opt); + rooms.Sort(); + Assert.Equal("key1;key3;key5;key6", string.Join(";", rooms)); + } [Fact] public void TestNullableAPI() diff --git a/code/TestProject/MapperTest.cs b/code/TestProject/MapperTest.cs index adb29c7..8e80ae6 100644 --- a/code/TestProject/MapperTest.cs +++ b/code/TestProject/MapperTest.cs @@ -190,6 +190,12 @@ public void TestValidateExit() opt.ClearCommandWhitelist(); opt.WithCommandWhitelist(["cmd2"]); Assert.False(mapper.ValidateExit("key1", exit, 10)); + opt.ClearCommandWhitelist(); + opt.WithCommandNotContains(["md1"]); + Assert.False(mapper.ValidateExit("key1", exit, 10)); + opt.ClearCommandNotContains(); + Assert.True(mapper.ValidateExit("key1", exit, 10)); + } [Fact] public void TestWalkingStep() @@ -293,11 +299,23 @@ public void TestGetRoomExits() Command="cmd2", To="key4", RoomConditions=[ - new ValueCondition("tag1", 1, true) + new ValueCondition("tag1", 1, true), ], Cost=1, }, ]); + md.APIInsertShortcuts([new Shortcut() + { + Key="eshortcut2", + Command="ecmd2", + To="key4", + RoomConditions=[ + new ValueCondition("etag1", 1, false), + ], + Cost=1, + }, + ]); + exits = mapper.GetRoomExits(room); Assert.Equal(3, exits.Count); ctx.WithShortcuts([ @@ -327,6 +345,7 @@ public void TestGetRoomExits() opt.WithDisableShortcuts(true); exits = mapper.GetRoomExits(room); Assert.Equal(2, exits.Count); + opt.WithDisableShortcuts(false); ctx.WithPaths([new HellMapManager.Models.Path(){ From="key1", To="key6", @@ -334,7 +353,22 @@ public void TestGetRoomExits() Cost=10, }]); exits = mapper.GetRoomExits(room); - Assert.Equal(3, exits.Count); + Assert.Equal(5, exits.Count); + ctx.WithRoomTags([new RoomTag("key1", "etag1", 5)]); + exits = mapper.GetRoomExits(room); + Assert.Equal(6, exits.Count); + ctx.ClearRoomTags(); + exits = mapper.GetRoomExits(room); + Assert.Equal(5, exits.Count); + ctx.WithRoomTags([new RoomTag("key2", "etag1", 5)]); + exits = mapper.GetRoomExits(room); + Assert.Equal(5, exits.Count); + ctx.ClearRoomTags(); + ctx.WithRoomTags([new RoomTag("", "etag1", 5)]); + exits = mapper.GetRoomExits(room); + Assert.Equal(6, exits.Count); + ctx.ClearRoomTags(); + } [Fact] public void TestValidateToWalkingStep() diff --git a/code/TestProject/ModelTest.cs b/code/TestProject/ModelTest.cs index e268d5a..3746cb1 100644 --- a/code/TestProject/ModelTest.cs +++ b/code/TestProject/ModelTest.cs @@ -1620,6 +1620,20 @@ public void TestContext() Assert.Equal(1, ctx.CommandCosts["cmd1"]["to1"]); Assert.Equal(2, ctx.CommandCosts["cmd2"]["to1"]); Assert.Equal(3, ctx.CommandCosts["cmd1"]["to3"]); + + Assert.Empty(ctx.RoomTags); + Assert.Equal(ctx, ctx.WithRoomTags([ + new RoomTag("room1","tag1",1), + new RoomTag("room2","tag2",2), + new RoomTag("room2","tag3",2), + ])); + Assert.Equal(2, ctx.RoomTags.Count); + Assert.Equal("tag1", ctx.RoomTags["room1"][0].Key); + Assert.Equal(1, ctx.RoomTags["room1"][0].Value); + Assert.Equal("tag2", ctx.RoomTags["room2"][0].Key); + Assert.Equal(2, ctx.RoomTags["room2"][0].Value); + Assert.Equal("tag3", ctx.RoomTags["room2"][1].Key); + Assert.Equal(2, ctx.RoomTags["room2"][1].Value); Assert.Equal(ctx, ctx.ClearTags()); Assert.Empty(ctx.Tags); Assert.Equal(ctx, ctx.ClearRoomConditions()); @@ -1638,6 +1652,8 @@ public void TestContext() Assert.Empty(ctx.BlockedLinks); Assert.Equal(ctx, ctx.ClearCommandCosts()); Assert.Empty(ctx.CommandCosts); + Assert.Equal(ctx, ctx.ClearRoomTags()); + Assert.Empty(ctx.RoomTags); } [Fact] public void TestEnvironment() @@ -1653,6 +1669,7 @@ public void TestEnvironment() Paths = [new HellMapManager.Models.Path() { To = "to1", From = "from1", Command = "cmd1" }, new HellMapManager.Models.Path() { To = "to2", From = "from2", Command = "cmd2" }, new HellMapManager.Models.Path() { To = "to3", From = "from1", Command = "cmd3" }], BlockedLinks = [new Link("from1", "to1"), new Link("from2", "to2"), new Link("from1", "to3")], CommandCosts = [new CommandCost("cmd1", "to1", 1), new CommandCost("cmd2", "to1", 2), new CommandCost("cmd1", "to3", 3)], + RoomTags = [new RoomTag("room1", "tag1", 1), new RoomTag("room2", "tag2", 2)], }; var ctx = Context.FromEnvironment(env); Assert.Equal(2, ctx.Tags.Count); @@ -1765,6 +1782,14 @@ public void TestEnvironment() Assert.Equal(env.CommandCosts[i].To, env2.CommandCosts[i].To); Assert.Equal(env.CommandCosts[i].Cost, env2.CommandCosts[i].Cost); } + env.RoomTags.Sort((a, b) => a.Room.CompareTo(b.Room)); + env2.RoomTags.Sort((a, b) => a.Room.CompareTo(b.Room)); + for (var i = 0; i < env.RoomTags.Count; i++) + { + Assert.Equal(env.RoomTags[i].Room, env2.RoomTags[i].Room); + Assert.Equal(env.RoomTags[i].Key, env2.RoomTags[i].Key); + Assert.Equal(env.RoomTags[i].Value, env2.RoomTags[i].Value); + } } [Fact] public void TestMapperOption() diff --git a/doc/api/history.md b/doc/api/history.md index bd98fd4..2752c78 100644 --- a/doc/api/history.md +++ b/doc/api/history.md @@ -1,5 +1,13 @@ # API变更记录 +## Version 1006 + +* Environment 加入RoomTags属性 +* Context 加入RoomsTags属性,WithRoomsTags方法和RoomsTags方法 + +## Version 1005 + +* MapperOptions加入 CommandNotContains属性 ## Version 1004 diff --git a/doc/api/query.md b/doc/api/query.md index 7f7a0f0..92f77cd 100644 --- a/doc/api/query.md +++ b/doc/api/query.md @@ -70,6 +70,10 @@ | --CommandCosts.Command | string | 指令 | | --CommandCosts.To | string | 指令目标 | | --CommandCosts.Cost | int | 指令消耗 | +| RoomTags | []object? | 临时的房间标签 | +| --RoomTags.Room | string | 房间名 | +| --RoomTags.Key | string | 标签主键 | +| --RoomTags.Value | int | 标签值 | * Tags 是当前环境的标签列表,用于匹配 出口/捷径 里的环境条件 * RoomConditions 是环境的房间条件列表,用于过滤房间的Tags @@ -80,9 +84,11 @@ * Blacklisk是房间黑名单,不进入黑名单中的房间 * BlockedLinks是临时封锁的连接,一般用于被拦路的情况 * CommandCosts是临时指令消耗,用于通过指定的指令到达指定房间的消耗。Command为空在当前版本属于Undefined Behavior。 +* RoomTags为制定房间加上制定的房间标签。如果Room为空字符串,则为所有房间加上对应的房间标签 **版本更新** +* 1006版本后,加入RoomTags * 1002版本后,CommandCosts支持To为空字符串作为通配符,匹配所有出口。 ### 地图选项 MapperOptions diff --git a/update.md b/update.md new file mode 100644 index 0000000..1535b61 --- /dev/null +++ b/update.md @@ -0,0 +1 @@ +* 修正数字空间为空时的报错 \ No newline at end of file