Skip to content

Commit 414db37

Browse files
committed
Add some class for parsing command arguments
1 parent ff01e1d commit 414db37

File tree

13 files changed

+498
-57
lines changed

13 files changed

+498
-57
lines changed

src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,12 @@ public Task SetLook(float yaw, float pitch, bool onGround)
133133
return Task.CompletedTask;
134134
}
135135

136-
public bool HasPermission(Permission permission)
136+
public Task<bool> HasPermission(Permission permission)
137+
{
138+
throw new NotImplementedException();
139+
}
140+
141+
public Task SendMessage(string msg)
137142
{
138143
throw new NotImplementedException();
139144
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ internal class GameSession : Grain, IGameSession
2121
private IDisposable _gameTick;
2222
private DateTime _lastGameTickTime;
2323

24+
private readonly Commands.CommandMap _commandMap = new Commands.CommandMap();
25+
2426
public override async Task OnActivateAsync()
2527
{
2628
_world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
@@ -61,10 +63,23 @@ public async Task SendChatMessage(IUser sender, string message)
6163
{
6264
var senderName = await sender.GetName();
6365

64-
// TODO command parser
66+
if (!string.IsNullOrWhiteSpace(message))
67+
{
68+
var command = message.Trim();
69+
if (command[0] == '/')
70+
{
71+
if (!_commandMap.Dispatch(await sender.GetPlayer(), message))
72+
{
73+
// TODO: 处理命令未成功执行的情形
74+
}
75+
76+
return;
77+
}
78+
}
79+
6580
// construct name
66-
Chat jsonData = await CreateStandardChatMessage(senderName, message);
67-
byte position = 0; // It represents user message in chat box
81+
var jsonData = await CreateStandardChatMessage(senderName, message);
82+
const byte position = 0; // It represents user message in chat box
6883
foreach (var item in _users.Keys)
6984
{
7085
await item.SendChatMessage(jsonData, position);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace MineCase.Server.Game.Commands
6+
{
7+
public class CommandMap
8+
{
9+
private readonly Dictionary<string, ICommand> _commandMap = new Dictionary<string, ICommand>();
10+
11+
public void RegisterCommand(ICommand command)
12+
{
13+
_commandMap.Add(command.Name, command);
14+
}
15+
16+
public bool Dispatch(ICommandSender sender, string commandContent)
17+
{
18+
var (commandName, args) = CommandParser.ParseCommand(commandContent);
19+
20+
try
21+
{
22+
return _commandMap.TryGetValue(commandName, out var command) &&
23+
(command.NeededPermission == null || sender.HasPermission(command.NeededPermission).Result) &&
24+
command.Execute(sender, args);
25+
}
26+
catch (CommandException e)
27+
{
28+
sender.SendMessage($"在执行指令 {commandName} 之时发生指令相关的异常 {e}");
29+
return false;
30+
}
31+
}
32+
}
33+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Diagnostics.Contracts;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace MineCase.Server.Game.Commands
9+
{
10+
/// <summary>
11+
/// 未解析参数
12+
/// </summary>
13+
/// <remarks>用于参数无法解析或当前不需要已解析的形式的情形</remarks>
14+
public class UnresolvedArgument : ICommandArgument
15+
{
16+
public string RawContent { get; }
17+
18+
public UnresolvedArgument(string rawContent)
19+
{
20+
if (rawContent == null)
21+
{
22+
throw new ArgumentNullException(nameof(rawContent));
23+
}
24+
25+
if (rawContent.Length <= 0)
26+
{
27+
throw new ArgumentException($"{nameof(rawContent)} 不得为空", nameof(rawContent));
28+
}
29+
30+
RawContent = rawContent;
31+
}
32+
}
33+
34+
/// <summary>
35+
/// 命令分析器
36+
/// </summary>
37+
public static class CommandParser
38+
{
39+
/// <summary>
40+
/// 分析命令
41+
/// </summary>
42+
/// <param name="input">输入,即作为命令被分析的文本</param>
43+
/// <returns>命令名及命令的参数</returns>
44+
public static (string, IList<ICommandArgument>) ParseCommand(string input)
45+
{
46+
if (input == null || input.Length < 2)
47+
{
48+
throw new ArgumentException("输入不合法", nameof(input));
49+
}
50+
51+
var splitResult = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
52+
if (splitResult.Length == 0)
53+
{
54+
throw new ArgumentException($"输入 ({input}) 不合法");
55+
}
56+
57+
return (splitResult[0], ParseCommandArgument(splitResult.Skip(1)));
58+
}
59+
60+
// 参数必须保持有序,因此返回值使用 IList 而不是 IEnumerable
61+
private static IList<ICommandArgument> ParseCommandArgument(IEnumerable<string> input)
62+
{
63+
var result = new List<ICommandArgument>();
64+
65+
foreach (var arg in input)
66+
{
67+
Contract.Assert(arg != null && arg.Length > 1);
68+
69+
// TODO: 使用更加具有可扩展性的方法
70+
switch (arg[0])
71+
{
72+
case '@':
73+
result.Add(new TargetSelectorArgument(arg));
74+
break;
75+
case '{':
76+
result.Add(new DataTagArgument(arg));
77+
break;
78+
default:
79+
result.Add(new UnresolvedArgument(arg));
80+
break;
81+
}
82+
}
83+
84+
return result;
85+
}
86+
}
87+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using MineCase.Nbt.Tags;
5+
6+
namespace MineCase.Server.Game.Commands
7+
{
8+
public class DataTagArgument : UnresolvedArgument
9+
{
10+
public NbtCompound Tag { get; }
11+
12+
public DataTagArgument(string rawContent)
13+
: base(rawContent)
14+
{
15+
throw new NotImplementedException();
16+
}
17+
}
18+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace MineCase.Server.Game.Commands
6+
{
7+
/// <summary>
8+
/// 命令参数接口
9+
/// </summary>
10+
public interface ICommandArgument
11+
{
12+
/// <summary>
13+
/// Gets 命令参数的原本内容
14+
/// </summary>
15+
string RawContent { get; }
16+
}
17+
18+
/// <summary>
19+
/// 命令接口
20+
/// </summary>
21+
public interface ICommand
22+
{
23+
/// <summary>
24+
/// Gets 该命令的名称
25+
/// </summary>
26+
string Name { get; }
27+
28+
/// <summary>
29+
/// Gets 该命令的描述,可为 null
30+
/// </summary>
31+
string Description { get; }
32+
33+
/// <summary>
34+
/// Gets 要执行此命令需要的权限,可为 null
35+
/// </summary>
36+
Permission NeededPermission { get; }
37+
38+
/// <summary>
39+
/// Gets 该命令的别名,可为 null
40+
/// </summary>
41+
IEnumerable<string> Aliases { get; }
42+
43+
/// <summary>
44+
/// 执行该命令
45+
/// </summary>
46+
/// <param name="commandSender">发送命令者</param>
47+
/// <param name="args">命令的参数</param>
48+
/// <returns>执行是否成功,如果成功则返回 true</returns>
49+
/// <exception cref="CommandException">可能抛出派生自 <see cref="CommandException"/> 的异常</exception>
50+
bool Execute(ICommandSender commandSender, IList<ICommandArgument> args);
51+
}
52+
53+
/// <summary>
54+
/// 命令执行过程中可能发生的异常的基类
55+
/// </summary>
56+
/// <remarks>派生自此类的异常在 <see cref="CommandMap.Dispatch(ICommandSender, string)"/> 中将会被吃掉,不会传播到外部</remarks>
57+
public class CommandException : Exception
58+
{
59+
public ICommand Command { get; }
60+
61+
public CommandException(ICommand command = null, string content = null, Exception innerException = null)
62+
: base(content, innerException)
63+
{
64+
Command = command;
65+
}
66+
}
67+
68+
/// <summary>
69+
/// 表示命令的使用方式错误的异常
70+
/// </summary>
71+
public class CommandWrongUsageException : CommandException
72+
{
73+
public CommandWrongUsageException(ICommand command, string content = null, Exception innerException = null)
74+
: base(command, content, innerException)
75+
{
76+
}
77+
}
78+
79+
/// <summary>
80+
/// 可发送命令者接口
81+
/// </summary>
82+
public interface ICommandSender : IPermissible
83+
{
84+
/// <summary>
85+
/// 向可发送命令者发送(一般为反馈)特定的信息
86+
/// </summary>
87+
/// <param name="msg">要发送的信息</param>
88+
Task SendMessage(string msg);
89+
}
90+
}

src/MineCase.Server.Interfaces/Game/SimpleCommand.cs renamed to src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Text;
43

5-
namespace MineCase.Server.Game
4+
namespace MineCase.Server.Game.Commands
65
{
6+
/// <summary>
7+
/// 简单指令
8+
/// </summary>
9+
/// <remarks>用于无复杂的名称、描述、权限及别名机制的指令</remarks>
710
public abstract class SimpleCommand : ICommand
811
{
912
public string Name { get; }
@@ -27,6 +30,6 @@ protected SimpleCommand(string name, string description = null, Permission neede
2730
}
2831
}
2932

30-
public abstract bool Execute(ICommandSender commandSender, IEnumerable<string> args);
33+
public abstract bool Execute(ICommandSender commandSender, IList<ICommandArgument> args);
3134
}
3235
}

0 commit comments

Comments
 (0)