Compare commits

...

54 Commits

Author SHA1 Message Date
a7fc245291 Release 3.14.3 2020-08-11 21:06:38 +01:00
9bd6d5c67e Updated travis pthreads to pmmp/pthreads@45579e1e62 2020-08-10 17:45:06 +01:00
aaa23361d1 updated devtools links 2020-08-10 17:32:09 +01:00
691d92a959 moved tests/plugins/PocketMine-DevTools -> tests/plugins/DevTools 2020-08-10 17:29:44 +01:00
50101663f2 Use the up-to-date git submodule urls 2020-08-10 17:25:48 +01:00
e369966890 updated composer.lock 2020-08-07 20:00:12 +01:00
63f57841de PlayerAuthInputPacket: fixed yaw/pitch being the wrong way round, closes #3757 2020-08-07 19:50:49 +01:00
ac3bba0a11 Bump phpunit/phpunit from 9.2.6 to 9.3.2 (#3758) 2020-08-07 15:38:57 +00:00
1ff3df6ff0 Bump phpstan/phpstan-phpunit from 0.12.15 to 0.12.16 (#3753) 2020-08-05 19:49:04 +00:00
4e29b216bf Bump phpstan/phpstan from 0.12.35 to 0.12.36 (#3752) 2020-08-05 18:12:12 +00:00
809dad2ac8 Bump phpstan/phpstan-phpunit from 0.12.11 to 0.12.15
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 0.12.11 to 0.12.15.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/0.12.11...0.12.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-05 10:46:27 +00:00
e238d583b8 Bump phpstan/phpstan-strict-rules from 0.12.3 to 0.12.4 (#3748) 2020-08-05 10:38:00 +00:00
3f89bd7bff TaskHandler->getOwnerName() never returns NULL 2020-08-04 16:58:27 +01:00
8da7e789fd LoginPacket: protocol cannot be NULL 2020-08-04 16:55:47 +01:00
0766952f39 FINALLY, a usable new build of phpstan 2020-08-04 11:38:00 +01:00
eeee1fbe73 Updated composer dependencies 2020-08-04 11:32:25 +01:00
46c224da86 phpstan: remove an obsolete ignored error pattern from explicit-mixed baseline 2020-08-03 19:54:53 +01:00
3c001b310f fix phpstan analyze failure 2020-08-03 19:54:14 +01:00
198a106b9f Merge branch 'stable' of https://github.com/pmmp/pocketmine-mp into stable 2020-08-03 19:37:30 +01:00
1f5e0bc96d Updated BedrockData submodule, new recipes.json format 2020-08-03 19:36:32 +01:00
41f7c07703 Entity: report the class in getSaveId() unregistered entity exception (#3744) 2020-08-03 00:20:28 +01:00
f0a0c9a85f Player: remove useless var 2020-08-02 23:49:07 +01:00
5b620d964e Do not assume the presence of a crafting transaction closing marker
fixes #3655, fixes #3241
instead of guessing where the end of the transaction is, we attempt validation after every piece of the transaction, with the assumption being that a crafting transaction will not validate until it's complete.
2020-08-02 23:37:33 +01:00
756840f11d Fixed matchItems corrupting CraftingTransaction internal state on repeated validation
This bug became apparent while developing a more robust fix for 1.16 crafting.
2020-08-02 23:07:47 +01:00
df2c3136c9 VersionString: added missing start anchor to regex 2020-08-02 21:10:47 +01:00
a6b5cddd5a remove unused import 2020-07-21 19:26:24 +01:00
5b9453af43 WhitelistCommand: fixed incorrect implode() parameter order
PHP allows this to work either way for legacy reasons, but glue-first is the canonical way for a long time.
2020-07-21 11:46:11 +01:00
8bba25f4f5 Fix wrong hardness value for Podzol (#3709) 2020-07-16 22:07:41 +01:00
f9bd7016aa Bump phpstan/phpstan-strict-rules from 0.12.2 to 0.12.3 (#3705) 2020-07-16 15:24:42 +00:00
213406fc60 Bump phpunit/phpunit from 9.2.5 to 9.2.6 (#3701) 2020-07-14 16:44:35 +00:00
7ff6e5895e added missing 3.14.2 changelog 2020-07-13 11:55:08 +01:00
2e6b62fdec 3.14.3 is next 2020-07-13 10:46:58 +01:00
4fc5b9772a Release 3.14.2 2020-07-13 10:46:57 +01:00
5d4880b0a7 SendUsageTask: fixed json_encode() choking on player list keys 2020-07-11 20:14:04 +01:00
2b1a0e1e72 PlayerRespawnEvent: harden setRespawnPosition()
apparently plugins like to pass around positions which have null worlds, which aside from being quite stupid, also breaks a lot of stuff and makes it look like PM is to blame when it's just trying to make everything work the way it's supposed to ...
2020-07-10 20:37:45 +01:00
cd022f1592 EmotePacket: make FLAG_SERVER constant public 2020-07-10 20:02:32 +01:00
4ae3fd7734 Player: Reset spawn chunk send count if teleporting pre-spawn 2020-07-09 12:17:19 +01:00
b2249f93c0 TaskHandler: bail if given a task that already has a handler
This fixes undefined behaviour when scheduling the same task twice. This is usually accidental and almost never desirable.
Note that this still allows a task to be scheduled again after it has
been cancelled; it only disallows scheduling a task multiple times
concurrently.

This commit will probably break MyPlot and other plugins that have
self-scheduling tasks, but as far as I can tell those use-cases should
be replaced with self-cancelling repeating tasks anyway.
2020-07-08 11:02:33 +01:00
303344783a CheckTestCompletionTask: use TaskHandler->cancel() 2020-07-08 10:57:20 +01:00
75e0844ff5 MainLogger: log stack traces with CRITICAL level
maybe this will get people to send the whole thing instead of just the error message? ...
2020-07-08 10:45:15 +01:00
18fabf5466 3.14.2 is next 2020-07-08 10:32:07 +01:00
2751c59979 Release 3.14.1 2020-07-08 10:32:07 +01:00
d99ffbd66c Attribute: register lava_movement attribute
this is purely to fix crashes when decoding net packets
2020-07-08 10:21:20 +01:00
a34f3261cb event: harden APIs that accept arrays
plugin devs can't be relied on to pass the proper types to these APIs, and when the wrong types get passed it makes type errors appear from inside the internals.
2020-07-04 21:55:23 +01:00
8ce0022de6 protocol: added UUInventorySlotOffset constants 2020-07-04 21:37:37 +01:00
fb6491ddeb BanListCommand: sort output into lexical order 2020-07-03 11:23:00 +01:00
3b961d0e5f WhitelistCommand: sort output of /whitelist list into lexical order 2020-07-03 11:19:23 +01:00
a60fc4cc28 ListCommand: sort output into lexical order 2020-07-03 11:15:31 +01:00
b747899fdd PluginsCommand: sort plugins list into lexical order 2020-07-03 11:13:32 +01:00
57b6451e16 Fix projectile motion being changed by the ladder, close #3602 (#3631) 2020-06-27 21:18:39 +01:00
8cf025a2df Default isVerified to true (#3644) 2020-06-27 21:17:34 +01:00
8480ee82ea Player: track hardcoded window state, fixes crashes opening inventory on high-latency connections 2020-06-27 18:34:39 +01:00
a6c1b7bf9c InventoryTransactionPacket: added missing field for encode 2020-06-26 20:57:48 +01:00
c267137fde 3.14.1 is next 2020-06-26 14:19:02 +01:00
45 changed files with 749 additions and 417 deletions

8
.gitmodules vendored
View File

@ -1,12 +1,12 @@
[submodule "src/pocketmine/lang/locale"]
path = src/pocketmine/lang/locale
url = https://github.com/pmmp/PocketMine-Language.git
url = https://github.com/pmmp/Language.git
[submodule "tests/preprocessor"]
path = build/preprocessor
url = https://github.com/pmmp/preprocessor.git
[submodule "tests/plugins/PocketMine-DevTools"]
path = tests/plugins/PocketMine-DevTools
url = https://github.com/pmmp/PocketMine-DevTools.git
[submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git
[submodule "build/php"]
path = build/php
url = https://github.com/pmmp/php-build-scripts.git

View File

@ -8,7 +8,7 @@ before_script:
- echo | pecl install channel://pecl.php.net/yaml-2.1.0
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout 646dac62ae0d48c1ada7b007e15575fb84f7d71d
- git checkout 45579e1e622acd80f9c880f3a025ba3b98b8ebef
- phpize
- ./configure
- make

View File

@ -19,7 +19,7 @@
## For developers
* [Building and running from source](BUILDING.md)
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
* [Contributing Guidelines](CONTRIBUTING.md)

View File

@ -15,3 +15,26 @@ Plugin developers should **only** update their required API to this version if y
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
- New blocks, items & mobs aren't implemented
- Nether doesn't exist
# 3.14.1
- All skins are now trusted, bypassing the client-side trusted skin setting. Note that this means that NSFW skin filtering will **not** apply.
- Fixed projectile motion being altered by ladders.
- Fixed client-sided crashes when pressing E repeatedly very quickly on a high-latency connection.
- `/plugins`, `/whitelist list`, `/banlist` and `/list` now show output in alphabetical order.
- Some `pocketmine\event` APIs which accept arrays now have more robust type checking, fixing type errors caused by plugin input occurring in core code.
- `Attribute::getAttributeByName()` is now aware of the `minecraft:lava_movement` attribute.
# 3.14.2
- Exception stack traces are now logged as CRITICAL. It's hoped that users will recognize that they are just as important as the error message and not leave them out when asking for help with errors on Discord.
- `TaskScheduler` no longer accepts tasks that already have a handler. This fixes undefined behaviour which occurs when scheduling the same task instance twice, but it does break plugins such as **MyPlot** which unintentionally used this buggy behaviour.
- Players will now correctly receive the needed number of spawn chunks if they are teleported between `PlayerLoginEvent` and `PlayerJoinEvent`. This fixes a bug that could occur when teleporting players in delayed tasks between login and join.
- `PlayerRespawnEvent->setRespawnPosition()` now throws an exception if the provided `Position` has an invalid world associated with it (null or unloaded).
- Fixed a crash that occurred when stats reporting was enabled.
# 3.14.3
- Fixed deprecation error when running `/whitelist list` on PHP 7.4.
- Fixed podzol breaking animation being incorrect (incorrect hardness).
- `Entity::getSaveId()` now reports the class name in the message thrown for unregistered entities.
- Fixed `CraftingManager->validate()` producing different results when called multiple times for the same transaction.
- Fixed various issues with batch-crafting items using the recipe book and shift-clicking.
- `tests/plugins/PocketMine-DevTools` submodule has been renamed to `tests/plugins/DevTools`.

View File

@ -38,7 +38,7 @@
"ocramius/package-versions": "^1.5"
},
"require-dev": {
"phpstan/phpstan": "0.12.29",
"phpstan/phpstan": "0.12.36",
"phpstan/phpstan-phpunit": "^0.12.6",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^9.2"

753
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,6 @@ This site contains auto-generated API documentation for PocketMine-MP (and depen
This site can be accessed via https://apidoc.pmmp.io.
### Additional developer resources
- [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
- [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
- [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
- [DeveloperDocs](https://github.com/pmmp/DeveloperDocs/) - Reference, guides and specifications for the PocketMine-MP API

View File

@ -149,6 +149,7 @@ use pocketmine\network\mcpe\protocol\types\CommandParameter;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\GameMode;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor;
use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece;
@ -176,6 +177,7 @@ use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\UUID;
use function abs;
use function array_key_exists;
use function array_merge;
use function array_values;
use function assert;
@ -315,6 +317,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var CraftingTransaction|null */
protected $craftingTransaction = null;
/**
* TODO: HACK! This tracks GUIs for inventories that the server considers "always open" so that the client can't
* open them twice. (1.16 hack)
* @var true[]
* @phpstan-var array<int, true>
* @internal
*/
public $openHardcodedWindows = [];
/** @var int */
protected $messageCounter = 2;
/** @var bool */
@ -1887,7 +1898,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol ?? "unknown"]), false);
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol]), false);
return true;
}
@ -2382,18 +2393,20 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var InventoryAction[] $actions */
$actions = [];
$isCraftingPart = false;
$isFinalCraftingPart = false;
foreach($packet->actions as $networkInventoryAction){
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and (
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
) or (
$this->craftingTransaction !== null &&
!$networkInventoryAction->oldItem->equalsExact($networkInventoryAction->newItem) &&
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
$networkInventoryAction->windowId === ContainerIds::UI &&
$networkInventoryAction->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT
)
){
$isCraftingPart = true;
if($networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT){
$isFinalCraftingPart = true;
}
}
try{
$action = $networkInventoryAction->createInventoryAction($this);
@ -2416,23 +2429,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($isFinalCraftingPart){
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
//trying to execute it
$ret = true;
try{
$this->craftingTransaction->execute();
}catch(TransactionValidationException $e){
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
$ret = false;
}
$this->craftingTransaction = null;
return $ret;
try{
$this->craftingTransaction->validate();
}catch(TransactionValidationException $e){
//transaction is incomplete - crafting transaction comes in lots of little bits, so we have to collect
//all of the parts before we can execute it
return true;
}
return true;
try{
$this->craftingTransaction->execute();
return true;
}catch(TransactionValidationException $e){
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
return false;
}finally{
$this->craftingTransaction = null;
}
}elseif($this->craftingTransaction !== null){
$this->server->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction from " . $this->getName() . ", refusing to execute crafting");
$this->craftingTransaction = null;
@ -2795,12 +2808,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case InteractPacket::ACTION_MOUSEOVER:
break; //TODO: handle these
case InteractPacket::ACTION_OPEN_INVENTORY:
if($target === $this){
if($target === $this && !array_key_exists($windowId = self::HARDCODED_INVENTORY_WINDOW_ID, $this->openHardcodedWindows)){
//TODO: HACK! this restores 1.14ish behaviour, but this should be able to be listened to and
//controlled by plugins. However, the player is always a subscriber to their own inventory so it
//doesn't integrate well with the regular container system right now.
$this->openHardcodedWindows[$windowId] = true;
$pk = new ContainerOpenPacket();
$pk->windowId = self::HARDCODED_INVENTORY_WINDOW_ID;
$pk->windowId = $windowId;
$pk->type = WindowTypes::INVENTORY;
$pk->x = $pk->y = $pk->z = 0;
$pk->entityUniqueId = $this->getId();
@ -3027,7 +3041,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->doCloseInventory();
if($packet->windowId >= self::RESERVED_WINDOW_ID_RANGE_START and $packet->windowId <= self::RESERVED_WINDOW_ID_RANGE_END){
if(array_key_exists($packet->windowId, $this->openHardcodedWindows)){
unset($this->openHardcodedWindows[$packet->windowId]);
$pk = new ContainerClosePacket();
$pk->windowId = $packet->windowId;
$this->sendDataPacket($pk);
@ -3911,6 +3926,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->resetFallDistance();
$this->nextChunkOrderRun = 0;
if($this->spawnChunkLoadCount !== -1){
$this->spawnChunkLoadCount = 0;
}
$this->stopSleep();
//TODO: workaround for player last pos not getting updated

View File

@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
const _VERSION_INFO_INCLUDED = true;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.14.0";
const BASE_VERSION = "3.14.3";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -28,6 +28,7 @@ use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\Player;
use function array_key_exists;
class CraftingTable extends Solid{
@ -53,15 +54,18 @@ class CraftingTable extends Solid{
if($player instanceof Player){
$player->setCraftingGrid(new CraftingGrid($player, CraftingGrid::SIZE_BIG));
//TODO: HACK! crafting grid doesn't fit very well into the current PM container system, so this hack allows
//it to carry on working approximately the same way as it did in 1.14
$pk = new ContainerOpenPacket();
$pk->windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID;
$pk->type = WindowTypes::WORKBENCH;
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
$pk->z = $this->getFloorZ();
$player->sendDataPacket($pk);
if(!array_key_exists($windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID, $player->openHardcodedWindows)){
//TODO: HACK! crafting grid doesn't fit very well into the current PM container system, so this hack allows
//it to carry on working approximately the same way as it did in 1.14
$pk = new ContainerOpenPacket();
$pk->windowId = $windowId;
$pk->type = WindowTypes::WORKBENCH;
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
$pk->z = $this->getFloorZ();
$player->sendDataPacket($pk);
$player->openHardcodedWindows[$windowId] = true;
}
}
return true;

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
@ -58,7 +59,7 @@ class Ladder extends Transparent{
}
public function onEntityCollide(Entity $entity) : void{
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
if($entity instanceof Living and $entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}

View File

@ -40,6 +40,6 @@ class Podzol extends Solid{
}
public function getHardness() : float{
return 2.5;
return 0.5;
}
}

View File

@ -30,7 +30,9 @@ use pocketmine\permission\BanEntry;
use function array_map;
use function count;
use function implode;
use function sort;
use function strtolower;
use const SORT_STRING;
class BanListCommand extends VanillaCommand{
@ -62,10 +64,11 @@ class BanListCommand extends VanillaCommand{
$args[0] = "players";
}
$list = $list->getEntries();
$message = implode(", ", array_map(function(BanEntry $entry) : string{
$list = array_map(function(BanEntry $entry) : string{
return $entry->getName();
}, $list));
}, $list->getEntries());
sort($list, SORT_STRING);
$message = implode(", ", $list);
if($args[0] === "ips"){
$sender->sendMessage(new TranslationContainer("commands.banlist.ips", [count($list)]));

View File

@ -30,6 +30,8 @@ use function array_filter;
use function array_map;
use function count;
use function implode;
use function sort;
use const SORT_STRING;
class ListCommand extends VanillaCommand{
@ -52,6 +54,7 @@ class ListCommand extends VanillaCommand{
}, array_filter($sender->getServer()->getOnlinePlayers(), function(Player $player) use ($sender) : bool{
return $player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player));
}));
sort($playerNames, SORT_STRING);
$sender->sendMessage(new TranslationContainer("commands.players.list", [count($playerNames), $sender->getServer()->getMaxPlayers()]));
$sender->sendMessage(implode(", ", $playerNames));

View File

@ -30,6 +30,8 @@ use pocketmine\utils\TextFormat;
use function array_map;
use function count;
use function implode;
use function sort;
use const SORT_STRING;
class PluginsCommand extends VanillaCommand{
@ -51,6 +53,7 @@ class PluginsCommand extends VanillaCommand{
$list = array_map(function(Plugin $plugin) : string{
return ($plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED) . $plugin->getDescription()->getFullName();
}, $sender->getServer()->getPluginManager()->getPlugins());
sort($list, SORT_STRING);
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($list), implode(TextFormat::WHITE . ", ", $list)]));
return true;

View File

@ -31,7 +31,9 @@ use pocketmine\Player;
use pocketmine\utils\TextFormat;
use function count;
use function implode;
use function sort;
use function strtolower;
use const SORT_STRING;
class WhitelistCommand extends VanillaCommand{
@ -71,7 +73,8 @@ class WhitelistCommand extends VanillaCommand{
return true;
case "list":
$entries = $sender->getServer()->getWhitelisted()->getAll(true);
$result = implode($entries, ", ");
sort($entries, SORT_STRING);
$result = implode(", ", $entries);
$count = count($entries);
$sender->sendMessage(new TranslationContainer("commands.whitelist.list", [$count, $count]));

View File

@ -45,6 +45,7 @@ class Attribute{
public const FALL_DAMAGE = 13;
public const HORSE_JUMP_STRENGTH = 14;
public const ZOMBIE_SPAWN_REINFORCEMENTS = 15;
public const LAVA_MOVEMENT = 16;
/** @var int */
private $id;
@ -84,6 +85,7 @@ class Attribute{
self::addAttribute(self::FALL_DAMAGE, "minecraft:fall_damage", 0.0, 340282346638528859811704183484516925440.0, 1.0);
self::addAttribute(self::HORSE_JUMP_STRENGTH, "minecraft:horse.jump_strength", 0.0, 2.0, 0.7);
self::addAttribute(self::ZOMBIE_SPAWN_REINFORCEMENTS, "minecraft:zombie.spawn_reinforcements", 0.0, 1.0, 0.0);
self::addAttribute(self::LAVA_MOVEMENT, "minecraft:lava_movement", 0.0, 340282346638528859811704183484516925440.0, 0.02);
}
/**

View File

@ -868,7 +868,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
*/
public function getSaveId() : string{
if(!isset(self::$saveNames[static::class])){
throw new \InvalidStateException("Entity is not registered");
throw new \InvalidStateException("Entity " . static::class . " is not registered");
}
reset(self::$saveNames[static::class]);
return current(self::$saveNames[static::class]);

View File

@ -26,6 +26,7 @@ namespace pocketmine\event\block;
use pocketmine\block\Block;
use pocketmine\event\Cancellable;
use pocketmine\Player;
use pocketmine\utils\Utils;
use function count;
/**
@ -79,6 +80,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
if(count($lines) !== 4){
throw new \InvalidArgumentException("Array size must be 4!");
}
Utils::validateArrayValueType($lines, function(string $_) : void{});
$this->lines = $lines;
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\utils\Utils;
/**
* @phpstan-extends EntityEvent<Living>
@ -62,6 +63,7 @@ class EntityDeathEvent extends EntityEvent{
* @param Item[] $drops
*/
public function setDrops(array $drops) : void{
Utils::validateArrayValueType($drops, function(Item $_) : void{});
$this->drops = $drops;
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\level\Position;
use pocketmine\utils\Utils;
/**
* Called when a entity explodes
@ -67,6 +68,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
* @param Block[] $blocks
*/
public function setBlockList(array $blocks) : void{
Utils::validateArrayValueType($blocks, function(Block $_) : void{});
$this->blocks = $blocks;
}

View File

@ -28,6 +28,7 @@ use pocketmine\event\Cancellable;
use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\utils\Utils;
use function spl_object_id;
/**
@ -97,6 +98,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
* @param CommandSender[] $recipients
*/
public function setRecipients(array $recipients) : void{
Utils::validateArrayValueType($recipients, function(CommandSender $_) : void{});
$this->recipients = $recipients;
}
}

View File

@ -43,6 +43,9 @@ class PlayerRespawnEvent extends PlayerEvent{
}
public function setRespawnPosition(Position $position) : void{
if(!$position->isValid()){
throw new \InvalidArgumentException("Spawn position must reference a valid and loaded World");
}
$this->position = $position;
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use function chr;
use function count;
use function str_replace;
@ -145,6 +146,7 @@ class QueryRegenerateEvent extends ServerEvent{
* @param Plugin[] $plugins
*/
public function setPlugins(array $plugins) : void{
Utils::validateArrayValueType($plugins, function(Plugin $_) : void{});
$this->plugins = $plugins;
$this->destroyCache();
}
@ -160,6 +162,7 @@ class QueryRegenerateEvent extends ServerEvent{
* @param Player[] $players
*/
public function setPlayerList(array $players) : void{
Utils::validateArrayValueType($players, function(Player $_) : void{});
$this->players = $players;
$this->destroyCache();
}

View File

@ -28,8 +28,10 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use function array_map;
use function file_get_contents;
use function is_array;
use function json_decode;
use function json_encode;
use function usort;
@ -52,41 +54,39 @@ class CraftingManager{
public function init() : void{
$recipes = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla" . DIRECTORY_SEPARATOR . "recipes.json"), true);
if(!is_array($recipes)){
throw new AssumptionFailedError("recipes.json root should contain a map of recipe types");
}
$itemDeserializerFunc = \Closure::fromCallable([Item::class, 'jsonDeserialize']);
foreach($recipes as $recipe){
switch($recipe["type"]){
case "shapeless":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerShapelessRecipe(new ShapelessRecipe(
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "shaped":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerShapedRecipe(new ShapedRecipe(
$recipe["shape"],
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "smelting":
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerFurnaceRecipe(new FurnaceRecipe(
Item::jsonDeserialize($recipe["output"]),
Item::jsonDeserialize($recipe["input"]))
);
break;
default:
break;
foreach($recipes["shapeless"] as $recipe){
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerShapelessRecipe(new ShapelessRecipe(
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
}
foreach($recipes["shaped"] as $recipe){
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerShapedRecipe(new ShapedRecipe(
$recipe["shape"],
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
}
foreach($recipes["smelting"] as $recipe){
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerFurnaceRecipe(new FurnaceRecipe(
Item::jsonDeserialize($recipe["output"]),
Item::jsonDeserialize($recipe["input"]))
);
}
$this->buildCraftingDataCache();

View File

@ -136,6 +136,8 @@ class InventoryTransaction{
* @throws TransactionValidationException
*/
protected function matchItems(array &$needItems, array &$haveItems) : void{
$needItems = [];
$haveItems = [];
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();

View File

@ -30,7 +30,7 @@ use pocketmine\network\mcpe\NetworkSession;
class EmotePacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::EMOTE_PACKET;
private const FLAG_SERVER = 1 << 0;
public const FLAG_SERVER = 1 << 0;
/** @var int */
private $entityRuntimeId;

View File

@ -129,6 +129,8 @@ class InventoryTransactionPacket extends DataPacket{
$this->putUnsignedVarInt($this->transactionType);
$this->putBool($this->hasItemStackIds);
$this->putUnsignedVarInt(count($this->actions));
foreach($this->actions as $action){
$action->write($this, $this->hasItemStackIds);

View File

@ -78,7 +78,7 @@ class LoginPacket extends DataPacket{
}
public function mayHaveUnreadBytes() : bool{
return $this->protocol !== null and $this->protocol !== ProtocolInfo::CURRENT_PROTOCOL;
return $this->protocol !== ProtocolInfo::CURRENT_PROTOCOL;
}
protected function decodePayload(){
@ -92,7 +92,7 @@ class LoginPacket extends DataPacket{
}
$logger = MainLogger::getLogger();
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version " . ($this->protocol ?? "unknown") . "): " . $e->getMessage());
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version $this->protocol): " . $e->getMessage());
foreach(Utils::printableTrace($e->getTrace()) as $line){
$logger->debug($line);
}

View File

@ -128,8 +128,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
}
protected function decodePayload() : void{
$this->yaw = $this->getLFloat();
$this->pitch = $this->getLFloat();
$this->yaw = $this->getLFloat();
$this->position = $this->getVector3();
$this->moveVecX = $this->getLFloat();
$this->moveVecZ = $this->getLFloat();
@ -143,8 +143,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
}
protected function encodePayload() : void{
$this->putLFloat($this->yaw);
$this->putLFloat($this->pitch);
$this->putLFloat($this->yaw);
$this->putVector3($this->position);
$this->putLFloat($this->moveVecX);
$this->putLFloat($this->moveVecZ);

View File

@ -30,7 +30,9 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\Player;
use function array_key_exists;
class NetworkInventoryAction{
public const SOURCE_CONTAINER = 0;
@ -162,21 +164,21 @@ class NetworkInventoryAction{
switch($this->sourceType){
case self::SOURCE_CONTAINER:
if($this->windowId === ContainerIds::UI and $this->inventorySlot > 0){
if($this->inventorySlot === 50){
if($this->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
return null; //useless noise
}
if($this->inventorySlot >= 28 and $this->inventorySlot <= 31){
if(array_key_exists($this->inventorySlot, UIInventorySlotOffset::CRAFTING2X2_INPUT)){
$window = $player->getCraftingGrid();
if($window->getGridWidth() !== CraftingGrid::SIZE_SMALL){
throw new \UnexpectedValueException("Expected small crafting grid");
}
$slot = $this->inventorySlot - 28;
}elseif($this->inventorySlot >= 32 and $this->inventorySlot <= 40){
$slot = UIInventorySlotOffset::CRAFTING2X2_INPUT[$this->inventorySlot];
}elseif(array_key_exists($this->inventorySlot, UIInventorySlotOffset::CRAFTING3X3_INPUT)){
$window = $player->getCraftingGrid();
if($window->getGridWidth() !== CraftingGrid::SIZE_BIG){
throw new \UnexpectedValueException("Expected big crafting grid");
}
$slot = $this->inventorySlot - 32;
$slot = UIInventorySlotOffset::CRAFTING3X3_INPUT[$this->inventorySlot];
}else{
throw new \UnexpectedValueException("Unhandled magic UI slot offset $this->inventorySlot");
}

View File

@ -70,7 +70,7 @@ class SkinData{
* @param PersonaSkinPiece[] $personaPieces
* @param PersonaPieceTintColor[] $pieceTintColors
*/
public function __construct(string $skinId, string $resourcePatch, SkinImage $skinImage, array $animations = [], SkinImage $capeImage = null, string $geometryData = "", string $animationData = "", bool $premium = false, bool $persona = false, bool $personaCapeOnClassic = false, string $capeId = "", ?string $fullSkinId = null, string $armSize = self::ARM_SIZE_WIDE, string $skinColor = "", array $personaPieces = [], array $pieceTintColors = [], bool $isVerified = false){
public function __construct(string $skinId, string $resourcePatch, SkinImage $skinImage, array $animations = [], SkinImage $capeImage = null, string $geometryData = "", string $animationData = "", bool $premium = false, bool $persona = false, bool $personaCapeOnClassic = false, string $capeId = "", ?string $fullSkinId = null, string $armSize = self::ARM_SIZE_WIDE, string $skinColor = "", array $personaPieces = [], array $pieceTintColors = [], bool $isVerified = true){
$this->skinId = $skinId;
$this->resourcePatch = $resourcePatch;
$this->skinImage = $skinImage;

View File

@ -0,0 +1,105 @@
<?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\network\mcpe\protocol\types\inventory;
final class UIInventorySlotOffset{
private function __construct(){
//NOOP
}
public const CURSOR = 0;
public const ANVIL = [
1 => 0,
2 => 1,
];
public const STONE_CUTTER_INPUT = 3;
public const TRADE2_INGREDIENT = [
4 => 0,
5 => 1,
];
public const TRADE_INGREDIENT = [
6 => 0,
7 => 1,
];
public const MATERIAL_REDUCER_INPUT = 8;
public const LOOM = [
9 => 0,
10 => 1,
11 => 2,
];
public const CARTOGRAPHY_TABLE = [
12 => 0,
13 => 1,
];
public const ENCHANTING_TABLE = [
14 => 0,
15 => 1,
];
public const GRINDSTONE = [
16 => 0,
17 => 1,
];
public const COMPOUND_CREATOR_INPUT = [
18 => 0,
19 => 1,
20 => 2,
21 => 3,
22 => 4,
23 => 5,
24 => 6,
25 => 7,
26 => 8,
];
public const BEACON_PAYMENT = 27;
public const CRAFTING2X2_INPUT = [
28 => 0,
29 => 1,
30 => 2,
31 => 3,
];
public const CRAFTING3X3_INPUT = [
32 => 0,
33 => 1,
34 => 2,
35 => 3,
36 => 4,
37 => 5,
38 => 6,
39 => 7,
40 => 8,
];
public const MATERIAL_REDUCER_OUTPUT = [
41 => 0,
42 => 1,
43 => 2,
44 => 3,
45 => 4,
46 => 5,
47 => 6,
48 => 7,
49 => 8,
];
public const CREATED_ITEM_OUTPUT = 50;
}

View File

@ -134,7 +134,7 @@ class SendUsageTask extends AsyncTask{
$data["players"] = [
"count" => count($players),
"limit" => $server->getMaxPlayers(),
"currentList" => $players,
"currentList" => array_values($players),
"historyList" => array_values($playerList)
];

View File

@ -55,6 +55,9 @@ class TaskHandler{
private $ownerName;
public function __construct(Task $task, int $taskId, int $delay = -1, int $period = -1, ?string $ownerName = null){
if($task->getHandler() !== null){
throw new \InvalidArgumentException("Cannot assign multiple handlers to the same task");
}
$this->task = $task;
$this->taskId = $taskId;
$this->delay = $delay;

View File

@ -149,7 +149,7 @@ abstract class Timings{
}
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
$name = "Task: " . ($task->getOwnerName() ?? "Unknown") . " Runnable: " . $task->getTaskName();
$name = "Task: " . $task->getOwnerName() . " Runnable: " . $task->getTaskName();
if($period > 0){
$name .= "(interval:" . $period . ")";

View File

@ -201,12 +201,12 @@ class MainLogger extends \AttachableThreadedLogger{
$this->synchronized(function() use ($e, $trace) : void{
$this->critical(self::printExceptionMessage($e));
foreach(Utils::printableTrace($trace) as $line){
$this->debug($line, true);
$this->critical($line);
}
for($prev = $e->getPrevious(); $prev !== null; $prev = $prev->getPrevious()){
$this->debug("Previous: " . self::printExceptionMessage($prev), true);
$this->critical("Previous: " . self::printExceptionMessage($prev));
foreach(Utils::printableTrace($prev->getTrace()) as $line){
$this->debug(" " . $line, true);
$this->critical(" " . $line);
}
}
});

View File

@ -685,6 +685,21 @@ class Utils{
}
}
/**
* @phpstan-template TMemberType
* @phpstan-param array<mixed, TMemberType> $array
* @phpstan-param \Closure(TMemberType) : void $validator
*/
public static function validateArrayValueType(array $array, \Closure $validator) : void{
foreach($array as $k => $v){
try{
$validator($v);
}catch(\TypeError $e){
throw new \TypeError("Incorrect type of element at \"$k\": " . $e->getMessage(), 0, $e);
}
}
}
public static function recursiveUnlink(string $dir) : void{
if(is_dir($dir)){
$objects = scandir($dir, SCANDIR_SORT_NONE);

View File

@ -52,7 +52,7 @@ class VersionString{
$this->development = $isDevBuild;
$this->build = $buildNumber;
preg_match('/(\d+)\.(\d+)\.(\d+)(?:-(.*))?$/', $this->baseVersion, $matches);
preg_match('/^(\d+)\.(\d+)\.(\d+)(?:-(.*))?$/', $this->baseVersion, $matches);
if(count($matches) < 4){
throw new \InvalidArgumentException("Invalid base version \"$baseVersion\", should contain at least 3 version digits");
}

View File

@ -205,11 +205,6 @@ parameters:
count: 1
path: ../../../src/pocketmine/event/EventPriority.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: ../../../src/pocketmine/inventory/CraftingManager.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1

View File

@ -35,6 +35,11 @@ parameters:
count: 1
path: ../../../src/pocketmine/utils/Utils.php
-
message: "#^Parameter \\#1 \\$ of closure expects TMemberType, TMemberType given\\.$#"
count: 1
path: ../../../src/pocketmine/utils/Utils.php
-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNotNull\\(\\) with int and string will always evaluate to true\\.$#"
count: 1

View File

@ -38,7 +38,7 @@ class CheckTestCompletionTask extends Task{
$test = $this->plugin->getCurrentTest();
if($test === null){
if(!$this->plugin->startNextTest()){
$this->plugin->getScheduler()->cancelTask($this->getHandler()->getTaskId());
$this->getHandler()->cancel();
$this->plugin->onAllTestsCompleted();
}
}elseif($test->isFinished() or $test->isTimedOut()){

View File

@ -17,7 +17,7 @@ PLUGINS_DIR="$DATA_DIR/plugins"
rm -rf "$DATA_DIR"
rm PocketMine-MP.phar 2> /dev/null
cd tests/plugins/PocketMine-DevTools
cd tests/plugins/DevTools
php -dphar.readonly=0 ./src/DevTools/ConsoleScript.php --make ./ --relative ./ --out ../../../DevTools.phar
cd ../../..