formatStrings as $formatString){ try{ $formatArgs = CommandStringHelper::parseQuoteAware($formatString); $commands[] = array_map(fn(string $formatArg) => $this->buildCommand($formatArg, $args), $formatArgs); }catch(\InvalidArgumentException $e){ $sender->sendMessage(TextFormat::RED . $e->getMessage()); return false; } } $commandMap = $sender->getServer()->getCommandMap(); foreach($commands as $commandArgs){ //this approximately duplicates the logic found in SimpleCommandMap::dispatch() //this is to allow directly invoking the commands without having to rebuild a command string and parse it //again for no reason //TODO: a method on CommandMap to invoke a command with pre-parsed arguments would probably be a good idea //for a future major version $commandLabel = array_shift($commandArgs); if($commandLabel === null){ throw new AssumptionFailedError("This should have been checked before construction"); } if(($target = $commandMap->getCommand($commandLabel)) !== null){ $target->timings->startTiming(); try{ $target->execute($sender, $commandLabel, $args); }catch(InvalidCommandSyntaxException $e){ $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage()))); }finally{ $target->timings->stopTiming(); } }else{ $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED))); //to match the behaviour of SimpleCommandMap::dispatch() //this shouldn't normally happen, but might happen if the command was unregistered or modified after //the alias was installed $result = false; } } return $result; } /** * @param string[] $args */ private function buildCommand(string $formatString, array $args) : string{ $index = 0; while(($index = strpos($formatString, '$', $index)) !== false){ $start = $index; if($index > 0 && $formatString[$start - 1] === "\\"){ $formatString = substr($formatString, 0, $start - 1) . substr($formatString, $start); //offset is now pointing at the next character because we just deleted the \ continue; } $info = self::extractPlaceholderInfo($formatString, $index); if($info === null){ throw new \InvalidArgumentException("Invalid replacement token"); } [$fullPlaceholder, $required, $position, $rest] = $info; $position--; //array offsets start at 0, but placeholders start at 1 if($required && $position >= count($args)){ throw new \InvalidArgumentException("Missing required argument " . ($position + 1)); } $replacement = self::buildReplacement($args, $position, $rest); $end = $index + strlen($fullPlaceholder); $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end); $index = $start + strlen($replacement); } return $formatString; } /** * @param string[] $args * @phpstan-param list $args */ private static function buildReplacement(array $args, int $position, bool $rest) : string{ $replacement = ""; if($rest && $position < count($args)){ for($i = $position, $c = count($args); $i < $c; ++$i){ if($i !== $position){ $replacement .= " "; } $replacement .= $args[$i]; } }elseif($position < count($args)){ $replacement .= $args[$position]; } return $replacement; } /** * @phpstan-return array{string, bool, int, bool} */ private static function extractPlaceholderInfo(string $commandString, int $offset) : ?array{ if(preg_match(self::FORMAT_STRING_REGEX, $commandString, $matches, 0, $offset) !== 1){ return null; } $fullPlaceholder = $matches[0]; $required = ($matches[1] ?? "") !== ""; $position = (int) $matches[2]; $variadic = ($matches[3] ?? "") !== ""; return [$fullPlaceholder, $required, $position, $variadic]; } }