Skip to content

Commit

Permalink
Add some class for parsing command arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
akemimadoka committed Aug 28, 2017
1 parent ff01e1d commit 414db37
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 57 deletions.
7 changes: 6 additions & 1 deletion src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ public Task SetLook(float yaw, float pitch, bool onGround)
return Task.CompletedTask;
}

public bool HasPermission(Permission permission)
public Task<bool> HasPermission(Permission permission)
{
throw new NotImplementedException();
}

public Task SendMessage(string msg)
{
throw new NotImplementedException();
}
Expand Down
21 changes: 18 additions & 3 deletions src/MineCase.Server.Grains/Game/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ internal class GameSession : Grain, IGameSession
private IDisposable _gameTick;
private DateTime _lastGameTickTime;

private readonly Commands.CommandMap _commandMap = new Commands.CommandMap();

public override async Task OnActivateAsync()
{
_world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
Expand Down Expand Up @@ -61,10 +63,23 @@ public async Task SendChatMessage(IUser sender, string message)
{
var senderName = await sender.GetName();

// TODO command parser
if (!string.IsNullOrWhiteSpace(message))
{
var command = message.Trim();
if (command[0] == '/')
{
if (!_commandMap.Dispatch(await sender.GetPlayer(), message))
{
// TODO: 处理命令未成功执行的情形
}

return;
}
}

// construct name
Chat jsonData = await CreateStandardChatMessage(senderName, message);
byte position = 0; // It represents user message in chat box
var jsonData = await CreateStandardChatMessage(senderName, message);
const byte position = 0; // It represents user message in chat box
foreach (var item in _users.Keys)
{
await item.SendChatMessage(jsonData, position);
Expand Down
33 changes: 33 additions & 0 deletions src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace MineCase.Server.Game.Commands
{
public class CommandMap
{
private readonly Dictionary<string, ICommand> _commandMap = new Dictionary<string, ICommand>();

public void RegisterCommand(ICommand command)
{
_commandMap.Add(command.Name, command);
}

public bool Dispatch(ICommandSender sender, string commandContent)
{
var (commandName, args) = CommandParser.ParseCommand(commandContent);

try
{
return _commandMap.TryGetValue(commandName, out var command) &&
(command.NeededPermission == null || sender.HasPermission(command.NeededPermission).Result) &&
command.Execute(sender, args);
}
catch (CommandException e)
{
sender.SendMessage($"在执行指令 {commandName} 之时发生指令相关的异常 {e}");
return false;
}
}
}
}
87 changes: 87 additions & 0 deletions src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;

namespace MineCase.Server.Game.Commands
{
/// <summary>
/// 未解析参数
/// </summary>
/// <remarks>用于参数无法解析或当前不需要已解析的形式的情形</remarks>
public class UnresolvedArgument : ICommandArgument
{
public string RawContent { get; }

public UnresolvedArgument(string rawContent)
{
if (rawContent == null)
{
throw new ArgumentNullException(nameof(rawContent));
}

if (rawContent.Length <= 0)
{
throw new ArgumentException($"{nameof(rawContent)} 不得为空", nameof(rawContent));
}

RawContent = rawContent;
}
}

/// <summary>
/// 命令分析器
/// </summary>
public static class CommandParser
{
/// <summary>
/// 分析命令
/// </summary>
/// <param name="input">输入,即作为命令被分析的文本</param>
/// <returns>命令名及命令的参数</returns>
public static (string, IList<ICommandArgument>) ParseCommand(string input)
{
if (input == null || input.Length < 2)
{
throw new ArgumentException("输入不合法", nameof(input));
}

var splitResult = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (splitResult.Length == 0)
{
throw new ArgumentException($"输入 ({input}) 不合法");
}

return (splitResult[0], ParseCommandArgument(splitResult.Skip(1)));
}

// 参数必须保持有序,因此返回值使用 IList 而不是 IEnumerable
private static IList<ICommandArgument> ParseCommandArgument(IEnumerable<string> input)
{
var result = new List<ICommandArgument>();

foreach (var arg in input)
{
Contract.Assert(arg != null && arg.Length > 1);

// TODO: 使用更加具有可扩展性的方法
switch (arg[0])
{
case '@':
result.Add(new TargetSelectorArgument(arg));
break;
case '{':
result.Add(new DataTagArgument(arg));
break;
default:
result.Add(new UnresolvedArgument(arg));
break;
}
}

return result;
}
}
}
18 changes: 18 additions & 0 deletions src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using MineCase.Nbt.Tags;

namespace MineCase.Server.Game.Commands
{
public class DataTagArgument : UnresolvedArgument
{
public NbtCompound Tag { get; }

public DataTagArgument(string rawContent)
: base(rawContent)
{
throw new NotImplementedException();
}
}
}
90 changes: 90 additions & 0 deletions src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MineCase.Server.Game.Commands
{
/// <summary>
/// 命令参数接口
/// </summary>
public interface ICommandArgument
{
/// <summary>
/// Gets 命令参数的原本内容
/// </summary>
string RawContent { get; }
}

/// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
/// <summary>
/// Gets 该命令的名称
/// </summary>
string Name { get; }

/// <summary>
/// Gets 该命令的描述,可为 null
/// </summary>
string Description { get; }

/// <summary>
/// Gets 要执行此命令需要的权限,可为 null
/// </summary>
Permission NeededPermission { get; }

/// <summary>
/// Gets 该命令的别名,可为 null
/// </summary>
IEnumerable<string> Aliases { get; }

/// <summary>
/// 执行该命令
/// </summary>
/// <param name="commandSender">发送命令者</param>
/// <param name="args">命令的参数</param>
/// <returns>执行是否成功,如果成功则返回 true</returns>
/// <exception cref="CommandException">可能抛出派生自 <see cref="CommandException"/> 的异常</exception>
bool Execute(ICommandSender commandSender, IList<ICommandArgument> args);
}

/// <summary>
/// 命令执行过程中可能发生的异常的基类
/// </summary>
/// <remarks>派生自此类的异常在 <see cref="CommandMap.Dispatch(ICommandSender, string)"/> 中将会被吃掉,不会传播到外部</remarks>
public class CommandException : Exception
{
public ICommand Command { get; }

public CommandException(ICommand command = null, string content = null, Exception innerException = null)
: base(content, innerException)
{
Command = command;
}
}

/// <summary>
/// 表示命令的使用方式错误的异常
/// </summary>
public class CommandWrongUsageException : CommandException
{
public CommandWrongUsageException(ICommand command, string content = null, Exception innerException = null)
: base(command, content, innerException)
{
}
}

/// <summary>
/// 可发送命令者接口
/// </summary>
public interface ICommandSender : IPermissible
{
/// <summary>
/// 向可发送命令者发送(一般为反馈)特定的信息
/// </summary>
/// <param name="msg">要发送的信息</param>
Task SendMessage(string msg);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

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

public abstract bool Execute(ICommandSender commandSender, IEnumerable<string> args);
public abstract bool Execute(ICommandSender commandSender, IList<ICommandArgument> args);
}
}
Loading

0 comments on commit 414db37

Please sign in to comment.