PocketMine-MP 5.35.1 git-f412a390b8f63d0311cc1d1c81046404153b8440
Loading...
Searching...
No Matches
SimpleCommandMap.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\command;
25
75use function array_filter;
76use function array_map;
77use function array_shift;
78use function count;
79use function implode;
80use function is_array;
81use function is_string;
82use function str_contains;
83use function strcasecmp;
84use function strtolower;
85use function trim;
86
88
93 private array $uniqueCommands = [];
94
95 private CommandAliasMap $aliasMap;
96
97 public function __construct(private Server $server){
98 $this->aliasMap = new CommandAliasMap();
99 $this->setDefaultCommands();
100 }
101
102 private function setDefaultCommands() : void{
103 $pmPrefix = "pocketmine";
104 $this->register(new BanCommand($pmPrefix, "ban"));
105 $this->register(new BanIpCommand($pmPrefix, "ban-ip"));
106 $this->register(new BanListCommand($pmPrefix, "banlist"));
107 $this->register(new ClearCommand($pmPrefix, "clear"));
108 $this->register(new CommandAliasCommand($pmPrefix, "cmdalias"));
109 $this->register(new DefaultGamemodeCommand($pmPrefix, "defaultgamemode"));
110 $this->register(new DeopCommand($pmPrefix, "deop"));
111 $this->register(new DifficultyCommand($pmPrefix, "difficulty"));
112 $this->register(new DumpMemoryCommand($pmPrefix, "dumpmemory"));
113 $this->register(new EffectCommand($pmPrefix, "effect"));
114 $this->register(new EnchantCommand($pmPrefix, "enchant"));
115 $this->register(new GamemodeCommand($pmPrefix, "gamemode"));
116 $this->register(new GarbageCollectorCommand($pmPrefix, "gc"));
117 $this->register(new GiveCommand($pmPrefix, "give"));
118 $this->register(new HelpCommand($pmPrefix, "help"), ["?"]);
119 $this->register(new KickCommand($pmPrefix, "kick"));
120 $this->register(new KillCommand($pmPrefix, "kill"), ["suicide"]);
121 $this->register(new ListCommand($pmPrefix, "list"));
122 $this->register(new MeCommand($pmPrefix, "me"));
123 $this->register(new OpCommand($pmPrefix, "op"));
124 $this->register(new PardonCommand($pmPrefix, "pardon"), ["unban"]);
125 $this->register(new PardonIpCommand($pmPrefix, "pardon-ip"), ["unban-ip"]);
126 $this->register(new ParticleCommand($pmPrefix, "particle"));
127 $this->register(new PluginsCommand($pmPrefix, "plugins"), ["pl"]);
128 $this->register(new SaveCommand($pmPrefix, "save-all"));
129 $this->register(new SaveOffCommand($pmPrefix, "save-off"));
130 $this->register(new SaveOnCommand($pmPrefix, "save-on"));
131 $this->register(new SayCommand($pmPrefix, "say"));
132 $this->register(new SeedCommand($pmPrefix, "seed"));
133 $this->register(new SetWorldSpawnCommand($pmPrefix, "setworldspawn"));
134 $this->register(new SpawnpointCommand($pmPrefix, "spawnpoint"));
135 $this->register(new StatusCommand($pmPrefix, "status"));
136 $this->register(new StopCommand($pmPrefix, "stop"));
137 $this->register(new TeleportCommand($pmPrefix, "tp"), ["teleport"]);
138 $this->register(new TellCommand($pmPrefix, "tell"), ["w", "msg"]);
139 $this->register(new TimeCommand($pmPrefix, "time"));
140 $this->register(new TimingsCommand($pmPrefix, "timings"));
141 $this->register(new TitleCommand($pmPrefix, "title"));
142 $this->register(new TransferServerCommand($pmPrefix, "transferserver"));
143 $this->register(new VersionCommand($pmPrefix, "version"), ["ver", "about"]);
144 $this->register(new WhitelistCommand($pmPrefix, "whitelist"));
145 $this->register(new XpCommand($pmPrefix, "xp"));
146 }
147
148 public function register(Command $command, array $otherAliases = []) : void{
149 if(count($command->getPermissions()) === 0){
150 throw new \InvalidArgumentException("Commands must have a permission set");
151 }
152
153 $commandId = $command->getId();
154 if(isset($this->uniqueCommands[$commandId])){
155 throw new \InvalidArgumentException("A command with ID $commandId has already been registered");
156 }
157
158 $preferredAlias = trim($command->getName());
159 $this->aliasMap->bindAlias($commandId, $preferredAlias, override: false);
160 foreach($otherAliases as $alias){
161 $this->aliasMap->bindAlias($commandId, $alias, override: false);
162 }
163
164 $this->uniqueCommands[$commandId] = $command;
165 }
166
167 public function unregister(Command $command) : bool{
168 unset($this->uniqueCommands[$command->getId()]);
169 $this->aliasMap->unbindAliasesForCommand($command->getId());
170
171 return true;
172 }
173
174 public function dispatch(CommandSender $sender, string $commandLine) : bool{
175 $args = CommandStringHelper::parseQuoteAware($commandLine);
176
177 $sentCommandLabel = array_shift($args);
178 if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel, $sender->getCommandAliasMap())) !== null){
179 if(is_array($target)){
180 self::handleConflicted($sender, $sentCommandLabel, $target, $this->aliasMap);
181 return true;
182 }
183 $timings = Timings::getCommandDispatchTimings($target->getId());
184 $timings->startTiming();
185
186 try{
187 if($target->testPermission($sentCommandLabel, $sender)){
188 $target->execute($sender, $sentCommandLabel, $args);
189 }
190 }catch(InvalidCommandSyntaxException $e){
191 //TODO: localised command message should use user-provided alias, it shouldn't be hard-baked into the language strings
192 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ?? "/$sentCommandLabel")));
193 }finally{
194 $timings->stopTiming();
195 }
196 return true;
197 }
198
199 //Don't love hardcoding the command ID here, but it seems like the only way for now
200 $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound(
201 $sentCommandLabel ?? "",
202 "/" . $sender->getCommandAliasMap()->getPreferredAlias("pocketmine:help", $this->aliasMap)
203 )->prefix(TextFormat::RED));
204 return false;
205 }
206
213 public static function handleConflicted(CommandSender $sender, string $alias, array $conflictedEntries, CommandAliasMap $fallbackAliasMap) : void{
214 $candidates = [];
215 $userAliasMap = $sender->getCommandAliasMap();
216 foreach($conflictedEntries as $c){
217 if($c->testPermissionSilent($sender)){
218 $candidates[] = "/" . $c->getId();
219 }
220 }
221 if(count($candidates) > 0){
222 //there might only be 1 permissible command here, but we still don't auto-select in this case
223 //because it might cause surprising behaviour if the user's permissions change between command
224 //invocations. Better to force them to use an unambiguous alias in all cases.
225 $candidateNames = implode(", ", $candidates);
226 $sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflict("/$alias", $candidateNames)->prefix(TextFormat::RED));
227 //Don't love hardcoding the command ID here, but it seems like the only way for now
228 $sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflictTip(
229 "/" . $userAliasMap->getPreferredAlias("pocketmine:cmdalias", $fallbackAliasMap)
230 )->prefix(TextFormat::RED));
231 }else{
232 $sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($alias)->prefix(TextFormat::RED));
233 }
234 }
235
236 public function clearCommands() : void{
237 $this->aliasMap = new CommandAliasMap();
238 $this->uniqueCommands = [];
239 $this->setDefaultCommands();
240 }
241
242 public function getCommand(string $name, ?CommandAliasMap $senderAliasMap = null) : Command|array|null{
243 if(isset($this->uniqueCommands[$name])){ //direct command ID reference
244 return $this->uniqueCommands[$name];
245 }
246 $commandId = $senderAliasMap?->resolveAlias($name) ?? $this->aliasMap->resolveAlias($name);
247 if(is_string($commandId)){
248 return $this->uniqueCommands[$commandId] ?? null;
249 }
250 if(is_array($commandId)){
251 //the user's command map may refer to commands that are no longer registered, so we need to filter these
252 //from the result set
253 //we don't deconflict if there's only 1 command left because we don't want re-running a command to randomly
254 //have a different result if the global command map was modified - the user can explicitly rebind the
255 //alias in this case
256 return array_filter(array_map(
257 fn(string $c) => $this->uniqueCommands[$c] ?? null,
258 $commandId
259 ), is_object(...));
260 }
261 return null;
262 }
263
268 public function getUniqueCommands() : array{
269 return $this->uniqueCommands;
270 }
271
272 public function registerServerAliases() : void{
273 $values = $this->server->getCommandAliases();
274
275 foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
276 if(str_contains($alias, ":")){
277 $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
278 continue;
279 }
280
281 $targets = [];
282 $bad = [];
283 $recursive = [];
284
285 foreach($commandStrings as $commandString){
286 $args = CommandStringHelper::parseQuoteAware($commandString);
287 $commandName = array_shift($args) ?? "";
288 $command = $this->getCommand($commandName);
289
290 if(!$command instanceof Command){
291 $bad[] = $commandString;
292 }elseif(strcasecmp($commandName, $alias) === 0){
293 $recursive[] = $commandString;
294 }else{
295 $targets[] = $commandString;
296 }
297 }
298
299 if(count($recursive) > 0){
300 $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_recursive($alias, implode(", ", $recursive))));
301 continue;
302 }
303
304 if(count($bad) > 0){
305 $this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_notFound($alias, implode(", ", $bad))));
306 continue;
307 }
308
309 //These registered commands have absolute priority
310 $lowerAlias = strtolower($alias);
311 if(count($targets) > 0){
312 $aliasInstance = new FormattedCommandAlias("pocketmine-config-defined", $lowerAlias, $targets);
313 $this->aliasMap->bindAlias($aliasInstance->getId(), $lowerAlias, override: true);
314 $this->uniqueCommands[$aliasInstance->getId()] = $aliasInstance;
315 }else{
316 //no targets blackholes the alias - this allows config to delete unwanted aliases
317 $this->aliasMap->unbindAlias($lowerAlias);
318 }
319 }
320 }
321
322 public function getAliasMap() : CommandAliasMap{ return $this->aliasMap; }
323}
getCommand(string $name, ?CommandAliasMap $senderAliasMap=null)
static handleConflicted(CommandSender $sender, string $alias, array $conflictedEntries, CommandAliasMap $fallbackAliasMap)