Пример #1
0
        /// <summary>
        /// Register an argument converter.
        /// </summary>
        /// <param name="type"> The type of the argument converter to register. </param>
        /// <exception cref="ArgumentNullException"> The given type is null. </exception>
        /// <exception cref="InvalidOperationException"> The given type does not implement IArgumentConverter. </exception>
        /// <exception cref="InvalidOperationException"> Failed to instantiate argument converter, or the converter returned a null converted type. </exception>
        public void RegisterArgumentConverter(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            if (!typeof(IArgumentConverter).IsAssignableFrom(type))
            {
                throw new InvalidOperationException("Type must implement IArgumentConverter.");
            }

            if (!type.IsDefined(typeof(ArgumentConverterAttribute)))
            {
                throw new InvalidOperationException(
                          $"Argument converter '{type.FullName}' does not have an [ArgumentConverter] attribute! Please add it as s&box requires it.");
            }

            if (Library.Create <IArgumentConverter>(type) is not {
            } converter)
            {
                throw new InvalidOperationException($"Could not instantiate argument converter of type '{type.Name}'.");
            }

            var convertedType = converter.GetConvertedType();

            if (convertedType == null)
            {
                throw new InvalidOperationException($"Converter '{type.FullName}' returned null converted type.");
            }

            if (_converters.ContainsKey(convertedType))
            {
                AdvisorLog.Warning($"Cannot register converter '{type.FullName}' as a converter for type '{convertedType.FullName}' already exists.");
                return;
            }

            AdvisorLog.Debug($"Registered argument converter '{type.FullName}' for type '{converter.GetConvertedType().FullName}'");
            _converters.Add(convertedType, converter);
        }
Пример #2
0
        private void InitializeAdvisor()
        {
            AdvisorLog.Info("Initializing Advisor Core...");
            var sw = new Stopwatch();

            _services = new AdvisorServiceContainer(this);

            _configuration = new ConfigurationService();
            _configuration.LoadConfiguration();
            _services.AddService(_configuration);

            _commandRegistry = new CommandRegistry(this);
            _services.AddService(_commandRegistry);

            // Register Advisor's argument converters and commands.
            _commandRegistry.RegisterArgumentConverters(Assembly.GetExecutingAssembly());
            _commandRegistry.RegisterCommandModules(Assembly.GetExecutingAssembly());

            _commandHandler = new CommandHandler(this);
            _services.AddService(_commandHandler);

            AdvisorLog.Info($"Successfully initialized Advisor Core in {sw.ElapsedMilliseconds} ms");
        }
Пример #3
0
        private void RegisterCommand(CommandModule module, MethodInfo info)
        {
            if (module == null)
            {
                throw new ArgumentNullException(nameof(module));
            }

            if (info == null)
            {
                throw new ArgumentNullException(nameof(info));
            }

            // Check that the given method is actually in the module.
            if (info.DeclaringType != module.GetType())
            {
                throw new InvalidOperationException(
                          "Cannot register a MethodInfo in a CommandModule that isn't its declaring type.");
            }

            // Check if the method has the command attribute. This should be the case here, but never trust others.
            var commandAttr = info.GetCustomAttribute <CommandAttribute>();

            if (commandAttr == null)
            {
                throw new InvalidOperationException($"Method {info.Name} has no CommandAttribute");
            }

            // Check that the command's name is valid.
            if (!commandAttr.HasValidCommandName())
            {
                throw new ArgumentException("Command name can only consist of characters A-Z, 0-9, -, _");
            }

            if (module.Prefix == null)
            {
                // Make sure that there's no existing command with that name.
                if (_rootCommands.ContainsKey(commandAttr.Name.ToLower()))
                {
                    AdvisorLog.Warning($"Cannot register command '{commandAttr.Name}' from module '{module.GetType().Name}' as another root command with that name exists.");
                    return;
                }
            }
            else
            {
                if (_categorizedCommands.ContainsKey(module.Prefix))
                {
                    var dict = _categorizedCommands[module.Prefix];
                    if (dict.ContainsKey(commandAttr.Name.ToLower()))
                    {
                        AdvisorLog.Warning($"Cannot register command '{module.Prefix} {commandAttr.Name}' from module '{module.GetType().Name}' as another command with that name exists.");
                        return;
                    }
                }
            }

            // Check that the return type is void.
            if (info.ReturnType != typeof(void))
            {
                throw new InvalidOperationException($"Command '{info.Name}' must return void.");
            }

            // Check that the first parameter is a CommandContext.
            var cmdParameters = info.GetParameters();

            if (cmdParameters.Length == 0 || cmdParameters[0].ParameterType != typeof(CommandContext))
            {
                throw new InvalidOperationException(
                          "Commands must always have at least one parameter, and start with a CommandContext.");
            }

            if (cmdParameters[0].IsOut || cmdParameters[0].IsIn)
            {
                throw new InvalidOperationException("Commands do not support in/out parameters.");
            }

            var commandArguments = new List <CommandArgument>();

            // Verify that we have argument converters for every single one of these types.
            if (cmdParameters.Length > 1)
            {
                for (int i = 1; i < cmdParameters.Length; i++)
                {
                    var param = cmdParameters[i];

                    if (param.IsIn || param.IsOut)
                    {
                        throw new InvalidOperationException("Commands do not support in/out parameters.");
                    }

                    var remainder = param.IsDefined(typeof(RemainderAttribute));

                    // Only the last argument can be catch all.
                    if (remainder && i != cmdParameters.Length - 1)
                    {
                        throw new InvalidOperationException(
                                  $"Only the last parameter of a command can have the RemainderAttribute (in command '{info.Name}' of module '{module.GetType().Name}')");
                    }

                    if (remainder && param.ParameterType != typeof(string))
                    {
                        throw new InvalidOperationException(
                                  $"Only an argument of type string can have the RemainderAttribute (in command '{info.Name}' of module '{module.GetType().Name}')");
                    }

                    var argType  = param.ParameterType;
                    var isParams = false;

                    if (param.IsDefined(typeof(ParamArrayAttribute)))
                    {
                        isParams = true;
                        argType  = param.ParameterType.GetElementType();
                        if (argType == null)
                        {
                            throw new InvalidOperationException(
                                      $"Command '{info.Name}' of module '{module.GetType().Name}' has a params argument with an invalid element type.");
                        }
                    }

                    if (!_converters.ContainsKey(argType))
                    {
                        throw new InvalidOperationException(
                                  $"Command '{info.Name}' of module '{module.GetType().Name}' has parameter '{param.Name ?? "null"}' with no IArgumentConverter registered for its type ({argType.Name})");
                    }

                    var arg = new CommandArgument
                    {
                        ArgumentType = argType,
                        Converter    = _converters[argType],
                        Parameter    = param,
                        IsParams     = isParams,
                        Remainder    = remainder,
                    };

                    commandArguments.Add(arg);
                }
            }

            // Create the command object.
            var cmd = new Command
            {
                Arguments        = commandArguments,
                ParentModule     = module,
                Name             = commandAttr.Name,
                Method           = info,
                CommandAttribute = commandAttr,
            };

            cmd.FullName = !string.IsNullOrWhiteSpace(module.Prefix)
                ? $"{module.Prefix} {cmd.Name}".ToLower()
                : cmd.Name.ToLower();

            // Populate any additional attributes.
            foreach (var attr in info.GetCustomAttributes())
            {
                switch (attr)
                {
                case DescriptionAttribute desc: cmd.Description = desc.Description;
                    break;

                case AliasAttribute alias: cmd.Aliases = alias.Aliases;
                    break;
                }
            }

            // TODO: Maybe add a CommandContext, TargetPlayer overload? If the performance is worth it over dyn invoke.
            if (commandArguments.Count > 0)
            {
                // Dynamically generate the delegate for the command's method.
                var paramTypes = info.GetParameters()
                                 .Select(p => p.ParameterType)
                                 .Append(info.ReturnType)
                                 .ToArray();

                // TODO: System.Linq.Expressions
                // Not as fast as a known delegate signature, and requires executing the command with DynamicInvoke().
                // var delType = Expression.GetDelegateType(paramTypes);
                // cmd.MethodDelegate = Delegate.CreateDelegate(delType, module, info);
            }
            else
            {
                // No arguments means we know exactly the type of the delegate and can build it that way.
                // Faster to execute than DynamicInvoke on an arbitrary delegate.
                cmd.MethodDelegateNoParams = info.CreateDelegate <Action <CommandContext> >(module);
            }

            AdvisorLog.Debug($"Registered command '{cmd.Name}' with {cmd.Arguments.Count} arguments.");
            module.InternalCommands.Add(cmd);

            if (module.Prefix == null)
            {
                _rootCommands.Add(cmd.FullName.ToLower(), cmd);

                foreach (string alias in cmd.Aliases)
                {
                    var a = alias.ToLower();
                    if (!_rootCommands.ContainsKey(a))
                    {
                        _rootCommands.Add(a, cmd);
                    }
                    else
                    {
                        AdvisorLog.Error($"Command '{cmd.Name}' in module 'w{module.GetType().Name}' has an alias '{alias}' that conflicts with another command!");
                        return;
                    }
                }
            }
            else
            {
                var prefix = module.Prefix.ToLower();
                if (!_categorizedCommands.ContainsKey(prefix))
                {
                    _categorizedCommands.Add(prefix, new Dictionary <string, Command>());
                }

                _categorizedCommands[prefix].Add(cmd.Name.ToLower(), cmd);

                foreach (string alias in cmd.Aliases)
                {
                    var a = alias.ToLower();
                    if (!_categorizedCommands[prefix].ContainsKey(a))
                    {
                        _categorizedCommands[prefix].Add(a, cmd);
                    }
                    else
                    {
                        AdvisorLog.Error($"Command '{module.Prefix} {cmd.Name}' in module {module.GetType().Name}' has an alias '{alias}' that conflicts with another command!");
                        return;
                    }
                }
            }
        }
Пример #4
0
 public void TestCommand(CommandContext ctx)
 {
     AdvisorLog.Error("Hello, world!");
 }