Open dktapps opened 1 week ago
What about using attribute in function signature to define custom validator, dynamic/hard enums to validate string or values ? In some case we probably want to have only a set of Player or whatever from a default validator/parser based on an enum. I think we need to include it to provide a better API
I'm thinking a brigadier style API would allow more flexibility. Attributes wouldn't be able to include context info, so it would be difficult to do stuff like dynamic integer bounds. Might be OK for the simple cases to infer the arg types from the function though. Similar to how we have registerEvents()
(uses magic & attributes) and registerEvent()
(everything explicit & can do weird stuff not possible with magic & attributes, but harder to work with).
Make no mistake though, this will probably be very hard to implement. As I rambled on about on #team the other day, the process of parsing args and selecting overloads would be non-trivial.
Description
This has been discussed many times over the years, but it's never been done in a satisfying way.
Currently, all commands receive their arguments as
string[]
and have to do boilerplate validation themselves, including argument count checking, conversion to appropriate types (e.g. targets), checking if the targets exist/are valid/etc, checking if the parameters are part of an accepted set of values, etc.You need only look at this nasty code to see why we need this. I remember spending hours and hours to get to those few lines of code.
Before I get into this, I'll mention that it's possible there are general-purpose libraries for supporting this kind of feature. Processing commands is not unique to Minecraft, as it's also a common need of applications to parse CLI arguments, which would have pretty much the same requirements.
It should be possible to implement this system without breaking BC, even just within a custom
Command::execute()
, as this is essentially a subcommands system combined with automatic argument validation & parsing. However, it may be desirable to introduce anOverloadedCommand
to allow overload information to be exported for use in features like #6517.This issue proposes the following:
function(Player $sender, Player $target)
function(Player $sender, Vector3 $target)
function(Player $sender, Vector3 $target, float $yaw, float $pitch)
function(CommandSender $sender, Player $subject, Player $target)
function(CommandSender $sender, Player $subject, Vector3 $target)
function(CommandSender $sender, Player $subject, Vector3 $target, float $yaw, float $pitch)
tp player1 player2
,player2
cannot be parsed as aVector3
, so overload 2 would be skipped. Overloads should be checked from most arguments to least arguments.ConsoleCommandSender
would automatically be prevented from using the first three overloads, asConsoleCommandSender
can't satisfy the typePlayer
. This fixes the age-old issue of needing to checkif($sender instanceof Player)
.pocketmine.command.tp.self
and the remaining overloads would havepocketmine.command.tp.other
. This would avoid 99% of explicit permission checking and also be used to prevent sending overloads to clients that they can't use (see #6517)string[]
, but not sure how this would work.)Justification
The current command system is annoying to work with for anything but the simplest of use-cases, due to the writer of the command needing to know how to parse the given arguments.
This also leads to many inconsistencies in the way arguments are parsed and handled. For example, some people will just use
(int) $args[0]
to convert to an int instead of usinggetInteger()
which has bounds checks. It's also easy to forget to check permissions for subcommands.Implementing this would also make it trivial to support client-side command hints as per #6517, with no extra work by the developer. I'm of the opinion that if we're going to break BC to support command hints, we should do so in a way that maximizes the benefit gained for the extra work the developer has to do.
Alternative methods