mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-16 03:51:37 +00:00
This PR started out as an effort to decouple Command and CommandMap, but it's turned into a bit more than that. A summary of changes: ## UX - Added `cmdalias create`, `cmdalias delete` and `cmdalias list` commands - `/help` now shows prefixed names such as `pocketmine:help` - Prefixed command name (e.g. `pocketmine:help`) are now visible to Minecraft clients - Permission denied messages are now able to show more useful context when e.g. checking subcommand permissions - Multiple commands claiming an alias make the alias unusable (an error will be shown when used telling the user to pick from the namespaced names), instead of whichever plugin loaded last getting lucky ## API - Added `CommandAliasMap`, which handles mapping of aliases to namespaced command IDs - Added `CommandSender->getCommandAliasMap()` for user-specific aliases - Added `CommandMap->getAliasMap()` for global fallback aliases - `Command` no longer tracks its own registered aliases (now the job of `CommandMap`), breaking circular dependency - Aliases must now be provided to `CommandMap->register()` - Aliases can now be individually registered and unregistered without re-registering/unregistering the whole command using `CommandAliasMap` APIs - Aliases are no longer namespaced, only the main command name (e.g. `pocketmine:?` is now gone while `pocketmine:help` still exists) - `Command` now requires a `$namespace` parameter, which replaces the old `$fallbackPrefix` parameter of `register()`. It should be set to the name of the plugin. Relevant issues - #6508 - #3371 - this PR doesn't implement storage, but allows configuration of per-user aliases during server runtime
243 lines
7.9 KiB
PHP
243 lines
7.9 KiB
PHP
<?php
|
|
|
|
/*
|
|
*
|
|
* ____ _ _ __ __ _ __ __ ____
|
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* @author PocketMine Team
|
|
* @link http://www.pocketmine.net/
|
|
*
|
|
*
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\command\defaults;
|
|
|
|
use pocketmine\block\BlockTypeIds;
|
|
use pocketmine\color\Color;
|
|
use pocketmine\command\CommandSender;
|
|
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
|
use pocketmine\item\StringToItemParser;
|
|
use pocketmine\item\VanillaItems;
|
|
use pocketmine\lang\KnownTranslationFactory;
|
|
use pocketmine\math\Vector3;
|
|
use pocketmine\permission\DefaultPermissionNames;
|
|
use pocketmine\player\Player;
|
|
use pocketmine\utils\Random;
|
|
use pocketmine\utils\TextFormat;
|
|
use pocketmine\world\particle\AngryVillagerParticle;
|
|
use pocketmine\world\particle\BlockForceFieldParticle;
|
|
use pocketmine\world\particle\BubbleParticle;
|
|
use pocketmine\world\particle\CriticalParticle;
|
|
use pocketmine\world\particle\DustParticle;
|
|
use pocketmine\world\particle\EnchantmentTableParticle;
|
|
use pocketmine\world\particle\EnchantParticle;
|
|
use pocketmine\world\particle\EntityFlameParticle;
|
|
use pocketmine\world\particle\ExplodeParticle;
|
|
use pocketmine\world\particle\FlameParticle;
|
|
use pocketmine\world\particle\HappyVillagerParticle;
|
|
use pocketmine\world\particle\HeartParticle;
|
|
use pocketmine\world\particle\HugeExplodeParticle;
|
|
use pocketmine\world\particle\HugeExplodeSeedParticle;
|
|
use pocketmine\world\particle\InkParticle;
|
|
use pocketmine\world\particle\InstantEnchantParticle;
|
|
use pocketmine\world\particle\ItemBreakParticle;
|
|
use pocketmine\world\particle\LavaDripParticle;
|
|
use pocketmine\world\particle\LavaParticle;
|
|
use pocketmine\world\particle\Particle;
|
|
use pocketmine\world\particle\PortalParticle;
|
|
use pocketmine\world\particle\RainSplashParticle;
|
|
use pocketmine\world\particle\RedstoneParticle;
|
|
use pocketmine\world\particle\SmokeParticle;
|
|
use pocketmine\world\particle\SplashParticle;
|
|
use pocketmine\world\particle\SporeParticle;
|
|
use pocketmine\world\particle\TerrainParticle;
|
|
use pocketmine\world\particle\WaterDripParticle;
|
|
use pocketmine\world\particle\WaterParticle;
|
|
use pocketmine\world\World;
|
|
use function count;
|
|
use function explode;
|
|
use function max;
|
|
use function microtime;
|
|
use function mt_rand;
|
|
use function strtolower;
|
|
|
|
class ParticleCommand extends VanillaCommand{
|
|
|
|
public function __construct(string $namespace, string $name){
|
|
parent::__construct(
|
|
$namespace,
|
|
$name,
|
|
KnownTranslationFactory::pocketmine_command_particle_description(),
|
|
KnownTranslationFactory::pocketmine_command_particle_usage()
|
|
);
|
|
$this->setPermission(DefaultPermissionNames::COMMAND_PARTICLE);
|
|
}
|
|
|
|
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
|
if(count($args) < 7){
|
|
throw new InvalidCommandSyntaxException();
|
|
}
|
|
|
|
if($sender instanceof Player){
|
|
$senderPos = $sender->getPosition();
|
|
$world = $senderPos->getWorld();
|
|
$pos = new Vector3(
|
|
$this->getRelativeDouble($senderPos->getX(), $sender, $args[1]),
|
|
$this->getRelativeDouble($senderPos->getY(), $sender, $args[2], World::Y_MIN, World::Y_MAX),
|
|
$this->getRelativeDouble($senderPos->getZ(), $sender, $args[3])
|
|
);
|
|
}else{
|
|
$world = $sender->getServer()->getWorldManager()->getDefaultWorld();
|
|
$pos = new Vector3((float) $args[1], (float) $args[2], (float) $args[3]);
|
|
}
|
|
|
|
$name = strtolower($args[0]);
|
|
|
|
$xd = (float) $args[4];
|
|
$yd = (float) $args[5];
|
|
$zd = (float) $args[6];
|
|
|
|
$count = isset($args[7]) ? max(1, (int) $args[7]) : 1;
|
|
|
|
$data = $args[8] ?? null;
|
|
|
|
$particle = $this->getParticle($name, $data);
|
|
|
|
if($particle === null){
|
|
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->prefix(TextFormat::RED));
|
|
return true;
|
|
}
|
|
|
|
$sender->sendMessage(KnownTranslationFactory::commands_particle_success($name, (string) $count));
|
|
|
|
$random = new Random((int) (microtime(true) * 1000) + mt_rand());
|
|
|
|
for($i = 0; $i < $count; ++$i){
|
|
$world->addParticle($pos->add(
|
|
$random->nextSignedFloat() * $xd,
|
|
$random->nextSignedFloat() * $yd,
|
|
$random->nextSignedFloat() * $zd
|
|
), $particle);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function getParticle(string $name, ?string $data = null) : ?Particle{
|
|
switch($name){
|
|
case "explode":
|
|
return new ExplodeParticle();
|
|
case "hugeexplosion":
|
|
return new HugeExplodeParticle();
|
|
case "hugeexplosionseed":
|
|
return new HugeExplodeSeedParticle();
|
|
case "bubble":
|
|
return new BubbleParticle();
|
|
case "splash":
|
|
return new SplashParticle();
|
|
case "wake":
|
|
case "water":
|
|
return new WaterParticle();
|
|
case "crit":
|
|
return new CriticalParticle();
|
|
case "smoke":
|
|
return new SmokeParticle((int) ($data ?? 0));
|
|
case "spell":
|
|
return new EnchantParticle(new Color(0, 0, 0, 255)); //TODO: colour support
|
|
case "instantspell":
|
|
return new InstantEnchantParticle(new Color(0, 0, 0, 255)); //TODO: colour support
|
|
case "dripwater":
|
|
return new WaterDripParticle();
|
|
case "driplava":
|
|
return new LavaDripParticle();
|
|
case "townaura":
|
|
case "spore":
|
|
return new SporeParticle();
|
|
case "portal":
|
|
return new PortalParticle();
|
|
case "flame":
|
|
return new FlameParticle();
|
|
case "lava":
|
|
return new LavaParticle();
|
|
case "reddust":
|
|
return new RedstoneParticle((int) ($data ?? 1));
|
|
case "snowballpoof":
|
|
return new ItemBreakParticle(VanillaItems::SNOWBALL());
|
|
case "slime":
|
|
return new ItemBreakParticle(VanillaItems::SLIMEBALL());
|
|
case "itembreak":
|
|
if($data !== null){
|
|
$item = StringToItemParser::getInstance()->parse($data);
|
|
if($item !== null && !$item->isNull()){
|
|
return new ItemBreakParticle($item);
|
|
}
|
|
}
|
|
break;
|
|
case "terrain":
|
|
if($data !== null){
|
|
$block = StringToItemParser::getInstance()->parse($data)?->getBlock();
|
|
if($block !== null && $block->getTypeId() !== BlockTypeIds::AIR){
|
|
return new TerrainParticle($block);
|
|
}
|
|
}
|
|
break;
|
|
case "heart":
|
|
return new HeartParticle((int) ($data ?? 0));
|
|
case "ink":
|
|
return new InkParticle((int) ($data ?? 0));
|
|
case "droplet":
|
|
return new RainSplashParticle();
|
|
case "enchantmenttable":
|
|
return new EnchantmentTableParticle();
|
|
case "happyvillager":
|
|
return new HappyVillagerParticle();
|
|
case "angryvillager":
|
|
return new AngryVillagerParticle();
|
|
case "forcefield":
|
|
return new BlockForceFieldParticle((int) ($data ?? 0));
|
|
case "mobflame":
|
|
return new EntityFlameParticle();
|
|
case "iconcrack":
|
|
if($data !== null && ($item = StringToItemParser::getInstance()->parse($data)) !== null && !$item->isNull()){
|
|
return new ItemBreakParticle($item);
|
|
}
|
|
break;
|
|
case "blockcrack":
|
|
if($data !== null && ($block = StringToItemParser::getInstance()->parse($data)?->getBlock()) !== null && $block->getTypeId() !== BlockTypeIds::AIR){
|
|
return new TerrainParticle($block);
|
|
}
|
|
break;
|
|
case "blockdust":
|
|
if($data !== null){
|
|
//to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts
|
|
//this allows the 4th argument to be processed normally if given without forcing it to also consume
|
|
//any unexpected parts
|
|
//we probably ought to error in this case, but this will do for now
|
|
$d = explode("_", $data, limit: 5);
|
|
if(count($d) >= 3){
|
|
return new DustParticle(new Color(
|
|
((int) $d[0]) & 0xff,
|
|
((int) $d[1]) & 0xff,
|
|
((int) $d[2]) & 0xff,
|
|
((int) ($d[3] ?? 255)) & 0xff
|
|
));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|