diff --git a/build/php b/build/php index 7b357f8cf..19222cfb2 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 7b357f8cf9b2d2ee3a9ad247cdc76c8ad62337f9 +Subproject commit 19222cfb2867869cfc9b96fbc5f6cdbd601f5e8b diff --git a/changelogs/4.3.md b/changelogs/4.3.md index b3e7bfbab..b5ebafe52 100644 --- a/changelogs/4.3.md +++ b/changelogs/4.3.md @@ -19,3 +19,14 @@ Released 23rd April 2022. ## Fixes - Updated BedrockProtocol dependency to fix incorrect command argument types. - Creative players no longer die in the void. + +# 4.3.2 +Released 10th May 2022. + +## Fixes +- Fixed an assertion failure in certain edge cases during world generation. +- Fixed `Entity::setNameTagVisible()` not immediately showing results to players already online. + +## Documentation +- Added more documentation in the template `pocketmine.yml` for the `aliases` config section. +- Removed useless doc comment in `PlayerChangeSkinEvent`. diff --git a/composer.json b/composer.json index 3ef7889d4..6d98d2876 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "pocketmine/classloader": "^0.2.0", "pocketmine/color": "^0.2.0", "pocketmine/errorhandler": "^0.6.0", - "pocketmine/locale-data": "~2.6.0", + "pocketmine/locale-data": "~2.7.0", "pocketmine/log": "^0.4.0", "pocketmine/log-pthreads": "^0.4.0", "pocketmine/math": "^0.4.0", @@ -54,7 +54,7 @@ "webmozart/path-util": "^2.3" }, "require-dev": { - "phpstan/phpstan": "1.6.3", + "phpstan/phpstan": "1.6.8", "phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.2.0", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index b18a79b6f..9efe7674e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f28b4d0dae7984a204a5538b7f614ee4", + "content-hash": "f3349dee8c1925a1890757897b25a2dc", "packages": [ { "name": "adhocore/json-comment", @@ -563,16 +563,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "eae9303493884edcc5321b50002a5dc956a36a23" + "reference": "f00216c4709d2c5a2af478498315206b336b8e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/eae9303493884edcc5321b50002a5dc956a36a23", - "reference": "eae9303493884edcc5321b50002a5dc956a36a23", + "url": "https://api.github.com/repos/pmmp/Language/zipball/f00216c4709d2c5a2af478498315206b336b8e2e", + "reference": "f00216c4709d2c5a2af478498315206b336b8e2e", "shasum": "" }, "type": "library", @@ -580,9 +580,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.6.2" + "source": "https://github.com/pmmp/Language/tree/2.7.0" }, - "time": "2022-04-10T20:44:40+00:00" + "time": "2022-05-10T13:29:27+00:00" }, { "name": "pocketmine/log", @@ -1846,16 +1846,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.6.3", + "version": "1.6.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e" + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6128620b98292e0b69ea6d799871d77163681c8e", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", + "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", "shasum": "" }, "require": { @@ -1881,7 +1881,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.3" + "source": "https://github.com/phpstan/phpstan/tree/1.6.8" }, "funding": [ { @@ -1901,7 +1901,7 @@ "type": "tidelift" } ], - "time": "2022-04-28T11:27:53+00:00" + "time": "2022-05-10T06:54:21+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1957,16 +1957,16 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "f3ca6464eae640a556c69a02b3b77a2507475d2f" + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f3ca6464eae640a556c69a02b3b77a2507475d2f", - "reference": "f3ca6464eae640a556c69a02b3b77a2507475d2f", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", + "reference": "0c82c96f2a55d8b91bbc7ee6512c94f68a206b43", "shasum": "" }, "require": { @@ -1999,9 +1999,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.2.3" }, - "time": "2022-04-28T07:20:18+00:00" + "time": "2022-05-04T15:20:40+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/resources/pocketmine.yml b/resources/pocketmine.yml index 1b22c9e80..f922cb225 100644 --- a/resources/pocketmine.yml +++ b/resources/pocketmine.yml @@ -195,6 +195,12 @@ aliases: #kill: [suicide, say "I tried to kill $1"] ## `kill alex` -> `suicide` + `say "I tried to kill alex"` #giverandom: [give $1 $2, say "Someone has just received a $2!"] ## `giverandom alex diamond` -> `give alex diamond` + `say "Someone has just received a diamond!"` + ##To make arguments mandatory (so that the command fails if they are not provided), use $$, e.g. $$1, $$2: + #makeadmin: [op $$1] ## `makeadmin alex` -> `op alex`, `makeadmin` with no arguments = error + + ##To pass through a range of arguments, put a - (hyphen) after the index: + #tpalias: [tp $1-] ## `tpalias 256 70 256` -> `tp 256 70 256` - this passes arguments 1 and everything after it to the `tp` command + ##To change an existing command alias and make it do something else: #tp: [suicide] diff --git a/src/block/Leaves.php b/src/block/Leaves.php index 7ca55c6dc..777a29926 100644 --- a/src/block/Leaves.php +++ b/src/block/Leaves.php @@ -148,6 +148,9 @@ class Leaves extends Transparent{ if(($this->treeType->equals(TreeType::OAK()) || $this->treeType->equals(TreeType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples $drops[] = VanillaItems::APPLE(); } + if(mt_rand(1, 50) === 1){ + $drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2)); + } return $drops; } diff --git a/src/command/FormattedCommandAlias.php b/src/command/FormattedCommandAlias.php index ca8908897..d710dd971 100644 --- a/src/command/FormattedCommandAlias.php +++ b/src/command/FormattedCommandAlias.php @@ -23,15 +23,28 @@ declare(strict_types=1); namespace pocketmine\command; -use pocketmine\Server; +use pocketmine\command\utils\CommandStringHelper; +use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\lang\KnownTranslationFactory; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\TextFormat; +use function array_map; +use function array_shift; use function count; -use function ord; +use function preg_match; use function strlen; use function strpos; use function substr; class FormattedCommandAlias extends Command{ + /** + * - matches a $ + * - captures an optional second $ to indicate required/optional + * - captures a series of digits which don't start with a 0 + * - captures an optional - to indicate variadic + */ + private const FORMAT_STRING_REGEX = '/\G\$(\$)?((?!0)+\d+)(-)?/'; + /** @var string[] */ private array $formatStrings = []; @@ -44,103 +57,124 @@ class FormattedCommandAlias extends Command{ } public function execute(CommandSender $sender, string $commandLabel, array $args){ - $commands = []; - $result = false; + $result = true; foreach($this->formatStrings as $formatString){ try{ - $commands[] = $this->buildCommand($formatString, $args); + $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; } } - foreach($commands as $command){ - $result |= Server::getInstance()->dispatchCommand($sender, $command, true); + $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 (bool) $result; + return $result; } /** * @param string[] $args */ private function buildCommand(string $formatString, array $args) : string{ - $index = strpos($formatString, '$'); - while($index !== false){ + $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); - $index = strpos($formatString, '$', $index); + //offset is now pointing at the next character because we just deleted the \ continue; } - $required = false; - if($formatString[$index + 1] == '$'){ - $required = true; - - ++$index; - } - - ++$index; - - $argStart = $index; - - while($index < strlen($formatString) && self::inRange(ord($formatString[$index]) - 48, 0, 9)){ - ++$index; - } - - if($argStart === $index){ + $info = self::extractPlaceholderInfo($formatString, $index); + if($info === null){ throw new \InvalidArgumentException("Invalid replacement token"); } - - $position = (int) substr($formatString, $argStart, $index); - - if($position === 0){ - throw new \InvalidArgumentException("Invalid replacement token"); - } - - --$position; - - $rest = false; - - if($index < strlen($formatString) && $formatString[$index] === "-"){ - $rest = true; - ++$index; - } - - $end = $index; + [$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 = ""; - 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]; - } + $replacement = self::buildReplacement($args, $position, $rest); + $end = $index + strlen($fullPlaceholder); $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end); $index = $start + strlen($replacement); - - $index = strpos($formatString, '$', $index); } return $formatString; } - private static function inRange(int $i, int $j, int $k) : bool{ - return $i >= $j && $i <= $k; + /** + * @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]; } } diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 13d27c06f..548f16c39 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -64,17 +64,15 @@ use pocketmine\command\defaults\TransferServerCommand; use pocketmine\command\defaults\VanillaCommand; use pocketmine\command\defaults\VersionCommand; use pocketmine\command\defaults\WhitelistCommand; +use pocketmine\command\utils\CommandStringHelper; use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\Server; use pocketmine\utils\TextFormat; use function array_shift; use function count; -use function explode; use function implode; -use function preg_match_all; use function strcasecmp; -use function stripslashes; use function strpos; use function strtolower; use function trim; @@ -197,16 +195,7 @@ class SimpleCommandMap implements CommandMap{ } public function dispatch(CommandSender $sender, string $commandLine) : bool{ - $args = []; - preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches); - foreach($matches[0] as $k => $_){ - for($i = 1; $i <= 2; ++$i){ - if($matches[$i][$k] !== ""){ - $args[$k] = $i === 1 ? stripslashes($matches[$i][$k]) : $matches[$i][$k]; - break; - } - } - } + $args = CommandStringHelper::parseQuoteAware($commandLine); $sentCommandLabel = array_shift($args); if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel)) !== null){ @@ -259,8 +248,8 @@ class SimpleCommandMap implements CommandMap{ $recursive = []; foreach($commandStrings as $commandString){ - $args = explode(" ", $commandString); - $commandName = array_shift($args); + $args = CommandStringHelper::parseQuoteAware($commandString); + $commandName = array_shift($args) ?? ""; $command = $this->getCommand($commandName); if($command === null){ diff --git a/src/command/utils/CommandStringHelper.php b/src/command/utils/CommandStringHelper.php new file mode 100644 index 000000000..526f2d594 --- /dev/null +++ b/src/command/utils/CommandStringHelper.php @@ -0,0 +1,64 @@ + ['give', 'steve jobs', 'apple'] + * - `say "This is a \"string containing quotes\""` -> ['say', 'This is a "string containing quotes"'] + * + * @return string[] + * @phpstan-return list + */ + public static function parseQuoteAware(string $commandLine) : array{ + $args = []; + preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches); + foreach($matches[0] as $k => $_){ + for($i = 1; $i <= 2; ++$i){ + if($matches[$i][$k] !== ""){ + /** @var string $match */ //phpstan can't understand preg_match and friends by itself :( + $match = $matches[$i][$k]; + $args[(int) $k] = preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); + break; + } + } + } + + return $args; + } +} diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 1ca59df1d..2c5adbe98 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -282,6 +282,7 @@ abstract class Entity{ public function setNameTagVisible(bool $value = true) : void{ $this->nameTagVisible = $value; + $this->networkPropertiesDirty = true; } public function setNameTagAlwaysVisible(bool $value = true) : void{ diff --git a/src/item/Boat.php b/src/item/Boat.php index c23f69146..c117dab1d 100644 --- a/src/item/Boat.php +++ b/src/item/Boat.php @@ -42,5 +42,9 @@ class Boat extends Item{ return 1200; //400 in PC } + public function getMaxStackSize() : int{ + return 1; + } + //TODO } diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index 7bff33d9b..6458215ba 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -1759,6 +1759,13 @@ final class KnownTranslationFactory{ ]); } + public static function pocketmine_plugin_enableError(Translatable|string $param0, Translatable|string $param1) : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_ENABLEERROR, [ + 0 => $param0, + 1 => $param1, + ]); + } + public static function pocketmine_plugin_extensionNotLoaded(Translatable|string $extensionName) : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EXTENSIONNOTLOADED, [ "extensionName" => $extensionName, @@ -1859,6 +1866,10 @@ final class KnownTranslationFactory{ ]); } + public static function pocketmine_plugin_suicide() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_SUICIDE, []); + } + public static function pocketmine_plugin_unknownDependency(Translatable|string $param0) : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_UNKNOWNDEPENDENCY, [ 0 => $param0, diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php index ff3c448fe..9201f4b6f 100644 --- a/src/lang/KnownTranslationKeys.php +++ b/src/lang/KnownTranslationKeys.php @@ -368,6 +368,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_PLUGIN_DUPLICATEPERMISSIONERROR = "pocketmine.plugin.duplicatePermissionError"; public const POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT = "pocketmine.plugin.emptyExtensionVersionConstraint"; public const POCKETMINE_PLUGIN_ENABLE = "pocketmine.plugin.enable"; + public const POCKETMINE_PLUGIN_ENABLEERROR = "pocketmine.plugin.enableError"; public const POCKETMINE_PLUGIN_EXTENSIONNOTLOADED = "pocketmine.plugin.extensionNotLoaded"; public const POCKETMINE_PLUGIN_GENERICLOADERROR = "pocketmine.plugin.genericLoadError"; public const POCKETMINE_PLUGIN_INCOMPATIBLEAPI = "pocketmine.plugin.incompatibleAPI"; @@ -385,6 +386,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_PLUGIN_MAINCLASSWRONGTYPE = "pocketmine.plugin.mainClassWrongType"; public const POCKETMINE_PLUGIN_RESTRICTEDNAME = "pocketmine.plugin.restrictedName"; public const POCKETMINE_PLUGIN_SPACESDISCOURAGED = "pocketmine.plugin.spacesDiscouraged"; + public const POCKETMINE_PLUGIN_SUICIDE = "pocketmine.plugin.suicide"; public const POCKETMINE_PLUGIN_UNKNOWNDEPENDENCY = "pocketmine.plugin.unknownDependency"; public const POCKETMINE_SAVE_START = "pocketmine.save.start"; public const POCKETMINE_SAVE_SUCCESS = "pocketmine.save.success"; diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 5cb4e4637..47f3a257b 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -447,6 +447,13 @@ class PluginManager{ $this->enabledPlugins[$plugin->getDescription()->getName()] = $plugin; (new PluginEnableEvent($plugin))->call(); + }else{ + $this->server->getLogger()->critical($this->server->getLanguage()->translate( + KnownTranslationFactory::pocketmine_plugin_enableError( + $plugin->getName(), + KnownTranslationFactory::pocketmine_plugin_suicide() + ) + )); } } } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 4654b7c87..94ed1d97e 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -59,6 +59,7 @@ use function interface_exists; use function is_a; use function is_array; use function is_bool; +use function is_float; use function is_infinite; use function is_int; use function is_nan; @@ -435,6 +436,19 @@ final class Utils{ return $lines; } + private static function stringifyValueForTrace(mixed $value, int $maxStringLength) : string{ + return match(true){ + is_object($value) => "object " . self::getNiceClassName($value) . "#" . spl_object_id($value), + is_array($value) => "array[" . count($value) . "]", + is_string($value) => "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength), + is_bool($value) => $value ? "true" : "false", + is_int($value) => "int " . $value, + is_float($value) => "float " . $value, + $value === null => "null", + default => gettype($value) . " " . Utils::printable((string) $value) + }; + } + /** * @param mixed[][] $trace * @phpstan-param list> $trace @@ -451,22 +465,15 @@ final class Utils{ }else{ $args = $trace[$i]["params"]; } + /** @var mixed[] $args */ - $params = implode(", ", array_map(function($value) use($maxStringLength) : string{ - if(is_object($value)){ - return "object " . self::getNiceClassName($value) . "#" . spl_object_id($value); - } - if(is_array($value)){ - return "array[" . count($value) . "]"; - } - if(is_string($value)){ - return "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength); - } - if(is_bool($value)){ - return $value ? "true" : "false"; - } - return gettype($value) . " " . Utils::printable((string) $value); - }, $args)); + $paramsList = []; + $offset = 0; + foreach($args as $argId => $value){ + $paramsList[] = ($argId === $offset ? "" : "$argId: ") . self::stringifyValueForTrace($value, $maxStringLength); + $offset++; + } + $params = implode(", ", $paramsList); } $messages[] = "#$i " . (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")"; } diff --git a/src/world/World.php b/src/world/World.php index fb54ca174..436ac5f2d 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -3004,9 +3004,14 @@ class World implements ChunkManager{ unset($this->activeChunkPopulationTasks[$index]); if($dirtyChunks === 0){ - $promise = $this->chunkPopulationRequestMap[$index]; - unset($this->chunkPopulationRequestMap[$index]); - $promise->resolve($chunk); + $promise = $this->chunkPopulationRequestMap[$index] ?? null; + if($promise !== null){ + unset($this->chunkPopulationRequestMap[$index]); + $promise->resolve($chunk); + }else{ + //Handlers of ChunkPopulateEvent, ChunkLoadEvent, or just ChunkListeners can cause this + $this->logger->debug("Unable to resolve population promise for chunk x=$x,z=$z - populated chunk was forcibly unloaded while setting modified chunks"); + } }else{ //request failed, stick it back on the queue //we didn't resolve the promise or touch it in any way, so any fake chunk loaders are still valid and diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index e1dbcda47..8035f3486 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -440,6 +440,16 @@ parameters: count: 1 path: ../../../src/command/Command.php + - + message: "#^Cannot call method startTiming\\(\\) on pocketmine\\\\timings\\\\TimingsHandler\\|null\\.$#" + count: 1 + path: ../../../src/command/FormattedCommandAlias.php + + - + message: "#^Cannot call method stopTiming\\(\\) on pocketmine\\\\timings\\\\TimingsHandler\\|null\\.$#" + count: 1 + path: ../../../src/command/FormattedCommandAlias.php + - message: "#^Cannot call method startTiming\\(\\) on pocketmine\\\\timings\\\\TimingsHandler\\|null\\.$#" count: 1 diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index b44c65f31..f64b88748 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -45,6 +45,11 @@ parameters: count: 1 path: ../../../src/network/mcpe/handler/InGamePacketHandler.php + - + message: "#^Negated boolean expression is always true\\.$#" + count: 1 + path: ../../../src/network/mcpe/handler/InGamePacketHandler.php + - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" count: 1 @@ -90,8 +95,3 @@ parameters: count: 2 path: ../../../src/world/format/io/region/RegionLoader.php - - - message: "#^Negated boolean expression is always true\\.$#" - count: 1 - path: ../../../src/network/mcpe/handler/InGamePacketHandler.php - diff --git a/tests/phpunit/command/utils/CommandStringHelperTest.php b/tests/phpunit/command/utils/CommandStringHelperTest.php new file mode 100644 index 000000000..d679e2057 --- /dev/null +++ b/tests/phpunit/command/utils/CommandStringHelperTest.php @@ -0,0 +1,58 @@ +