From fb38aa81cd476e61d6202351a7123ff0b6f52821 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Sun, 27 Aug 2017 16:18:20 +0800 Subject: [PATCH 01/11] Add command interfaces --- build.bat | 6 +++ .../Game/ICommand.cs | 46 +++++++++++++++++++ .../Game/IPermissible.cs | 18 ++++++++ .../Game/Permission.cs | 44 ++++++++++++++++++ .../Game/PermissionDefaultValue.cs | 27 +++++++++++ .../Game/SimpleCommand.cs | 32 +++++++++++++ tests/UnitTest/NbtTest.cs | 1 + 7 files changed, 174 insertions(+) create mode 100644 build.bat create mode 100644 src/MineCase.Server.Interfaces/Game/ICommand.cs create mode 100644 src/MineCase.Server.Interfaces/Game/IPermissible.cs create mode 100644 src/MineCase.Server.Interfaces/Game/Permission.cs create mode 100644 src/MineCase.Server.Interfaces/Game/PermissionDefaultValue.cs create mode 100644 src/MineCase.Server.Interfaces/Game/SimpleCommand.cs diff --git a/build.bat b/build.bat new file mode 100644 index 00000000..32c93163 --- /dev/null +++ b/build.bat @@ -0,0 +1,6 @@ +@echo off +cd src +dotnet restore +dotnet build + +pause diff --git a/src/MineCase.Server.Interfaces/Game/ICommand.cs b/src/MineCase.Server.Interfaces/Game/ICommand.cs new file mode 100644 index 00000000..e443bf5e --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/ICommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace MineCase.Server.Game +{ + /// + /// 命令接口 + /// + public interface ICommand + { + /// + /// Gets 该命令的名称 + /// + string Name { get; } + + /// + /// Gets 该命令的描述,可为 null + /// + string Description { get; } + + /// + /// Gets 要执行此命令需要的权限,可为 null + /// + Permission NeededPermission { get; } + + /// + /// Gets 该命令的别名,可为 null + /// + IEnumerable Aliases { get; } + + /// + /// 执行该命令 + /// + /// 发送命令者 + /// 命令的参数 + /// 执行是否成功,如果成功则返回 true + bool Execute(ICommandSender commandSender, IEnumerable args); + } + + /// + /// 可发送命令者接口 + /// + public interface ICommandSender : IPermissible + { + } +} diff --git a/src/MineCase.Server.Interfaces/Game/IPermissible.cs b/src/MineCase.Server.Interfaces/Game/IPermissible.cs new file mode 100644 index 00000000..8960deae --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/IPermissible.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MineCase.Server.Game +{ + /// + /// 具有权限者接口 + /// + public interface IPermissible + { + /// + /// 判断是否具有特定的权限 + /// + /// 要判断的权限 + bool HasPermission(Permission permission); + } +} diff --git a/src/MineCase.Server.Interfaces/Game/Permission.cs b/src/MineCase.Server.Interfaces/Game/Permission.cs new file mode 100644 index 00000000..c188e0e2 --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Permission.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MineCase.Server.Game +{ + /// + /// 权限 + /// + public class Permission + { + /// + /// Gets 该权限的名称 + /// + public string Name { get; } + + /// + /// Gets 该权限的描述,可为 null + /// + public string Description { get; } + + /// + /// Gets or sets 该权限的默认值 + /// + public PermissionDefaultValue DefaultValue { get; set; } + + private readonly Dictionary _children; + + public Permission( + string name, + string description = null, + PermissionDefaultValue permissionDefaultValue = PermissionDefaultValue.True, + IEnumerable children = null) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Description = description; + DefaultValue = permissionDefaultValue; + _children = children.ToDictionary( + p => p?.Name ?? throw new ArgumentException("子权限不得为 null", nameof(children))) ?? + new Dictionary(); + } + } +} diff --git a/src/MineCase.Server.Interfaces/Game/PermissionDefaultValue.cs b/src/MineCase.Server.Interfaces/Game/PermissionDefaultValue.cs new file mode 100644 index 00000000..4af9aa62 --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/PermissionDefaultValue.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MineCase.Server.Game +{ + /// + /// 权限 () 的默认值 + /// + public enum PermissionDefaultValue + { + /// + /// 对所有人都启用 + /// + True, + + /// + /// 对 Operator 启用 + /// + TrueIfOp, + + /// + /// 对所有人都禁用 + /// + False + } +} diff --git a/src/MineCase.Server.Interfaces/Game/SimpleCommand.cs b/src/MineCase.Server.Interfaces/Game/SimpleCommand.cs new file mode 100644 index 00000000..9486203b --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/SimpleCommand.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MineCase.Server.Game +{ + public abstract class SimpleCommand : ICommand + { + public string Name { get; } + + public string Description { get; } + + public Permission NeededPermission { get; } + + private readonly HashSet _aliases; + + public IEnumerable Aliases => _aliases; + + protected SimpleCommand(string name, string description = null, Permission neededPermission = null, IEnumerable aliases = null) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Description = description; + NeededPermission = neededPermission; + if (aliases != null) + { + _aliases = new HashSet(aliases); + } + } + + public abstract bool Execute(ICommandSender commandSender, IEnumerable args); + } +} diff --git a/tests/UnitTest/NbtTest.cs b/tests/UnitTest/NbtTest.cs index 907f141e..ccb23da6 100644 --- a/tests/UnitTest/NbtTest.cs +++ b/tests/UnitTest/NbtTest.cs @@ -98,6 +98,7 @@ public void Test1() testList.Add(new NbtInt(2)); testList.Add(new NbtInt(4)); testCompound.Add(new NbtLong(0x000000FFFFFFFFFF, "testLong")); + testCompound.Add(new NbtDouble(3.1415926, "testDouble")); using (var sw = new StringWriter()) { From 7e43de5e3f99b1b01f50da79ce896425de128264 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Sun, 27 Aug 2017 16:40:58 +0800 Subject: [PATCH 02/11] Rename MineCase.NBT to MineCase.Nbt --- src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs | 5 +++++ src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs index 08886cb4..d9dcfb44 100644 --- a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs @@ -336,5 +336,10 @@ private class WindowContext public IWindow Window; public short ActionNumber; } + + public bool HasPermission(Permission permission) + { + throw new NotImplementedException(); + } } } diff --git a/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs b/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs index e3b1b93d..dc900756 100644 --- a/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs +++ b/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs @@ -12,7 +12,7 @@ namespace MineCase.Server.Game.Entities { - public interface IPlayer : IEntity + public interface IPlayer : IEntity, ICommandSender { Task GetName(); From bcb3eb58de239043f647c3cc47dd57ab7c4d3cf1 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Mon, 28 Aug 2017 22:08:22 +0800 Subject: [PATCH 03/11] Add some class for parsing command arguments --- .../Game/Entities/PlayerGrain.cs | 7 +- .../Game/GameSession.cs | 21 +- .../Game/Commands/CommandMap.cs | 33 +++ .../Game/Commands/CommandParser.cs | 87 ++++++++ .../Game/Commands/DataTagArgument.cs | 18 ++ .../Game/Commands/ICommand.cs | 90 ++++++++ .../Game/{ => Commands}/SimpleCommand.cs | 9 +- .../Game/Commands/TargetSelector.cs | 197 ++++++++++++++++++ .../Game/ICommand.cs | 46 ---- .../Game/IPermissible.cs | 3 +- .../Game/Permission.cs | 40 +++- .../MineCase.Server.Interfaces.csproj | 2 + 12 files changed, 496 insertions(+), 57 deletions(-) create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs rename src/MineCase.Server.Interfaces/Game/{ => Commands}/SimpleCommand.cs (79%) create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs delete mode 100644 src/MineCase.Server.Interfaces/Game/ICommand.cs diff --git a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs index d9dcfb44..7195a811 100644 --- a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs @@ -337,7 +337,12 @@ private class WindowContext public short ActionNumber; } - public bool HasPermission(Permission permission) + public Task HasPermission(Permission permission) + { + throw new NotImplementedException(); + } + + public Task SendMessage(string msg) { throw new NotImplementedException(); } diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs index 07dae81e..9fbd70cf 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -23,6 +23,8 @@ internal class GameSession : Grain, IGameSession private HashSet _tickables; + private readonly Commands.CommandMap _commandMap = new Commands.CommandMap(); + public override async Task OnActivateAsync() { _world = await GrainFactory.GetGrain(0).GetWorld(this.GetPrimaryKeyString()); @@ -64,10 +66,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); diff --git a/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs b/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs new file mode 100644 index 00000000..00b4dd2d --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MineCase.Server.Game.Commands +{ + public class CommandMap + { + private readonly Dictionary _commandMap = new Dictionary(); + + 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; + } + } + } +} diff --git a/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs new file mode 100644 index 00000000..0d913340 --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs @@ -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 +{ + /// + /// 未解析参数 + /// + /// 用于参数无法解析或当前不需要已解析的形式的情形 + 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; + } + } + + /// + /// 命令分析器 + /// + public static class CommandParser + { + /// + /// 分析命令 + /// + /// 输入,即作为命令被分析的文本 + /// 命令名及命令的参数 + public static (string, IList) 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 ParseCommandArgument(IEnumerable input) + { + var result = new List(); + + 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; + } + } +} diff --git a/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs new file mode 100644 index 00000000..f35f6e6a --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs @@ -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(); + } + } +} diff --git a/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs new file mode 100644 index 00000000..e807242b --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MineCase.Server.Game.Commands +{ + /// + /// 命令参数接口 + /// + public interface ICommandArgument + { + /// + /// Gets 命令参数的原本内容 + /// + string RawContent { get; } + } + + /// + /// 命令接口 + /// + public interface ICommand + { + /// + /// Gets 该命令的名称 + /// + string Name { get; } + + /// + /// Gets 该命令的描述,可为 null + /// + string Description { get; } + + /// + /// Gets 要执行此命令需要的权限,可为 null + /// + Permission NeededPermission { get; } + + /// + /// Gets 该命令的别名,可为 null + /// + IEnumerable Aliases { get; } + + /// + /// 执行该命令 + /// + /// 发送命令者 + /// 命令的参数 + /// 执行是否成功,如果成功则返回 true + /// 可能抛出派生自 的异常 + bool Execute(ICommandSender commandSender, IList args); + } + + /// + /// 命令执行过程中可能发生的异常的基类 + /// + /// 派生自此类的异常在 中将会被吃掉,不会传播到外部 + public class CommandException : Exception + { + public ICommand Command { get; } + + public CommandException(ICommand command = null, string content = null, Exception innerException = null) + : base(content, innerException) + { + Command = command; + } + } + + /// + /// 表示命令的使用方式错误的异常 + /// + public class CommandWrongUsageException : CommandException + { + public CommandWrongUsageException(ICommand command, string content = null, Exception innerException = null) + : base(command, content, innerException) + { + } + } + + /// + /// 可发送命令者接口 + /// + public interface ICommandSender : IPermissible + { + /// + /// 向可发送命令者发送(一般为反馈)特定的信息 + /// + /// 要发送的信息 + Task SendMessage(string msg); + } +} diff --git a/src/MineCase.Server.Interfaces/Game/SimpleCommand.cs b/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs similarity index 79% rename from src/MineCase.Server.Interfaces/Game/SimpleCommand.cs rename to src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs index 9486203b..f409492d 100644 --- a/src/MineCase.Server.Interfaces/Game/SimpleCommand.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; -using System.Text; -namespace MineCase.Server.Game +namespace MineCase.Server.Game.Commands { + /// + /// 简单指令 + /// + /// 用于无复杂的名称、描述、权限及别名机制的指令 public abstract class SimpleCommand : ICommand { public string Name { get; } @@ -27,6 +30,6 @@ protected SimpleCommand(string name, string description = null, Permission neede } } - public abstract bool Execute(ICommandSender commandSender, IEnumerable args); + public abstract bool Execute(ICommandSender commandSender, IList args); } } diff --git a/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs new file mode 100644 index 00000000..371ce394 --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace MineCase.Server.Game.Commands +{ + [AttributeUsage(AttributeTargets.Field)] + internal class TargetSelectorAliasAsAttribute : Attribute + { + public char Alias { get; } + + public TargetSelectorAliasAsAttribute(char alias) => Alias = alias; + } + + /// + /// TargetSelector 类型 + /// + public enum TargetSelectorType + { + /// + /// 选择最近的玩家为目标 + /// + [TargetSelectorAliasAs('p')] + NearestPlayer, + + /// + /// 选择随机的玩家为目标 + /// + [TargetSelectorAliasAs('r')] + RandomPlayer, + + /// + /// 选择所有玩家为目标 + /// + [TargetSelectorAliasAs('a')] + AllPlayers, + + /// + /// 选择所有实体为目标 + /// + [TargetSelectorAliasAs('e')] + AllEntites, + + /// + /// 选择命令的执行者为目标 + /// + [TargetSelectorAliasAs('s')] + Executor + } + + /// + /// 用于选择目标的 + /// + public class TargetSelectorArgument : UnresolvedArgument, IEnumerable> + { + private enum ParseStatus + { + Prefix, + VariableTag, + OptionalArgumentListStart, + ArgumentElementName, + ArgumentElementValue, + ArgumentListEnd, + + Accepted, + Rejected + } + + private 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() + .ToDictionary( + v => v.GetType().GetTypeInfo().GetCustomAttribute().Alias, + v => (TargetSelectorType)v.GetValue(null)); + + /// + /// Gets 指示选择了哪一类型的目标 + /// + public TargetSelectorType Type { get; } + + private readonly Dictionary _arguments = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// 构造并分析一个 TargetSelector + /// + /// 作为 TargetSelector 的内容 + /// 为 null + /// 无法作为 TargetSelector 解析 + public TargetSelectorArgument(string rawContent) + : base(rawContent) + { + var status = ParseStatus.Prefix; + string argName = null; + var tmpString = new StringBuilder(); + + foreach (var cur in rawContent) + { + switch (status) + { + case ParseStatus.Prefix: + status = cur == PrefixToken ? ParseStatus.VariableTag : ParseStatus.Rejected; + break; + case ParseStatus.VariableTag: + if (!TargetSelectorMap.TryGetValue(cur, out var type)) + { + status = ParseStatus.Rejected; + break; + } + + Type = type; + status = ParseStatus.OptionalArgumentListStart; + break; + case ParseStatus.OptionalArgumentListStart: + if (cur != ArgumentListStartToken) + { + status = ParseStatus.Rejected; + } + + break; + case ParseStatus.ArgumentElementName: + if (cur == ArgumentAssignmentToken) + { + argName = tmpString.ToString(); + tmpString = new StringBuilder(); + status = ParseStatus.ArgumentElementValue; + break; + } + + tmpString.Append(cur); + break; + case ParseStatus.ArgumentElementValue: + if (cur == ArgumentSeparatorToken || cur == ArgumentListEndToken) + { + Contract.Assert(argName != null); + _arguments.Add(argName, tmpString.ToString()); + tmpString = new StringBuilder(); + status = cur == ArgumentSeparatorToken ? ParseStatus.ArgumentElementName : ParseStatus.ArgumentListEnd; + break; + } + + tmpString.Append(cur); + break; + case ParseStatus.ArgumentListEnd: + status = ParseStatus.Accepted; + break; + case ParseStatus.Accepted: + return; + case ParseStatus.Rejected: + throw new ArgumentException($"\"{rawContent}\" 不能被解析为合法的 TargetSelector", nameof(rawContent)); + default: + // 任何情况下都不应当发生 + throw new ArgumentOutOfRangeException(nameof(status)); + } + } + + if (status != ParseStatus.Accepted || status != ParseStatus.OptionalArgumentListStart) + { + throw new ArgumentException($"在解析 \"{rawContent}\" 的过程中,解析被过早地中止"); + } + } + + /// + public string this[string name] => GetArgumentValue(name); + + /// + /// 获得具有指定名称的参数值 + /// + /// 要查找的名称 + /// 无法找到具有指定名称的参数 + public string GetArgumentValue(string name) => _arguments[name]; + + /// + /// 判断是否存在具有指定名称的参数 + /// + /// 要判断的名称 + public bool ContainsArgument(string name) => _arguments.ContainsKey(name); + + /// + /// Gets 具有的参数数量 + /// + public int ArgumentCount => _arguments.Count; + + public IEnumerator> GetEnumerator() => _arguments.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/MineCase.Server.Interfaces/Game/ICommand.cs b/src/MineCase.Server.Interfaces/Game/ICommand.cs deleted file mode 100644 index e443bf5e..00000000 --- a/src/MineCase.Server.Interfaces/Game/ICommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MineCase.Server.Game -{ - /// - /// 命令接口 - /// - public interface ICommand - { - /// - /// Gets 该命令的名称 - /// - string Name { get; } - - /// - /// Gets 该命令的描述,可为 null - /// - string Description { get; } - - /// - /// Gets 要执行此命令需要的权限,可为 null - /// - Permission NeededPermission { get; } - - /// - /// Gets 该命令的别名,可为 null - /// - IEnumerable Aliases { get; } - - /// - /// 执行该命令 - /// - /// 发送命令者 - /// 命令的参数 - /// 执行是否成功,如果成功则返回 true - bool Execute(ICommandSender commandSender, IEnumerable args); - } - - /// - /// 可发送命令者接口 - /// - public interface ICommandSender : IPermissible - { - } -} diff --git a/src/MineCase.Server.Interfaces/Game/IPermissible.cs b/src/MineCase.Server.Interfaces/Game/IPermissible.cs index 8960deae..21c583ec 100644 --- a/src/MineCase.Server.Interfaces/Game/IPermissible.cs +++ b/src/MineCase.Server.Interfaces/Game/IPermissible.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace MineCase.Server.Game { @@ -13,6 +14,6 @@ public interface IPermissible /// 判断是否具有特定的权限 /// /// 要判断的权限 - bool HasPermission(Permission permission); + Task HasPermission(Permission permission); } } diff --git a/src/MineCase.Server.Interfaces/Game/Permission.cs b/src/MineCase.Server.Interfaces/Game/Permission.cs index c188e0e2..76522cd7 100644 --- a/src/MineCase.Server.Interfaces/Game/Permission.cs +++ b/src/MineCase.Server.Interfaces/Game/Permission.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,7 +9,7 @@ namespace MineCase.Server.Game /// /// 权限 /// - public class Permission + public class Permission : IEnumerable { /// /// Gets 该权限的名称 @@ -27,6 +28,15 @@ public class Permission private readonly Dictionary _children; + /// + /// Initializes a new instance of the class. + /// 以指定的名称、描述、默认值及子权限构造 + /// + /// 名称 + /// 描述,可为 null + /// 默认值 + /// 子权限,可为 null,为 null 时表示不存在子权限 + /// 为 null public Permission( string name, string description = null, @@ -36,9 +46,33 @@ public Permission( Name = name ?? throw new ArgumentNullException(nameof(name)); Description = description; DefaultValue = permissionDefaultValue; - _children = children.ToDictionary( - p => p?.Name ?? throw new ArgumentException("子权限不得为 null", nameof(children))) ?? + _children = children?.Where(p => p != null).Distinct().ToDictionary(p => p.Name) ?? new Dictionary(); } + + /// + public Permission this[string name] => GetChild(name); + + /// + /// 以指定的名称获取子权限 + /// + /// 要查找的名称 + public Permission GetChild(string name) => _children[name]; + + /// + /// 判断是否包含指定名称的子权限 + /// + /// 要判断的名称 + public bool ContainsChild(string name) => _children.ContainsKey(name); + + public IEnumerator GetEnumerator() + { + return _children.Select(p => p.Value).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } } diff --git a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj index 81c97e2f..25a5c3be 100644 --- a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj +++ b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj @@ -12,6 +12,7 @@ + @@ -21,6 +22,7 @@ + From 8ad2b5fde9fcfd7181616b76366d14e3f301ce94 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Tue, 29 Aug 2017 15:55:33 +0800 Subject: [PATCH 04/11] Fix bugs, add UnitTest --- src/MineCase.Nbt/Tags/NbtCompound.cs | 2 +- .../Game/GameSession.cs | 5 +- .../Game/Commands/CommandParser.cs | 20 +++--- .../Game/Commands/DataTagArgument.cs | 6 ++ .../Game/Commands/TargetSelector.cs | 54 ++++++++++----- .../Game/Commands/TildeNotationArgument.cs | 43 ++++++++++++ .../MineCase.Server.Interfaces.csproj | 1 - tests/UnitTest/CommandTest.cs | 65 +++++++++++++++++++ tests/UnitTest/NbtTest.cs | 2 +- 9 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs create mode 100644 tests/UnitTest/CommandTest.cs 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 9fbd70cf..7d84b094 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -73,7 +73,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 25a5c3be..87a20dea 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/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 +} From a38b896c97d53e168ba8439ac879e5c0e6cd5ba6 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Fri, 1 Sep 2017 13:09:44 +0800 Subject: [PATCH 05/11] Improve code style --- .../Game/GameSession.cs | 2 +- .../Game/Commands/CommandMap.cs | 39 ++++++++++++++++--- .../Game/Commands/CommandParser.cs | 7 ++-- .../Game/Commands/DataTagArgument.cs | 3 +- .../Game/Commands/ICommand.cs | 7 +++- .../Game/Commands/SimpleCommand.cs | 4 +- .../Game/Commands/TargetSelector.cs | 3 +- .../Game/Commands/TildeNotationArgument.cs | 1 + tests/UnitTest/CommandTest.cs | 6 +-- 9 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs index 7d84b094..84896bfa 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -71,7 +71,7 @@ public async Task SendChatMessage(IUser sender, string message) var command = message.Trim(); if (command[0] == '/') { - if (!_commandMap.Dispatch(await sender.GetPlayer(), message)) + if (!await _commandMap.Dispatch(await sender.GetPlayer(), message)) { await sender.SendChatMessage( await CreateStandardChatMessage( diff --git a/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs b/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs index 00b4dd2d..dcbb2f1b 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs @@ -1,32 +1,59 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Text; +using System.Threading.Tasks; namespace MineCase.Server.Game.Commands { + /// + /// 命令 Map + /// public class CommandMap { private readonly Dictionary _commandMap = new Dictionary(); + /// + /// 注册一个命令 + /// + /// 要注册的命令 + /// 为 null + /// 已有重名的 command 被注册 public void RegisterCommand(ICommand command) { + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + + Contract.EndContractBlock(); + _commandMap.Add(command.Name, command); } - public bool Dispatch(ICommandSender sender, string commandContent) + /// + /// 分派命令 + /// + /// 命令的发送者 + /// 命令的内容 + public Task 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); + if (_commandMap.TryGetValue(commandName, out var command) && + (command.NeededPermission == null || sender.HasPermission(command.NeededPermission).Result)) + { + return command.Execute(sender, args); + } + + return Task.FromResult(false); } catch (CommandException e) { - sender.SendMessage($"在执行指令 {commandName} 之时发生指令相关的异常 {e}"); - return false; + sender.SendMessage($"在执行命令 {commandName} 之时发生命令相关的异常 {e}"); + return Task.FromResult(false); } } } diff --git a/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs index 0aa7dd1b..8c106e02 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs @@ -7,6 +7,7 @@ namespace MineCase.Server.Game.Commands { + /// /// /// 未解析参数 /// @@ -42,7 +43,7 @@ public static class CommandParser /// 输入,即作为命令被分析的文本,应当不为 null、经过 处理且以 '/' 开头 /// 命令名及命令的参数 /// 不合法 - public static (string, IList) ParseCommand(string input) + public static (string, IReadOnlyList) ParseCommand(string input) { if (input == null || input.Length < 2 || input[0] != '/') { @@ -58,8 +59,8 @@ public static (string, IList) ParseCommand(string input) return (splitResult[0].Substring(1), ParseCommandArgument(splitResult.Skip(1))); } - // 参数必须保持原来的顺序,因此返回值使用 IList 而不是 IEnumerable - private static IList ParseCommandArgument(IEnumerable input) + // 参数必须保持原来的顺序,因此返回值使用 IReadOnlyList 而不是 IEnumerable + private static IReadOnlyList ParseCommandArgument(IEnumerable input) { var result = new List(); diff --git a/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs index ed06c1f0..9e50c96e 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/DataTagArgument.cs @@ -5,10 +5,11 @@ namespace MineCase.Server.Game.Commands { + /// /// /// 数据标签参数 /// - /// 表示一个 + /// 表示一个 public class DataTagArgument : UnresolvedArgument { internal const char PrefixToken = '{'; diff --git a/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs index e807242b..8d8c480d 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs @@ -47,13 +47,14 @@ public interface ICommand /// 命令的参数 /// 执行是否成功,如果成功则返回 true /// 可能抛出派生自 的异常 - bool Execute(ICommandSender commandSender, IList args); + Task Execute(ICommandSender commandSender, IReadOnlyList args); } + /// /// /// 命令执行过程中可能发生的异常的基类 /// - /// 派生自此类的异常在 中将会被吃掉,不会传播到外部 + /// 派生自此类的异常在 中将会被吃掉,不会传播到外部 public class CommandException : Exception { public ICommand Command { get; } @@ -65,6 +66,7 @@ public CommandException(ICommand command = null, string content = null, Exceptio } } + /// /// /// 表示命令的使用方式错误的异常 /// @@ -76,6 +78,7 @@ public CommandWrongUsageException(ICommand command, string content = null, Excep } } + /// /// /// 可发送命令者接口 /// diff --git a/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs b/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs index f409492d..3bf1c8b4 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/SimpleCommand.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace MineCase.Server.Game.Commands { + /// /// /// 简单指令 /// @@ -30,6 +32,6 @@ protected SimpleCommand(string name, string description = null, Permission neede } } - public abstract bool Execute(ICommandSender commandSender, IList args); + public abstract Task Execute(ICommandSender commandSender, IReadOnlyList args); } } diff --git a/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs index d878e642..ae040b3f 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs @@ -52,8 +52,9 @@ public enum TargetSelectorType Executor } + /// /// - /// 用于选择目标的 + /// 用于选择目标的 /// public class TargetSelectorArgument : UnresolvedArgument, IEnumerable> { diff --git a/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs b/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs index bea6f298..28ba2938 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/TildeNotationArgument.cs @@ -4,6 +4,7 @@ namespace MineCase.Server.Game.Commands { + /// /// /// 波浪号记号参数 /// diff --git a/tests/UnitTest/CommandTest.cs b/tests/UnitTest/CommandTest.cs index 311b8167..30e9722f 100644 --- a/tests/UnitTest/CommandTest.cs +++ b/tests/UnitTest/CommandTest.cs @@ -36,14 +36,14 @@ public Task SendMessage(string msg) private class TestCommand : SimpleCommand { public TestCommand() - : base("test", null, null, null) + : base("test") { } - public override bool Execute(ICommandSender commandSender, IList args) + public override Task Execute(ICommandSender commandSender, IReadOnlyList args) { commandSender.SendMessage(string.Join(", ", args.Select(arg => arg.ToString()))); - return true; + return Task.FromResult(true); } } From e2d5d1beae4a13b04a8d3d57e60b4393bff7742c Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Wed, 13 Sep 2017 15:49:22 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20GameModeCommand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MineCase.Server.Commands.csproj | 12 ++ .../Vanilla/GameModeCommand.cs | 105 ++++++++++++++++++ .../Game/Entities/PlayerGrain.cs | 6 +- .../Game/GameSession.cs | 43 +++++-- .../World/WorldGrain.cs | 5 + .../Game/Commands/ICommand.cs | 2 +- .../Game/Entities/IPlayer.cs | 1 + .../Game/IGameSession.cs | 4 + .../World/IWorld.cs | 2 + src/MineCase.sln | 8 +- 10 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 src/MineCase.Server.Commands/MineCase.Server.Commands.csproj create mode 100644 src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs diff --git a/src/MineCase.Server.Commands/MineCase.Server.Commands.csproj b/src/MineCase.Server.Commands/MineCase.Server.Commands.csproj new file mode 100644 index 00000000..ddde8657 --- /dev/null +++ b/src/MineCase.Server.Commands/MineCase.Server.Commands.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.0 + + + + + + + + diff --git a/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs b/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs new file mode 100644 index 00000000..930258e9 --- /dev/null +++ b/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MineCase.Server.Game; +using MineCase.Server.Game.Entities; +using MineCase.Server.Game.Commands; + +namespace MineCase.Server.Commands.Vanilla +{ + public class GameModeCommand + : SimpleCommand + { + public GameModeCommand() + : base("gamemode", "Changes the player to a specific game mode") + { + } + + public override async Task Execute(ICommandSender commandSender, IReadOnlyList args) + { + var player = (IPlayer)commandSender; + + if (args.Count < 1) + { + throw new CommandWrongUsageException(this, "Invalid arguments"); + } + + GameMode.Class gameModeClass; + + switch (((UnresolvedArgument)args[0]).RawContent) + { + case "survival": + case "s": + case "0": + gameModeClass = GameMode.Class.Survival; + break; + case "creative": + case "c": + case "1": + gameModeClass = GameMode.Class.Creative; + break; + case "adventure": + case "ad": + case "2": + gameModeClass = GameMode.Class.Adventure; + break; + case "spectator": + case "sp": + case "3": + gameModeClass = GameMode.Class.Spectator; + break; + default: + throw new CommandWrongUsageException(this); + } + + var target = player; + + if (args.Count == 2) + { + var rawArg = (UnresolvedArgument)args[1]; + + if (rawArg is TargetSelectorArgument targetSelector) + { + switch (targetSelector.Type) + { + case TargetSelectorType.NearestPlayer: + break; + case TargetSelectorType.RandomPlayer: + break; + case TargetSelectorType.AllPlayers: + break; + case TargetSelectorType.AllEntites: + break; + case TargetSelectorType.Executor: + break; + default: + throw new CommandException(this); + } + + throw new CommandException(this, "Sorry, this feature has not been implemented."); + } + else + { + var user = await player.GetUser(); + var session = await user.GetGameSession(); + + target = await (await session.FindUserByName(rawArg.RawContent)).GetPlayer(); + + if (target == null) + { + throw new CommandException(this, $"Player \"{rawArg.RawContent}\" not found, may be offline or not existing."); + } + } + } + + var targetDesc = await target.GetDescription(); + var mode = targetDesc.GameMode; + mode.ModeClass = gameModeClass; + targetDesc.GameMode = mode; + + return true; + } + } +} diff --git a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs index 7195a811..c927ffb5 100644 --- a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs @@ -339,12 +339,14 @@ private class WindowContext public Task HasPermission(Permission permission) { - throw new NotImplementedException(); + // TODO: 临时提供所有权限,需要实现权限管理 + return Task.FromResult(true); } public Task SendMessage(string msg) { - throw new NotImplementedException(); + // TODO: 向玩家发送信息 + return Task.CompletedTask; } } } diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs index 84896bfa..1fe03c49 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -34,6 +34,24 @@ public override async Task OnActivateAsync() _gameTick = RegisterTimer(OnGameTick, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(50)); } + public Task> GetUsers() + { + return Task.FromResult((IEnumerable)_users.Keys); + } + + public async Task FindUserByName(string name) + { + foreach (var user in _users) + { + if (await user.Key.GetName() == name) + { + return user.Key; + } + } + + return null; + } + public async Task JoinGame(IUser user) { var sink = await user.GetClientPacketSink(); @@ -97,8 +115,8 @@ public async Task SendChatMessage(IUser sender, IUser receiver, string message) var senderName = await sender.GetName(); var receiverName = await receiver.GetName(); - 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) { if (await item.GetName() == receiverName || @@ -123,20 +141,23 @@ await Task.WhenAll(from u in _tickables private Task CreateStandardChatMessage(string name, string message) { - StringComponent nameComponent = new StringComponent(name); - nameComponent.ClickEvent = new ChatClickEvent(ClickEventType.SuggestCommand, "/msg " + name); - nameComponent.HoverEvent = new ChatHoverEvent(HoverEventType.ShowEntity, name); - nameComponent.Insertion = name; + var nameComponent = new StringComponent(name) + { + ClickEvent = new ChatClickEvent(ClickEventType.SuggestCommand, "/msg " + name), + HoverEvent = new ChatHoverEvent(HoverEventType.ShowEntity, name), + Insertion = name + }; // construct message - StringComponent messageComponent = new StringComponent(message); + var messageComponent = new StringComponent(message); // list - List list = new List(); - list.Add(nameComponent); - list.Add(messageComponent); + var list = new List + { + nameComponent, messageComponent + }; - Chat jsonData = new Chat(new TranslationComponent("chat.type.text", list)); + var jsonData = new Chat(new TranslationComponent("chat.type.text", list)); return Task.FromResult(jsonData); } diff --git a/src/MineCase.Server.Grains/World/WorldGrain.cs b/src/MineCase.Server.Grains/World/WorldGrain.cs index e05b3365..5809719a 100644 --- a/src/MineCase.Server.Grains/World/WorldGrain.cs +++ b/src/MineCase.Server.Grains/World/WorldGrain.cs @@ -43,6 +43,11 @@ public Task FindEntity(uint eid) return Task.FromException(new EntityNotFoundException()); } + public Task> GetEntities() + { + return Task.FromResult((IEnumerable)_entities.Values); + } + public Task<(long age, long timeOfDay)> GetTime() { return Task.FromResult((_worldAge, _worldAge % 24000)); diff --git a/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs index 8d8c480d..e21b97ba 100644 --- a/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs +++ b/src/MineCase.Server.Interfaces/Game/Commands/ICommand.cs @@ -54,7 +54,7 @@ public interface ICommand /// /// 命令执行过程中可能发生的异常的基类 /// - /// 派生自此类的异常在 中将会被吃掉,不会传播到外部 + /// 派生自此类的异常在 中将会被吃掉,不会传播到外部 public class CommandException : Exception { public ICommand Command { get; } diff --git a/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs b/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs index dc900756..3e950024 100644 --- a/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs +++ b/src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MineCase.Protocol.Play; +using MineCase.Server.Game.Commands; using MineCase.Server.Game.Windows; using MineCase.Server.Network; using MineCase.Server.User; diff --git a/src/MineCase.Server.Interfaces/Game/IGameSession.cs b/src/MineCase.Server.Interfaces/Game/IGameSession.cs index fac3b822..8822af00 100644 --- a/src/MineCase.Server.Interfaces/Game/IGameSession.cs +++ b/src/MineCase.Server.Interfaces/Game/IGameSession.cs @@ -11,6 +11,10 @@ namespace MineCase.Server.Game { public interface IGameSession : IGrainWithStringKey { + Task> GetUsers(); + + Task FindUserByName(string name); + Task JoinGame(IUser player); Task LeaveGame(IUser player); diff --git a/src/MineCase.Server.Interfaces/World/IWorld.cs b/src/MineCase.Server.Interfaces/World/IWorld.cs index 441d20d6..addf0ef7 100644 --- a/src/MineCase.Server.Interfaces/World/IWorld.cs +++ b/src/MineCase.Server.Interfaces/World/IWorld.cs @@ -17,6 +17,8 @@ public interface IWorld : IGrainWithStringKey Task FindEntity(uint eid); + Task> GetEntities(); + Task<(long age, long timeOfDay)> GetTime(); Task GetAge(); diff --git a/src/MineCase.sln b/src/MineCase.sln index 235dc028..41913555 100644 --- a/src/MineCase.sln +++ b/src/MineCase.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +VisualStudioVersion = 15.0.26730.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MineCase.Server", "MineCase.Server\MineCase.Server.csproj", "{8E71CBEC-5804-4125-B651-C78426E57C8C}" EndProject @@ -38,6 +38,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{7DC8CDBD-0 ..\data\furnace.txt = ..\data\furnace.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MineCase.Commands", "MineCase.Commands\MineCase.Commands.csproj", "{A56AEC13-918A-4A54-94DA-7CE8F01C378B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +82,10 @@ Global {B7B1A959-72F3-42C6-B138-93C8D654F139}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7B1A959-72F3-42C6-B138-93C8D654F139}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7B1A959-72F3-42C6-B138-93C8D654F139}.Release|Any CPU.Build.0 = Release|Any CPU + {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 732a2e0f2d05bc019f3c8f86a499acf8ff3c6156 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Wed, 13 Sep 2017 15:55:31 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E7=BC=BA=E6=BC=8F?= =?UTF-8?q?=E7=9A=84=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MineCase.Server.Interfaces.csproj | 1 - src/MineCase.sln | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj index 87a20dea..81c97e2f 100644 --- a/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj +++ b/src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj @@ -21,7 +21,6 @@ - diff --git a/src/MineCase.sln b/src/MineCase.sln index 41913555..fdae31f2 100644 --- a/src/MineCase.sln +++ b/src/MineCase.sln @@ -38,7 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{7DC8CDBD-0 ..\data\furnace.txt = ..\data\furnace.txt EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MineCase.Commands", "MineCase.Commands\MineCase.Commands.csproj", "{A56AEC13-918A-4A54-94DA-7CE8F01C378B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MineCase.Server.Commands", "MineCase.Server.Commands\MineCase.Server.Commands.csproj", "{FA7A21DF-BC1B-4DD4-A7CF-0F023FE709F3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -82,10 +82,10 @@ Global {B7B1A959-72F3-42C6-B138-93C8D654F139}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7B1A959-72F3-42C6-B138-93C8D654F139}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7B1A959-72F3-42C6-B138-93C8D654F139}.Release|Any CPU.Build.0 = Release|Any CPU - {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A56AEC13-918A-4A54-94DA-7CE8F01C378B}.Release|Any CPU.Build.0 = Release|Any CPU + {FA7A21DF-BC1B-4DD4-A7CF-0F023FE709F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA7A21DF-BC1B-4DD4-A7CF-0F023FE709F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA7A21DF-BC1B-4DD4-A7CF-0F023FE709F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA7A21DF-BC1B-4DD4-A7CF-0F023FE709F3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 748b8cfd07330270451b1ed433f957fc74046612 Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Wed, 13 Sep 2017 16:20:40 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E5=BC=95=E7=94=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MineCase.Core/Minecase.Core.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MineCase.Core/Minecase.Core.csproj b/src/MineCase.Core/Minecase.Core.csproj index 50806e6f..f3141288 100644 --- a/src/MineCase.Core/Minecase.Core.csproj +++ b/src/MineCase.Core/Minecase.Core.csproj @@ -9,10 +9,6 @@ MineCase.Core - - - - @@ -25,4 +21,8 @@ + + + + From 6dae373d469d5bcab56b73ca2058b95391085afe Mon Sep 17 00:00:00 2001 From: akemimadoka Date: Sat, 25 Nov 2017 16:25:48 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20GameModeCommand=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Vanilla/GameModeCommand.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs b/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs index 930258e9..dfc0cdba 100644 --- a/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs +++ b/src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs @@ -54,7 +54,7 @@ public override async Task Execute(ICommandSender commandSender, IReadOnly throw new CommandWrongUsageException(this); } - var target = player; + var targets = new List(); if (args.Count == 2) { @@ -85,19 +85,24 @@ public override async Task Execute(ICommandSender commandSender, IReadOnly var user = await player.GetUser(); var session = await user.GetGameSession(); - target = await (await session.FindUserByName(rawArg.RawContent)).GetPlayer(); + var targetPlayer = await (await session.FindUserByName(rawArg.RawContent)).GetPlayer(); - if (target == null) + if (targetPlayer == null) { throw new CommandException(this, $"Player \"{rawArg.RawContent}\" not found, may be offline or not existing."); } + + targets.Add(targetPlayer); } } - var targetDesc = await target.GetDescription(); - var mode = targetDesc.GameMode; - mode.ModeClass = gameModeClass; - targetDesc.GameMode = mode; + foreach (var target in targets) + { + var targetDesc = await target.GetDescription(); + var mode = targetDesc.GameMode; + mode.ModeClass = gameModeClass; + targetDesc.GameMode = mode; + } return true; } From 3732451ea26dd4f0ab7dcbd7bbca79cd9fc90deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=8A=20=E7=8E=8B?= Date: Sat, 25 Nov 2017 17:55:48 +0800 Subject: [PATCH 10/11] Add 'SendSystemMessage' --- .../Game/GameSession.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs index 1fe03c49..74752a32 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -91,10 +91,7 @@ public async Task SendChatMessage(IUser sender, string message) { if (!await _commandMap.Dispatch(await sender.GetPlayer(), message)) { - await sender.SendChatMessage( - await CreateStandardChatMessage( - senderName, - $"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确"), 0); + await SendSystemMessage(sender, $"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确"); } return; @@ -125,6 +122,15 @@ await item.GetName() == senderName) } } + public async Task SendSystemMessage(IUser receiver, string message) + { + var receiverName = await receiver.GetName(); + + var jsonData = await CreateStandardSystemMessage(message); + const byte position = 1; // It represents user message as system message + await receiver.SendChatMessage(jsonData, position); + } + private async Task OnGameTick(object state) { var now = DateTime.UtcNow; @@ -161,6 +167,12 @@ private Task CreateStandardChatMessage(string name, string message) return Task.FromResult(jsonData); } + private Task CreateStandardSystemMessage(string message) + { + var jsonData = new Chat(new StringComponent(message)); + return Task.FromResult(jsonData); + } + public Task Subscribe(ITickable tickable) { _tickables.Add(tickable); From a83044bda479021deff3d5a2016bffedeb9c8349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=8A=20=E7=8E=8B?= Date: Sat, 25 Nov 2017 18:06:37 +0800 Subject: [PATCH 11/11] Add 'SendGameInfo' --- src/MineCase.Server.Grains/Game/GameSession.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/MineCase.Server.Grains/Game/GameSession.cs b/src/MineCase.Server.Grains/Game/GameSession.cs index 74752a32..ecf49f24 100644 --- a/src/MineCase.Server.Grains/Game/GameSession.cs +++ b/src/MineCase.Server.Grains/Game/GameSession.cs @@ -131,6 +131,15 @@ public async Task SendSystemMessage(IUser receiver, string message) await receiver.SendChatMessage(jsonData, position); } + public async Task SendGameInfoMessage(IUser receiver, string message) + { + var receiverName = await receiver.GetName(); + + var jsonData = await CreateStandardGameInfoMessage(message); + const byte position = 2; // It represents user message as system message + await receiver.SendChatMessage(jsonData, position); + } + private async Task OnGameTick(object state) { var now = DateTime.UtcNow; @@ -173,6 +182,12 @@ private Task CreateStandardSystemMessage(string message) return Task.FromResult(jsonData); } + private Task CreateStandardGameInfoMessage(string message) + { + var jsonData = new Chat(new StringComponent(message)); + return Task.FromResult(jsonData); + } + public Task Subscribe(ITickable tickable) { _tickables.Add(tickable);