Skip to content

Commit 3fd4c74

Browse files
committed
Fix bugs, add UnitTest
1 parent b03af6e commit 3fd4c74

File tree

10 files changed

+172
-27
lines changed

10 files changed

+172
-27
lines changed

src/MineCase.Nbt/Tags/NbtCompound.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void Add(NbtTag tag)
111111

112112
if (_childTags.ContainsKey(tag.Name))
113113
{
114-
throw new ArgumentException($"试图加入具有名称{tag.Name}的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
114+
throw new ArgumentException($"试图加入具有名称 \"{tag.Name}\" 的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
115115
}
116116

117117
Contract.EndContractBlock();

src/MineCase.Server.Grains/Game/GameSession.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ public async Task SendChatMessage(IUser sender, string message)
7070
{
7171
if (!_commandMap.Dispatch(await sender.GetPlayer(), message))
7272
{
73-
// TODO: 处理命令未成功执行的情形
73+
await sender.SendChatMessage(
74+
await CreateStandardChatMessage(
75+
senderName,
76+
$"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确"), 0);
7477
}
7578

7679
return;

src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,42 +39,46 @@ public static class CommandParser
3939
/// <summary>
4040
/// 分析命令
4141
/// </summary>
42-
/// <param name="input">输入,即作为命令被分析的文本</param>
42+
/// <param name="input">输入,即作为命令被分析的文本,应当不为 null、经过 <see cref="string.Trim()"/> 处理且以 '/' 开头</param>
4343
/// <returns>命令名及命令的参数</returns>
44+
/// <exception cref="ArgumentException"><paramref name="input"/> 不合法</exception>
4445
public static (string, IList<ICommandArgument>) ParseCommand(string input)
4546
{
46-
if (input == null || input.Length < 2)
47+
if (input == null || input.Length < 2 || input[0] != '/')
4748
{
4849
throw new ArgumentException("输入不合法", nameof(input));
4950
}
5051

5152
var splitResult = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
5253
if (splitResult.Length == 0)
5354
{
54-
throw new ArgumentException($"输入 ({input}) 不合法");
55+
throw new ArgumentException($"输入 ({input}) 不合法", nameof(input));
5556
}
5657

57-
return (splitResult[0], ParseCommandArgument(splitResult.Skip(1)));
58+
return (splitResult[0].Substring(1), ParseCommandArgument(splitResult.Skip(1)));
5859
}
5960

60-
// 参数必须保持有序,因此返回值使用 IList 而不是 IEnumerable
61+
// 参数必须保持原来的顺序,因此返回值使用 IList 而不是 IEnumerable
6162
private static IList<ICommandArgument> ParseCommandArgument(IEnumerable<string> input)
6263
{
6364
var result = new List<ICommandArgument>();
6465

6566
foreach (var arg in input)
6667
{
67-
Contract.Assert(arg != null && arg.Length > 1);
68+
Contract.Assert(!string.IsNullOrWhiteSpace(arg));
6869

6970
// TODO: 使用更加具有可扩展性的方法
7071
switch (arg[0])
7172
{
72-
case '@':
73+
case TargetSelectorArgument.PrefixToken:
7374
result.Add(new TargetSelectorArgument(arg));
7475
break;
75-
case '{':
76+
case DataTagArgument.PrefixToken:
7677
result.Add(new DataTagArgument(arg));
7778
break;
79+
case TildeNotationArgument.PrefixToken:
80+
result.Add(new TildeNotationArgument(arg));
81+
break;
7882
default:
7983
result.Add(new UnresolvedArgument(arg));
8084
break;

src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55

66
namespace MineCase.Server.Game.Commands
77
{
8+
/// <summary>
9+
/// 数据标签参数
10+
/// </summary>
11+
/// <remarks>表示一个 <see cref="NbtTag"/></remarks>
812
public class DataTagArgument : UnresolvedArgument
913
{
14+
internal const char PrefixToken = '{';
15+
1016
public NbtCompound Tag { get; }
1117

1218
public DataTagArgument(string rawContent)

src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal class TargetSelectorAliasAsAttribute : Attribute
1717
}
1818

1919
/// <summary>
20-
/// TargetSelector 类型
20+
/// 目标选择器类型
2121
/// </summary>
2222
public enum TargetSelectorType
2323
{
@@ -70,17 +70,17 @@ private enum ParseStatus
7070
Rejected
7171
}
7272

73-
private const char PrefixToken = '@';
73+
internal const char PrefixToken = '@';
7474
private const char ArgumentListStartToken = '[';
7575
private const char ArgumentListEndToken = ']';
7676
private const char ArgumentAssignmentToken = '=';
7777
private const char ArgumentSeparatorToken = ',';
7878

7979
private static readonly Dictionary<char, TargetSelectorType> TargetSelectorMap =
80-
typeof(TargetSelectorType).GetFields()
80+
typeof(TargetSelectorType).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static)
8181
.ToDictionary(
82-
v => v.GetType().GetTypeInfo().GetCustomAttribute<TargetSelectorAliasAsAttribute>().Alias,
83-
v => (TargetSelectorType)v.GetValue(null));
82+
v => v.GetCustomAttribute<TargetSelectorAliasAsAttribute>().Alias,
83+
v => (TargetSelectorType)v.GetRawConstantValue());
8484

8585
/// <summary>
8686
/// Gets 指示选择了哪一类型的目标
@@ -91,11 +91,11 @@ private enum ParseStatus
9191

9292
/// <summary>
9393
/// Initializes a new instance of the <see cref="TargetSelectorArgument"/> class.<para />
94-
/// 构造并分析一个 TargetSelector
94+
/// 构造并分析一个目标选择器
9595
/// </summary>
96-
/// <param name="rawContent">作为 TargetSelector 的内容</param>
96+
/// <param name="rawContent">作为目标选择器的内容</param>
9797
/// <exception cref="ArgumentNullException"><paramref name="rawContent"/> 为 null</exception>
98-
/// <exception cref="ArgumentException"><paramref name="rawContent"/> 无法作为 TargetSelector 解析</exception>
98+
/// <exception cref="ArgumentException"><paramref name="rawContent"/> 无法作为目标选择器解析</exception>
9999
public TargetSelectorArgument(string rawContent)
100100
: base(rawContent)
101101
{
@@ -108,13 +108,20 @@ public TargetSelectorArgument(string rawContent)
108108
switch (status)
109109
{
110110
case ParseStatus.Prefix:
111-
status = cur == PrefixToken ? ParseStatus.VariableTag : ParseStatus.Rejected;
111+
if (cur == PrefixToken)
112+
{
113+
status = ParseStatus.VariableTag;
114+
}
115+
else
116+
{
117+
goto case ParseStatus.Rejected;
118+
}
119+
112120
break;
113121
case ParseStatus.VariableTag:
114122
if (!TargetSelectorMap.TryGetValue(cur, out var type))
115123
{
116-
status = ParseStatus.Rejected;
117-
break;
124+
goto case ParseStatus.Rejected;
118125
}
119126

120127
Type = type;
@@ -123,11 +130,18 @@ public TargetSelectorArgument(string rawContent)
123130
case ParseStatus.OptionalArgumentListStart:
124131
if (cur != ArgumentListStartToken)
125132
{
126-
status = ParseStatus.Rejected;
133+
goto case ParseStatus.Rejected;
127134
}
128135

136+
status = ParseStatus.ArgumentElementName;
129137
break;
130138
case ParseStatus.ArgumentElementName:
139+
if (char.IsWhiteSpace(cur) && tmpString.Length == 0)
140+
{
141+
// 略过开头的空白字符,但是这不可能发生。。。
142+
continue;
143+
}
144+
131145
if (cur == ArgumentAssignmentToken)
132146
{
133147
argName = tmpString.ToString();
@@ -144,7 +158,15 @@ public TargetSelectorArgument(string rawContent)
144158
Contract.Assert(argName != null);
145159
_arguments.Add(argName, tmpString.ToString());
146160
tmpString = new StringBuilder();
147-
status = cur == ArgumentSeparatorToken ? ParseStatus.ArgumentElementName : ParseStatus.ArgumentListEnd;
161+
if (cur == ArgumentSeparatorToken)
162+
{
163+
status = ParseStatus.ArgumentElementName;
164+
}
165+
else
166+
{
167+
goto case ParseStatus.ArgumentListEnd;
168+
}
169+
148170
break;
149171
}
150172

@@ -154,7 +176,9 @@ public TargetSelectorArgument(string rawContent)
154176
status = ParseStatus.Accepted;
155177
break;
156178
case ParseStatus.Accepted:
157-
return;
179+
// 尾部有多余的字符
180+
status = ParseStatus.Rejected;
181+
break;
158182
case ParseStatus.Rejected:
159183
throw new ArgumentException($"\"{rawContent}\" 不能被解析为合法的 TargetSelector", nameof(rawContent));
160184
default:
@@ -163,7 +187,7 @@ public TargetSelectorArgument(string rawContent)
163187
}
164188
}
165189

166-
if (status != ParseStatus.Accepted || status != ParseStatus.OptionalArgumentListStart)
190+
if (status != ParseStatus.Accepted && status != ParseStatus.OptionalArgumentListStart)
167191
{
168192
throw new ArgumentException($"在解析 \"{rawContent}\" 的过程中,解析被过早地中止");
169193
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace MineCase.Server.Game.Commands
6+
{
7+
/// <summary>
8+
/// 波浪号记号参数
9+
/// </summary>
10+
/// <remarks>用于表示一个相对的值,具体含义由具体命令定义</remarks>
11+
public class TildeNotationArgument : UnresolvedArgument
12+
{
13+
internal const char PrefixToken = '~';
14+
15+
/// <summary>
16+
/// Gets 波浪号记号参数表示的偏移量
17+
/// </summary>
18+
public int Offset { get; }
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="TildeNotationArgument"/> class.
22+
/// 构造并分析一个波浪号记号参数
23+
/// </summary>
24+
/// <param name="rawContent">作为波浪号记号的内容</param>
25+
public TildeNotationArgument(string rawContent)
26+
: base(rawContent)
27+
{
28+
if (rawContent.Length == 1)
29+
{
30+
Offset = 0;
31+
return;
32+
}
33+
34+
var strToParse = rawContent.Substring(1);
35+
if (!int.TryParse(strToParse, out int offset))
36+
{
37+
throw new ArgumentException($"\"{strToParse}\" 不是合法的 offset 值", nameof(rawContent));
38+
}
39+
40+
Offset = offset;
41+
}
42+
}
43+
}

src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
<PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-preview2-20170724" />
1313
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" PrivateAssets="All" />
1414
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
15-
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
1615
</ItemGroup>
1716

1817
<ItemGroup>

tests/UnitTest/CommandTest.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using MineCase.Server.Game;
8+
using MineCase.Server.Game.Commands;
9+
using Xunit;
10+
11+
namespace MineCase.UnitTest
12+
{
13+
public class CommandTest
14+
{
15+
private class OutputCommandSender : ICommandSender
16+
{
17+
private readonly TextWriter _tw;
18+
19+
public OutputCommandSender(TextWriter tw)
20+
{
21+
_tw = tw;
22+
}
23+
24+
public Task<bool> HasPermission(Permission permission)
25+
{
26+
return Task.FromResult(true);
27+
}
28+
29+
public Task SendMessage(string msg)
30+
{
31+
_tw.WriteLine(msg);
32+
return Task.CompletedTask;
33+
}
34+
}
35+
36+
private class TestCommand : SimpleCommand
37+
{
38+
public TestCommand()
39+
: base("test", null, null, null)
40+
{
41+
}
42+
43+
public override bool Execute(ICommandSender commandSender, IList<ICommandArgument> args)
44+
{
45+
commandSender.SendMessage(string.Join(", ", args.Select(arg => arg.ToString())));
46+
return true;
47+
}
48+
}
49+
50+
[Fact]
51+
public void Test1()
52+
{
53+
var commandMap = new CommandMap();
54+
commandMap.RegisterCommand(new TestCommand());
55+
56+
var sb = new StringBuilder();
57+
using (var sw = new StringWriter(sb))
58+
{
59+
commandMap.Dispatch(new OutputCommandSender(sw), "/test 1 ~2 @p[arg1=233,arg2=]");
60+
var str = sb.ToString();
61+
Console.Write(str);
62+
}
63+
}
64+
}
65+
}

tests/UnitTest/MineCase.UnitTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<ProjectReference Include="..\..\src\MineCase.Algorithm\MineCase.Algorithm.csproj" />
2424
<ProjectReference Include="..\..\src\MineCase.Nbt\MineCase.Nbt.csproj" />
2525
<ProjectReference Include="..\..\src\MineCase.Protocol\MineCase.Protocol.csproj" />
26+
<ProjectReference Include="..\..\src\MineCase.Server.Interfaces\MineCase.Server.Interfaces.csproj" />
2627
</ItemGroup>
2728

2829
</Project>

tests/UnitTest/NbtTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,4 @@ public void Test1()
121121
}
122122
}
123123
}
124-
}
124+
}

0 commit comments

Comments
 (0)