Begin script abstraction

This commit is contained in:
hydrant
2020-02-29 14:50:10 +01:00
parent 447dd2eabc
commit 37f499a446
48 changed files with 2201 additions and 49 deletions

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Logging;
using ReallifeGamemode.Server.Core.API;
using ReallifeGamemode.Server.Log;
namespace ReallifeGamemode.Server.Core.Commands
{
public abstract class Command
{
public abstract bool CanExecute(IPlayer player);
protected abstract IAPI Api { get; }
protected ILogger Log { get; set; }
public abstract string CommandName { get; }
public virtual string HelpText { get; } = null;
public Command()
{
Log = LogManager.GetLogger(this.GetType());
}
}
}

View File

@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using ReallifeGamemode.Server.Core.API;
using ReallifeGamemode.Server.Types;
using ReallifeGamemode.Server.Common;
using ReallifeGamemode.Server.Log;
using Microsoft.Extensions.Logging;
namespace ReallifeGamemode.Server.Core.Commands
{
class CommandHandler
{
private readonly Dictionary<string, Command> registeredCommands = new Dictionary<string, Command>();
private readonly ILogger logger = LogManager.GetLogger<CommandHandler>();
private readonly IAPI api;
private Random random = new Random();
public CommandHandler(IAPI api)
{
this.api = api;
Main.EventHandler.RegisterClientEvent("Command", OnPlayerCommand);
}
private void OnPlayerCommand(IPlayer player, object[] args)
{
var command = (args.First() as string)?.ToString()?.ToLower();
logger.LogInformation("Player '{Name}' executed command '{command}'", player.Name, command);
if (!registeredCommands.ContainsKey(command))
{
player.SendMessage($"Der Befehl ~b~/{command}~s~ existiert nicht", ChatPrefix.Error);
return;
}
var executor = registeredCommands[command];
if (!executor.CanExecute(player))
{
player.SendMessage($"Du darfst diesen Befehl nicht ausführen", ChatPrefix.Error);
return;
}
var methodInfo = executor.GetType().GetMethod("Handle");
var arguments = new List<object>
{
player
};
try
{
arguments.AddRange(ComputeParameters(methodInfo, args.Skip(1).ToArray()));
var result = methodInfo.Invoke(executor, arguments.ToArray());
}
catch (ArgumentException ex)
{
player.SendMessage($"Der Parameter ~y~'{ex.Message}'~s~ ist ungültig.", ChatPrefix.Error);
}
catch (SendHelpTextException)
{
player.SendMessage("/" + command + " " + executor.HelpText, ChatPrefix.Usage);
}
catch (TargetInvocationException ex)
{
var errorCode = GetNewErrorCode();
ex.Data.Add("ErrorCode", errorCode);
logger.LogError(ex, "Error (Code: {ErrorCode}) while executing command '{Command}' from user '{Name}'", errorCode, command, player.Name);
player.SendMessage($"Bei der Ausführung des Befehls gab es einen unbekannten Fehler. ~m~({errorCode})", ChatPrefix.Error);
}
}
public void RegisterCommand(string command, Command commandExecutor)
{
command = command.ToLower();
logger.LogDebug("Adding command '{command}'", command);
if (command.IsNullOrEmpty())
{
logger.LogError("Command name is null");
return;
}
if (commandExecutor == null)
{
logger.LogError("Command executor for command '{command}' is null", command);
return;
}
var methodInfo = commandExecutor.GetType().GetMethod("Handle");
if (methodInfo == null)
{
logger.LogError("The executor for command '{command}' does not have a 'Handle' Method", command);
return;
}
registeredCommands[command] = commandExecutor;
}
private object[] ComputeParameters(MethodInfo methodInfo, object[] arguments)
{
var computedParameters = new List<object>();
var parameterInfos = methodInfo.GetParameters();
var parameterCount = parameterInfos.Length - 1;
var argumentCount = arguments.Length;
for (var i = 1; i <= parameterCount; i++)
{
var currentParameter = parameterInfos[i];
var parameterType = currentParameter.ParameterType;
object currentArgument;
if (i < argumentCount + 1)
{
currentArgument = arguments[i - 1];
}
else if (argumentCount <= (i - 1) && currentParameter.HasDefaultValue)
{
currentArgument = currentParameter.DefaultValue;
computedParameters.Add(currentArgument);
continue;
}
else
{
throw new CommandArgumentCountMismatchException();
}
if (parameterType == typeof(IPlayer))
{
var player = api.GetPlayerFromNameOrId(currentArgument.ToString());
if (player == null)
{
throw new ArgumentException($"Der Spieler wurde nicht gefunden.");
}
computedParameters.Add(player);
continue;
}
if (parameterType == typeof(string))
{
if (i == parameterCount)
{
var str = string.Join(' ', arguments.Skip(i - 1));
computedParameters.Add(str);
break;
}
else
{
computedParameters.Add(currentArgument);
}
}
if (parameterType.IsEnum)
{
var stringArg = currentArgument.ToString();
if (stringArg.TryParseEnum(parameterType, out var result))
{
computedParameters.Add(result);
}
else
{
throw new ArgumentException(currentArgument.ToString());
}
}
else if (parameterType.IsPrimitive)
{
try
{
computedParameters.Add(Convert.ChangeType(currentArgument, parameterType));
}
catch
{
throw new ArgumentException(currentArgument.ToString());
}
}
}
return computedParameters.ToArray();
}
private string GetNewErrorCode()
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#?$";
return new string(Enumerable.Repeat(chars, 6)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.Logging;
using ReallifeGamemode.Database.Models;
using ReallifeGamemode.Server.Core.API;
using ReallifeGamemode.Server.Core.Commands;
using ReallifeGamemode.Server.Log;
namespace ReallifeGamemode.Server.Core
{
public class Main
{
internal static IAPI API { get; private set; }
internal static Events.EventHandler EventHandler { get; private set; }
private static List<Script> loadedScripts = new List<Script>();
private readonly ILogger logger = LogManager.GetLogger<Main>();
public Main(IAPI api, Events.EventHandler eventHandler)
{
logger.LogInformation("Loading scripts...");
API = api;
EventHandler = eventHandler;
LoadScript();
LoadCommands();
}
private void LoadScript()
{
var allTypes = Assembly.GetExecutingAssembly().GetTypes();
var commandTypes = allTypes.Where(t => t.IsSubclassOf(typeof(Script)) && !t.IsAbstract);
foreach (var scriptType in commandTypes)
{
logger.LogDebug("Loading script '{FullName}'", scriptType.FullName);
var script = Activator.CreateInstance(scriptType) as Script;
loadedScripts.Add(script);
}
}
private void LoadCommands()
{
CommandHandler commandHandler = new CommandHandler(API);
var allTypes = Assembly.GetExecutingAssembly().GetTypes();
var commandTypes = allTypes.Where(t => t.IsSubclassOf(typeof(Command)) && !t.IsAbstract);
foreach (var command in commandTypes)
{
var cmd = Activator.CreateInstance(command) as Command;
commandHandler.RegisterCommand(cmd.CommandName, cmd);
}
}
public static DatabaseContext GetDbContext(bool useLoggerFactory = true)
{
return new DatabaseContext(useLoggerFactory ? LogManager.Factory : null);
}
internal static TScript GetScript<TScript>() where TScript : Script
{
return loadedScripts.Where(s => s.GetType() == typeof(TScript)).First() as TScript;
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ReallifeGamemode.Database\ReallifeGamemode.Database.csproj" />
<ProjectReference Include="..\ReallifeGamemode.Server.Common\ReallifeGamemode.Server.Common.csproj" />
<ProjectReference Include="..\ReallifeGamemode.Server.Core.API\ReallifeGamemode.Server.Core.API.csproj" />
<ProjectReference Include="..\ReallifeGamemode.Server.Core.Events\ReallifeGamemode.Server.Core.Events.csproj" />
<ProjectReference Include="..\ReallifeGamemode.Server.Log\ReallifeGamemode.Server.Log.csproj" />
<ProjectReference Include="..\ReallifeGamemode.Server.Types\ReallifeGamemode.Server.Types.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using ReallifeGamemode.Server.Core.API;
namespace ReallifeGamemode.Server.Core
{
internal abstract class Script
{
protected IAPI Api { get; } = Main.API;
public Script()
{
}
}
}