Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add command support #32

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@echo off
cd src
dotnet restore
dotnet build

pause
8 changes: 4 additions & 4 deletions src/MineCase.Core/Minecase.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
<AssemblyName>MineCase.Core</AssemblyName>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\MineCase.Nbt\MineCase.Nbt.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
Expand All @@ -25,4 +21,8 @@
<AdditionalFiles Include="..\..\build\stylecop.json" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MineCase.Nbt\MineCase.Nbt.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/MineCase.Nbt/Tags/NbtCompound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
12 changes: 12 additions & 0 deletions src/MineCase.Server.Commands/MineCase.Server.Commands.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\MineCase.Server.Grains\MineCase.Server.Grains.csproj" />
<ProjectReference Include="..\MineCase.Server.Interfaces\MineCase.Server.Interfaces.csproj" />
</ItemGroup>

</Project>
110 changes: 110 additions & 0 deletions src/MineCase.Server.Commands/Vanilla/GameModeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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<bool> Execute(ICommandSender commandSender, IReadOnlyList<ICommandArgument> 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 targets = new List<IPlayer>();

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();

var targetPlayer = await (await session.FindUserByName(rawArg.RawContent)).GetPlayer();

if (targetPlayer == null)
{
throw new CommandException(this, $"Player \"{rawArg.RawContent}\" not found, may be offline or not existing.");
}

targets.Add(targetPlayer);
}
}

foreach (var target in targets)
{
var targetDesc = await target.GetDescription();
var mode = targetDesc.GameMode;
mode.ModeClass = gameModeClass;
targetDesc.GameMode = mode;
}

return true;
}
}
}
12 changes: 12 additions & 0 deletions src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,17 @@ private class WindowContext
public IWindow Window;
public short ActionNumber;
}

public Task<bool> HasPermission(Permission permission)
{
// TODO: 临时提供所有权限,需要实现权限管理
return Task.FromResult(true);
}

public Task SendMessage(string msg)
{
// TODO: 向玩家发送信息
return Task.CompletedTask;
}
}
}
94 changes: 80 additions & 14 deletions src/MineCase.Server.Grains/Game/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal class GameSession : Grain, IGameSession

private HashSet<ITickable> _tickables;

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

public override async Task OnActivateAsync()
{
_world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
Expand All @@ -32,6 +34,24 @@ public override async Task OnActivateAsync()
_gameTick = RegisterTimer(OnGameTick, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(50));
}

public Task<IEnumerable<IUser>> GetUsers()
{
return Task.FromResult((IEnumerable<IUser>)_users.Keys);
}

public async Task<IUser> 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();
Expand Down Expand Up @@ -64,10 +84,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 (!await _commandMap.Dispatch(await sender.GetPlayer(), message))
{
await SendSystemMessage(sender, $"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确");
}

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 All @@ -79,8 +112,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 ||
Expand All @@ -89,6 +122,24 @@ 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);
}

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;
Expand All @@ -105,20 +156,35 @@ await Task.WhenAll(from u in _tickables

private Task<Chat> 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<ChatComponent> list = new List<ChatComponent>();
list.Add(nameComponent);
list.Add(messageComponent);
var list = new List<ChatComponent>
{
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);
}

private Task<Chat> CreateStandardSystemMessage(string message)
{
var jsonData = new Chat(new StringComponent(message));
return Task.FromResult(jsonData);
}

private Task<Chat> CreateStandardGameInfoMessage(string message)
{
var jsonData = new Chat(new StringComponent(message));
return Task.FromResult(jsonData);
}

Expand Down
5 changes: 5 additions & 0 deletions src/MineCase.Server.Grains/World/WorldGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ public Task<IEntity> FindEntity(uint eid)
return Task.FromException<IEntity>(new EntityNotFoundException());
}

public Task<IEnumerable<IEntity>> GetEntities()
{
return Task.FromResult((IEnumerable<IEntity>)_entities.Values);
}

public Task<(long age, long timeOfDay)> GetTime()
{
return Task.FromResult((_worldAge, _worldAge % 24000));
Expand Down
60 changes: 60 additions & 0 deletions src/MineCase.Server.Interfaces/Game/Commands/CommandMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using System.Threading.Tasks;

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

/// <summary>
/// 注册一个命令
/// </summary>
/// <param name="command">要注册的命令</param>
/// <exception cref="ArgumentNullException"><paramref name="command"/> 为 null</exception>
/// <exception cref="ArgumentException">已有重名的 command 被注册</exception>
public void RegisterCommand(ICommand command)
{
if (command == null)
{
throw new ArgumentNullException(nameof(command));
}

Contract.EndContractBlock();

_commandMap.Add(command.Name, command);
}

/// <summary>
/// 分派命令
/// </summary>
/// <param name="sender">命令的发送者</param>
/// <param name="commandContent">命令的内容</param>
public Task<bool> Dispatch(ICommandSender sender, string commandContent)
{
var (commandName, args) = CommandParser.ParseCommand(commandContent);

try
{
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 Task.FromResult(false);
}
}
}
}
Loading