diff --git a/src/MineCase.Nbt/Tags/NbtCompound.cs b/src/MineCase.Nbt/Tags/NbtCompound.cs
index 58bbee9f..84c8ceb9 100644
--- a/src/MineCase.Nbt/Tags/NbtCompound.cs
+++ b/src/MineCase.Nbt/Tags/NbtCompound.cs
@@ -111,7 +111,7 @@ public void Add(NbtTag tag)
if (_childTags.ContainsKey(tag.Name))
{
- throw new ArgumentException($"试图加入具有名称{tag.Name}的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
+ throw new ArgumentException($"试图加入具有名称 \"{tag.Name}\" 的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
}
Contract.EndContractBlock();
diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs
index 559aedb2..3015b44d 100644
--- a/src/MineCase.Server.Grains/Game/GameSession.cs
+++ b/src/MineCase.Server.Grains/Game/GameSession.cs
@@ -70,7 +70,10 @@ public async Task SendChatMessage(IUser sender, string message)
{
if (!_commandMap.Dispatch(await sender.GetPlayer(), message))
{
- // TODO: 处理命令未成功执行的情形
+ await sender.SendChatMessage(
+ await CreateStandardChatMessage(
+ senderName,
+ $"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确"), 0);
}
return;
diff --git a/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs
index 0d913340..0aa7dd1b 100644
--- a/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs
+++ b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs
@@ -39,11 +39,12 @@ public static class CommandParser
///
/// 分析命令
///
- /// 输入,即作为命令被分析的文本
+ /// 输入,即作为命令被分析的文本,应当不为 null、经过 处理且以 '/' 开头
/// 命令名及命令的参数
+ /// 不合法
public static (string, IList) ParseCommand(string input)
{
- if (input == null || input.Length < 2)
+ if (input == null || input.Length < 2 || input[0] != '/')
{
throw new ArgumentException("输入不合法", nameof(input));
}
@@ -51,30 +52,33 @@ public static (string, IList) ParseCommand(string input)
var splitResult = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (splitResult.Length == 0)
{
- throw new ArgumentException($"输入 ({input}) 不合法");
+ throw new ArgumentException($"输入 ({input}) 不合法", nameof(input));
}
- return (splitResult[0], ParseCommandArgument(splitResult.Skip(1)));
+ return (splitResult[0].Substring(1), ParseCommandArgument(splitResult.Skip(1)));
}
- // 参数必须保持有序,因此返回值使用 IList 而不是 IEnumerable
+ // 参数必须保持原来的顺序,因此返回值使用 IList 而不是 IEnumerable
private static IList ParseCommandArgument(IEnumerable input)
{
var result = new List();
foreach (var arg in input)
{
- Contract.Assert(arg != null && arg.Length > 1);
+ Contract.Assert(!string.IsNullOrWhiteSpace(arg));
// TODO: 使用更加具有可扩展性的方法
switch (arg[0])
{
- case '@':
+ case TargetSelectorArgument.PrefixToken:
result.Add(new TargetSelectorArgument(arg));
break;
- case '{':
+ case DataTagArgument.PrefixToken:
result.Add(new DataTagArgument(arg));
break;
+ case TildeNotationArgument.PrefixToken:
+ result.Add(new TildeNotationArgument(arg));
+ break;
default:
result.Add(new UnresolvedArgument(arg));
break;
diff --git a/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs
index f35f6e6a..ed06c1f0 100644
--- a/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs
+++ b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs
@@ -5,8 +5,14 @@
namespace MineCase.Server.Game.Commands
{
+ ///
+ /// 数据标签参数
+ ///
+ /// 表示一个
public class DataTagArgument : UnresolvedArgument
{
+ internal const char PrefixToken = '{';
+
public NbtCompound Tag { get; }
public DataTagArgument(string rawContent)
diff --git a/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs
index 371ce394..d878e642 100644
--- a/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs
+++ b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs
@@ -17,7 +17,7 @@ internal class TargetSelectorAliasAsAttribute : Attribute
}
///
- /// TargetSelector 类型
+ /// 目标选择器类型
///
public enum TargetSelectorType
{
@@ -70,17 +70,17 @@ private enum ParseStatus
Rejected
}
- private const char PrefixToken = '@';
+ internal const char PrefixToken = '@';
private const char ArgumentListStartToken = '[';
private const char ArgumentListEndToken = ']';
private const char ArgumentAssignmentToken = '=';
private const char ArgumentSeparatorToken = ',';
private static readonly Dictionary TargetSelectorMap =
- typeof(TargetSelectorType).GetFields()
+ typeof(TargetSelectorType).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static)
.ToDictionary(
- v => v.GetType().GetTypeInfo().GetCustomAttribute().Alias,
- v => (TargetSelectorType)v.GetValue(null));
+ v => v.GetCustomAttribute().Alias,
+ v => (TargetSelectorType)v.GetRawConstantValue());
///
/// Gets 指示选择了哪一类型的目标
@@ -91,11 +91,11 @@ private enum ParseStatus
///
/// Initializes a new instance of the class.
- /// 构造并分析一个 TargetSelector
+ /// 构造并分析一个目标选择器
///
- /// 作为 TargetSelector 的内容
+ /// 作为目标选择器的内容
/// 为 null
- /// 无法作为 TargetSelector 解析
+ /// 无法作为目标选择器解析
public TargetSelectorArgument(string rawContent)
: base(rawContent)
{
@@ -108,13 +108,20 @@ public TargetSelectorArgument(string rawContent)
switch (status)
{
case ParseStatus.Prefix:
- status = cur == PrefixToken ? ParseStatus.VariableTag : ParseStatus.Rejected;
+ if (cur == PrefixToken)
+ {
+ status = ParseStatus.VariableTag;
+ }
+ else
+ {
+ goto case ParseStatus.Rejected;
+ }
+
break;
case ParseStatus.VariableTag:
if (!TargetSelectorMap.TryGetValue(cur, out var type))
{
- status = ParseStatus.Rejected;
- break;
+ goto case ParseStatus.Rejected;
}
Type = type;
@@ -123,11 +130,18 @@ public TargetSelectorArgument(string rawContent)
case ParseStatus.OptionalArgumentListStart:
if (cur != ArgumentListStartToken)
{
- status = ParseStatus.Rejected;
+ goto case ParseStatus.Rejected;
}
+ status = ParseStatus.ArgumentElementName;
break;
case ParseStatus.ArgumentElementName:
+ if (char.IsWhiteSpace(cur) && tmpString.Length == 0)
+ {
+ // 略过开头的空白字符,但是这不可能发生。。。
+ continue;
+ }
+
if (cur == ArgumentAssignmentToken)
{
argName = tmpString.ToString();
@@ -144,7 +158,15 @@ public TargetSelectorArgument(string rawContent)
Contract.Assert(argName != null);
_arguments.Add(argName, tmpString.ToString());
tmpString = new StringBuilder();
- status = cur == ArgumentSeparatorToken ? ParseStatus.ArgumentElementName : ParseStatus.ArgumentListEnd;
+ if (cur == ArgumentSeparatorToken)
+ {
+ status = ParseStatus.ArgumentElementName;
+ }
+ else
+ {
+ goto case ParseStatus.ArgumentListEnd;
+ }
+
break;
}
@@ -154,7 +176,9 @@ public TargetSelectorArgument(string rawContent)
status = ParseStatus.Accepted;
break;
case ParseStatus.Accepted:
- return;
+ // 尾部有多余的字符
+ status = ParseStatus.Rejected;
+ break;
case ParseStatus.Rejected:
throw new ArgumentException($"\"{rawContent}\" 不能被解析为合法的 TargetSelector", nameof(rawContent));
default:
@@ -163,7 +187,7 @@ public TargetSelectorArgument(string rawContent)
}
}
- if (status != ParseStatus.Accepted || status != ParseStatus.OptionalArgumentListStart)
+ if (status != ParseStatus.Accepted && status != ParseStatus.OptionalArgumentListStart)
{
throw new ArgumentException($"在解析 \"{rawContent}\" 的过程中,解析被过早地中止");
}
diff --git a/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs b/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs
new file mode 100644
index 00000000..bea6f298
--- /dev/null
+++ b/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game.Commands
+{
+ ///
+ /// 波浪号记号参数
+ ///
+ /// 用于表示一个相对的值,具体含义由具体命令定义
+ public class TildeNotationArgument : UnresolvedArgument
+ {
+ internal const char PrefixToken = '~';
+
+ ///
+ /// Gets 波浪号记号参数表示的偏移量
+ ///
+ public int Offset { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// 构造并分析一个波浪号记号参数
+ ///
+ /// 作为波浪号记号的内容
+ public TildeNotationArgument(string rawContent)
+ : base(rawContent)
+ {
+ if (rawContent.Length == 1)
+ {
+ Offset = 0;
+ return;
+ }
+
+ var strToParse = rawContent.Substring(1);
+ if (!int.TryParse(strToParse, out int offset))
+ {
+ throw new ArgumentException($"\"{strToParse}\" 不是合法的 offset 值", nameof(rawContent));
+ }
+
+ Offset = offset;
+ }
+ }
+}
diff --git a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj
index 23d4b9ea..fb7b5d5c 100644
--- a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj
+++ b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj
@@ -12,7 +12,6 @@
-
diff --git a/tests/UnitTest/CommandTest.cs b/tests/UnitTest/CommandTest.cs
new file mode 100644
index 00000000..311b8167
--- /dev/null
+++ b/tests/UnitTest/CommandTest.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Server.Game;
+using MineCase.Server.Game.Commands;
+using Xunit;
+
+namespace MineCase.UnitTest
+{
+ public class CommandTest
+ {
+ private class OutputCommandSender : ICommandSender
+ {
+ private readonly TextWriter _tw;
+
+ public OutputCommandSender(TextWriter tw)
+ {
+ _tw = tw;
+ }
+
+ public Task HasPermission(Permission permission)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendMessage(string msg)
+ {
+ _tw.WriteLine(msg);
+ return Task.CompletedTask;
+ }
+ }
+
+ private class TestCommand : SimpleCommand
+ {
+ public TestCommand()
+ : base("test", null, null, null)
+ {
+ }
+
+ public override bool Execute(ICommandSender commandSender, IList args)
+ {
+ commandSender.SendMessage(string.Join(", ", args.Select(arg => arg.ToString())));
+ return true;
+ }
+ }
+
+ [Fact]
+ public void Test1()
+ {
+ var commandMap = new CommandMap();
+ commandMap.RegisterCommand(new TestCommand());
+
+ var sb = new StringBuilder();
+ using (var sw = new StringWriter(sb))
+ {
+ commandMap.Dispatch(new OutputCommandSender(sw), "/test 1 ~2 @p[arg1=233,arg2=]");
+ var str = sb.ToString();
+ Console.Write(str);
+ }
+ }
+ }
+}
diff --git a/tests/UnitTest/MineCase.UnitTest.csproj b/tests/UnitTest/MineCase.UnitTest.csproj
index 04d14594..0b26526a 100644
--- a/tests/UnitTest/MineCase.UnitTest.csproj
+++ b/tests/UnitTest/MineCase.UnitTest.csproj
@@ -23,6 +23,7 @@
+
diff --git a/tests/UnitTest/NbtTest.cs b/tests/UnitTest/NbtTest.cs
index ccb23da6..d84ea337 100644
--- a/tests/UnitTest/NbtTest.cs
+++ b/tests/UnitTest/NbtTest.cs
@@ -121,4 +121,4 @@ public void Test1()
}
}
}
-}
\ No newline at end of file
+}