Skip to content

Commit

Permalink
Fix CommandHandler/Registry
Browse files Browse the repository at this point in the history
  • Loading branch information
Tides authored Sep 24, 2024
1 parent 972d229 commit 54d0939
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Obsidian.API.Commands.ArgumentParsers;

public sealed class MinecraftTimeArgumentParser : BaseArgumentParser<MinecraftTime>
{
public MinecraftTimeArgumentParser() : base(42, "minecraft:time")
{
}

public override bool TryParseArgument(string input, CommandContext ctx, out MinecraftTime result)
{
var lastChar = input.LastOrDefault();
var isSuccess = false;

result = default;

if (lastChar == 'd' && int.TryParse(input.TrimEnd('d'), out var dayTime))
{
result = MinecraftTime.FromDay(dayTime);

isSuccess = true;
}
else if (lastChar == 't' && int.TryParse(input.TrimEnd('t'), out var tickTime))
{
result = MinecraftTime.FromTick(tickTime);

isSuccess = true;
}
else if (lastChar == 's' && int.TryParse(input.TrimEnd('s'), out var secondsTime))
{
result = MinecraftTime.FromSecond(secondsTime);

isSuccess = true;
}
else if (int.TryParse(input, out var intValue))
{
result = MinecraftTime.FromDay(intValue);

isSuccess = true;
}

return isSuccess;
}
}
4 changes: 2 additions & 2 deletions Obsidian.API/_Interfaces/IWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public interface IWorld : IAsyncDisposable

public string DimensionName { get; }

public long Time { get; }

public long Time { get; set; }
public int DayTime { get; set; }
public string Seed { get; }

public Gamemode DefaultGamemode { get; }
Expand Down
41 changes: 41 additions & 0 deletions Obsidian.API/_Types/MinecraftTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace Obsidian.API;
public readonly struct MinecraftTime
{
public int? Day { get; private init; }

public int? Second { get; private init; }

public int? Tick { get; private init; }

public static MinecraftTime FromDay(int day) => new() { Day = day };

public static MinecraftTime FromSecond(int second) => new() { Second = second };

public static MinecraftTime FromTick(int tick) => new() { Tick = tick };

public bool ConvertServerTime(IServer server)
{
var success = false;

if (this.Day.HasValue)
{
server.DefaultWorld.Time = this.Day.Value * 24000;
success = true;
}
else if (this.Second.HasValue)
{
server.DefaultWorld.Time = this.Second.Value * 20;
success = true;
}
else if (this.Tick.HasValue)
{
server.DefaultWorld.Time = this.Tick.Value;
success = true;
}

return success;
}
}
69 changes: 41 additions & 28 deletions Obsidian/Commands/Framework/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public BaseArgumentParser GetArgumentParser(Type argumentType) =>

public Command[] GetAllCommands() => _commands.ToArray();

public void RegisterCommand(PluginContainer pluginContainer, string name, Delegate commandDelegate)
public void RegisterCommand(PluginContainer? pluginContainer, string name, Delegate commandDelegate)
{
var method = commandDelegate.Method;

Expand Down Expand Up @@ -90,20 +90,56 @@ public void RegisterCommand(PluginContainer pluginContainer, string name, Delega

public void RegisterCommandClass(PluginContainer? plugin, Type moduleType)
{
if (moduleType.GetCustomAttribute<CommandGroupAttribute>() != null)
{
this.RegisterGroupCommand(moduleType, plugin, null);
return;
}

RegisterSubgroups(moduleType, plugin);
RegisterSubcommands(moduleType, plugin);
}

public void RegisterCommands(PluginContainer pluginContainer)
public void RegisterCommands(PluginContainer? pluginContainer = null)
{
// Registering commands found in the plugin assembly
var commandRoots = pluginContainer.PluginAssembly.GetTypes().Where(x => x.IsSubclassOf(typeof(CommandModuleBase)));
var assembly = pluginContainer?.PluginAssembly ?? Assembly.GetExecutingAssembly();

var commandRoots = assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(CommandModuleBase)));

foreach (var root in commandRoots)
{
this.RegisterCommandClass(pluginContainer, root);
}
}

private void RegisterGroupCommand(Type moduleType, PluginContainer? pluginContainer, Command? parent = null)
{
var group = moduleType.GetCustomAttribute<CommandGroupAttribute>()!;
// Get command name from first constructor argument for command attribute.
var name = group.GroupName;
// Get aliases
var aliases = group.Aliases;

var checks = moduleType.GetCustomAttributes<BaseExecutionCheckAttribute>();

var info = moduleType.GetCustomAttribute<CommandInfoAttribute>();
var issuers = moduleType.GetCustomAttribute<IssuerScopeAttribute>()?.Issuers ?? CommandHelpers.DefaultIssuerScope;

var command = CommandBuilder.Create(name)
.WithDescription(info?.Description)
.WithParent(parent)
.WithUsage(info?.Usage)
.AddAliases(aliases)
.AddExecutionChecks(checks)
.CanIssueAs(issuers)
.Build(this, pluginContainer);

RegisterSubgroups(moduleType, pluginContainer, command);
RegisterSubcommands(moduleType, pluginContainer, command);

_commands.Add(command);
}

private void RegisterSubgroups(Type moduleType, PluginContainer? pluginContainer, Command? parent = null)
{
// find all command groups under this command
Expand All @@ -112,30 +148,7 @@ private void RegisterSubgroups(Type moduleType, PluginContainer? pluginContainer

foreach (var subModule in subModules)
{
var group = subModule.GetCustomAttribute<CommandGroupAttribute>()!;
// Get command name from first constructor argument for command attribute.
var name = group.GroupName;
// Get aliases
var aliases = group.Aliases;

var checks = subModule.GetCustomAttributes<BaseExecutionCheckAttribute>();

var info = subModule.GetCustomAttribute<CommandInfoAttribute>();
var issuers = subModule.GetCustomAttribute<IssuerScopeAttribute>()?.Issuers ?? CommandHelpers.DefaultIssuerScope;

var command = CommandBuilder.Create(name)
.WithDescription(info?.Description)
.WithParent(parent)
.WithUsage(info?.Usage)
.AddAliases(aliases)
.AddExecutionChecks(checks)
.CanIssueAs(issuers)
.Build(this, pluginContainer);

RegisterSubgroups(subModule, pluginContainer, command);
RegisterSubcommands(subModule, pluginContainer, command);

_commands.Add(command);
this.RegisterGroupCommand(subModule, pluginContainer, parent);
}
}

Expand Down
7 changes: 5 additions & 2 deletions Obsidian/Commands/Framework/Entities/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ public async Task ExecuteAsync(CommandContext context, string[] args)
}

if (!this.TryFindExecutor(executors, args, context, out var executor))
throw new InvalidOperationException($"Failed to find valid executor for /{this.Name}");
{
await context.Sender.SendMessageAsync(ChatMessage.Simple($"Correct usage: {Usage}", ChatColor.Red));
return;
}

await this.ExecuteAsync(executor, context, args);
}
Expand All @@ -92,7 +95,7 @@ private bool TryFindExecutor(IEnumerable<IExecutor<CommandContext>> executors, s
{
executor = null;

var success = args.Length == 0;
var success = args.Length == 0 && executors.Any(x => x.GetParameters().Length == 0);

if (success)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using Obsidian.API.Commands;
using Obsidian.API.Commands;
using Obsidian.API.Utilities;
using Obsidian.Commands.Framework.Entities;
using Obsidian.Entities;
using Obsidian.Registries;
using Obsidian.WorldData;
using System.Data;
using System.Collections.Frozen;
using System.Diagnostics;

namespace Obsidian.Commands;
namespace Obsidian.Commands.Modules;

public class MainCommandModule : CommandModuleBase

public sealed class MainCommandModule : CommandModuleBase
{
private const int CommandsPerPage = 15;

Expand Down Expand Up @@ -370,20 +371,6 @@ public async Task StopAsync()
await server.StopAsync();
}

[Command("time")]
[CommandInfo("Sets declared time", "/time <timeOfDay>")]
public Task TimeAsync() => TimeAsync(1337);

[CommandOverload]
public async Task TimeAsync(int time)
{
if (this.Player is Player player)
{
player.world.LevelData.DayTime = time;
await this.Player.SendMessageAsync($"Time set to {time}");
}
}

[Command("toggleweather", "weather")]
[RequirePermission(permissions: "obsidian.weather")]
public async Task WeatherAsync()
Expand Down
78 changes: 78 additions & 0 deletions Obsidian/Commands/Modules/TimeCommandModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Obsidian.API.Commands;
using Obsidian.Entities;
using Obsidian.Net.Packets.Play.Clientbound;
using System.Collections.Frozen;

namespace Obsidian.Commands.Modules;

[CommandGroup("time")]
[CommandInfo("Sets declared time", "/time <set|add> (value)")]
[RequirePermission(permissions: "time")]
public sealed class TimeCommandModule : CommandModuleBase
{
private const int Mod = 24_000;
private static readonly FrozenDictionary<string, int> TimeDictionary = new Dictionary<string, int>()
{
{ "day", 1000 },
{ "night", 13_000 },
{ "noon", 6000 },
{ "midnight", 18_000 }
}.ToFrozenDictionary();

[Command("query")]
[CommandInfo("Queries the time", "/time query <day|daytime|gametime>")]
public async Task Query(string value)
{
switch (value)
{
case "daytime":
await this.Sender.SendMessageAsync($"The time is {this.Server.DefaultWorld.DayTime}");
break;
case "day":
await this.Sender.SendMessageAsync($"The time is {(int)(this.Server.DefaultWorld.Time / Mod)}");
break;
case "gametime":
await this.Sender.SendMessageAsync($"The time is {this.Server.DefaultWorld.Time}");
break;
default:
await this.Sender.SendMessageAsync("Invalid value.");
break;
}
}

[Command("set")]
[CommandInfo("Sets declared time", "/time set <(d|t|s)>")]
public async Task SetTime(MinecraftTime time)
{
if (time.ConvertServerTime(this.Server))
await this.Sender.SendMessageAsync($"Set the time to {this.Server.DefaultWorld.Time}");
else
await this.Sender.SendMessageAsync("Failed to set the time.");
}

//TODO: Command Suggestions
[Command("set")]
[CommandOverload]
[CommandInfo("Sets declared time", "/time set <day|night|noon|midnight>")]
public async Task SetTime(string value)
{
if (TimeDictionary.TryGetValue(value, out int time))
{
this.Server.DefaultWorld.DayTime = time;

await this.Sender.SendMessageAsync($"Set the time to {value}");

return;
}

await this.Sender.SendMessageAsync($"{value} is an invalid argument value.");
}

[Command("add")]
public async Task AddTime(int timeToAdd)
{
this.Server.DefaultWorld.DayTime += timeToAdd;

await this.Sender.SendMessageAsync($"Set the time to {this.Server.DefaultWorld.Time}");
}
}
18 changes: 18 additions & 0 deletions Obsidian/Commands/Parsers/MinecraftTimeParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Obsidian.Net;

namespace Obsidian.Commands.Parsers;
public sealed class MinecraftTimeParser : CommandParser
{
public int Min { get; set; } = 0;

public MinecraftTimeParser() : base(42, "minecraft:time")
{
}

public override void Write(MinecraftStream stream)
{
base.Write(stream);

stream.WriteInt(Min);
}
}
7 changes: 6 additions & 1 deletion Obsidian/Obsidian.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,25 @@

<ItemGroup>
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
<Compile Remove="Commands\Framework\ArgumentParsers\**" />
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="Assets\**\*.json" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Remove="Commands\Framework\ArgumentParsers\**" />
<None Remove="Commands\Framework\ArgumentParsers\**" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Obsidian.API\Obsidian.API.csproj" />
<ProjectReference Include="..\Obsidian.SourceGenerators\Obsidian.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Obsidian.Nbt\Obsidian.Nbt.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Commands\Framework\ArgumentParsers\" />
<Folder Include="Net\Packets\Configuration\Serverbound\" />
</ItemGroup>
</Project>
Loading

0 comments on commit 54d0939

Please sign in to comment.