Merge branch 'stable' into next-minor

# Conflicts:
#	build/preprocessor
This commit is contained in:
Dylan K. Taylor 2019-06-16 14:08:06 +01:00
commit ac5339414a
47 changed files with 267 additions and 102 deletions

8
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: pocketminemp
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom: https://github.com/pmmp/PocketMine-MP#donate

2
.gitmodules vendored
View File

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

View File

@ -8,6 +8,9 @@ __A highly customisable, open source server software for Minecraft: Bedrock Edit
Head over to the [documentation site](http://pmmp.readthedocs.org/).
If you don't find what you're looking for there, [talk to a human](#discussion). Please do not use our issue tracker for support requests.
### Docker
We provide an official docker image on Docker Hub: [`pmmp/pocketmine-mp`](https://hub.docker.com/r/pmmp/pocketmine-mp).
### Discussion
- [Forums](https://forums.pmmp.io/)
- [Community Discord](https://discord.gg/bge7dYQ)

1
build/preprocessor Submodule

@ -0,0 +1 @@
Subproject commit 63e0092d623d13e47f9083b3d65fdf431933a471

View File

@ -1086,7 +1086,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->noDamageTicks = 60;
foreach($this->usedChunks as $index => $c){
foreach($this->usedChunks as $index => $hasSent){
if(!$hasSent){
continue; //this will happen when the chunk is ready to send
}
Level::getXZ($index, $chunkX, $chunkZ);
foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){
if($entity !== $this and !$entity->isClosed() and $entity->isAlive() and !$entity->isFlaggedForDespawn()){
@ -3343,7 +3346,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
/**
* Resets the title duration settings.
* Resets the title duration settings to defaults and removes any existing titles.
*/
public function resetTitles(){
$pk = new SetTitlePacket();

View File

@ -187,12 +187,20 @@ namespace pocketmine {
}
define('pocketmine\LOCK_FILE_PATH', \pocketmine\DATA . 'server.lock');
define('pocketmine\LOCK_FILE', fopen(\pocketmine\LOCK_FILE_PATH, "cb"));
define('pocketmine\LOCK_FILE', fopen(\pocketmine\LOCK_FILE_PATH, "a+b"));
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
critical_error("Another " . \pocketmine\NAME . " instance is already using this folder (" . realpath(\pocketmine\DATA) . ").");
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
//other server wrote its PID and released exclusive lock before we get our lock
flock(\pocketmine\LOCK_FILE, LOCK_SH);
$pid = stream_get_contents(\pocketmine\LOCK_FILE);
critical_error("Another " . \pocketmine\NAME . " instance (PID $pid) is already using this folder (" . realpath(\pocketmine\DATA) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
ftruncate(\pocketmine\LOCK_FILE, 0);
fwrite(\pocketmine\LOCK_FILE, (string) getmypid());
fflush(\pocketmine\LOCK_FILE);
flock(\pocketmine\LOCK_FILE, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading
//Logger has a dependency on timezone
$tzError = Timezone::init();

View File

@ -112,7 +112,6 @@ use function array_sum;
use function asort;
use function assert;
use function base64_encode;
use function bin2hex;
use function class_exists;
use function count;
use function define;
@ -799,6 +798,7 @@ class Server{
* @return bool
*/
public function hasOfflinePlayerData(string $name) : bool{
$name = strtolower($name);
return file_exists($this->getDataPath() . "players/$name.dat");
}
@ -892,6 +892,11 @@ class Server{
}
/**
* Returns an online player whose name begins with or equals the given string (case insensitive).
* The closest match will be returned, or null if there are no online matches.
*
* @see Server::getPlayerExact()
*
* @param string $name
*
* @return Player|null
@ -917,6 +922,8 @@ class Server{
}
/**
* Returns an online player with the given name (case insensitive), or null if not found.
*
* @param string $name
*
* @return Player|null
@ -933,6 +940,9 @@ class Server{
}
/**
* Returns a list of online players whose names contain with the given string (case insensitive).
* If an exact match is found, only that match is returned.
*
* @param string $partialName
*
* @return Player[]
@ -2271,6 +2281,7 @@ class Server{
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
$report = false;
}
}
@ -2526,7 +2537,7 @@ class Server{
if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){
$this->queryHandler->handle($interface, $address, $port, $payload);
}else{
$this->logger->debug("Unhandled raw packet from $address $port: " . bin2hex($payload));
$this->logger->debug("Unhandled raw packet from $address $port: " . base64_encode($payload));
}
}catch(\Throwable $e){
$this->logger->logException($e);

View File

@ -22,6 +22,6 @@
namespace pocketmine;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.8.3";
const BASE_VERSION = "3.8.5";
const IS_DEVELOPMENT_BUILD = true;
const BUILD_NUMBER = 0;

View File

@ -46,7 +46,7 @@ class Carpet extends Flowable{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Carpet";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Carpet";
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -35,7 +35,7 @@ class Concrete extends Solid{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete";
}
public function getHardness() : float{

View File

@ -34,7 +34,7 @@ class ConcretePowder extends Fallable{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete Powder";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete Powder";
}
public function getHardness() : float{

View File

@ -39,7 +39,7 @@ class DoublePlant extends Flowable{
}
public function canBeReplaced() : bool{
return $this->meta === 2 or $this->meta === 3; //grass or fern
return $this->getVariant() === 2 or $this->getVariant() === 3; //grass or fern
}
public function getName() : string{
@ -95,11 +95,11 @@ class DoublePlant extends Flowable{
}
public function getToolType() : int{
return ($this->meta === 2 or $this->meta === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
}
public function getToolHarvestLevel() : int{
return ($this->meta === 2 or $this->meta === 3) ? 1 : 0; //only grass or fern require shears
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? 1 : 0; //only grass or fern require shears
}
public function getDrops(Item $item) : array{

View File

@ -56,7 +56,7 @@ class Flower extends Flowable{
self::TYPE_PINK_TULIP => "Pink Tulip",
self::TYPE_OXEYE_DAISY => "Oxeye Daisy"
];
return $names[$this->meta] ?? "Unknown";
return $names[$this->getVariant()] ?? "Unknown";
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{

View File

@ -44,4 +44,8 @@ class IronTrapdoor extends Trapdoor{
public function getToolHarvestLevel() : int{
return TieredTool::TIER_WOODEN;
}
public function getFuelTime() : int{
return 0; //TODO: remove this hack on 4.0
}
}

View File

@ -188,7 +188,7 @@ class Leaves extends Transparent{
}
public function canDropApples() : bool{
return $this->meta === self::OAK;
return $this->getVariant() === self::OAK;
}
public function getFlameEncouragement() : int{

View File

@ -44,6 +44,6 @@ class Leaves2 extends Leaves{
}
public function canDropApples() : bool{
return $this->meta === self::DARK_OAK;
return $this->getVariant() === self::DARK_OAK;
}
}

View File

@ -55,7 +55,7 @@ class Quartz extends Solid{
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
if($this->meta !== self::NORMAL){
if($this->getVariant() !== self::NORMAL){
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
}
return $this->getLevel()->setBlock($blockReplace, $this, true, true);

View File

@ -40,7 +40,7 @@ class Sand extends Fallable{
}
public function getName() : string{
if($this->meta === 0x01){
if($this->getVariant() === 0x01){
return "Red Sand";
}

View File

@ -78,7 +78,7 @@ abstract class Slab extends Transparent{
}
}else{ //TODO: collision
if($blockReplace->getId() === $this->id){
if($blockReplace->getVariant() === $this->meta){
if($blockReplace->getVariant() === $this->getVariant()){
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
return true;

View File

@ -30,6 +30,6 @@ class StainedClay extends HardenedClay{
protected $id = self::STAINED_CLAY;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Clay";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Clay";
}
}

View File

@ -30,6 +30,6 @@ class StainedGlass extends Glass{
protected $id = self::STAINED_GLASS;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass";
}
}

View File

@ -30,6 +30,6 @@ class StainedGlassPane extends GlassPane{
protected $id = self::STAINED_GLASS_PANE;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass Pane";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass Pane";
}
}

View File

@ -37,6 +37,10 @@ class Stonecutter extends Solid{
return "Stonecutter";
}
public function getHardness() : float{
return 3.5;
}
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}

View File

@ -43,7 +43,7 @@ class Wool extends Solid{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Wool";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Wool";
}
public function getBreakTime(Item $item) : float{

View File

@ -81,7 +81,7 @@ class TimeCommand extends VanillaCommand{
}else{
$level = $sender->getServer()->getDefaultLevel();
}
$sender->sendMessage(new TranslationContainer("commands.time.query", [$level->getTime()]));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString("commands.time.query", [$level->getTime()]));
return true;
}

View File

@ -40,6 +40,11 @@ class Attribute{
public const ATTACK_DAMAGE = 8;
public const EXPERIENCE_LEVEL = 9;
public const EXPERIENCE = 10;
public const UNDERWATER_MOVEMENT = 11;
public const LUCK = 12;
public const FALL_DAMAGE = 13;
public const HORSE_JUMP_STRENGTH = 14;
public const ZOMBIE_SPAWN_REINFORCEMENTS = 15;
private $id;
protected $minValue;
@ -66,8 +71,11 @@ class Attribute{
self::addAttribute(self::ATTACK_DAMAGE, "minecraft:attack_damage", 0.00, 340282346638528859811704183484516925440.00, 1.00, false);
self::addAttribute(self::EXPERIENCE_LEVEL, "minecraft:player.level", 0.00, 24791.00, 0.00);
self::addAttribute(self::EXPERIENCE, "minecraft:player.experience", 0.00, 1.00, 0.00);
//TODO: minecraft:luck (for fishing?)
//TODO: minecraft:fall_damage
self::addAttribute(self::UNDERWATER_MOVEMENT, "minecraft:underwater_movement", 0.0, 340282346638528859811704183484516925440.0, 0.02);
self::addAttribute(self::LUCK, "minecraft:luck", -1024.0, 1024.0, 0.0);
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);
}
/**

View File

@ -562,6 +562,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var bool */
protected $constructed = false;
/** @var bool */
private $closeInFlight = false;
public function __construct(Level $level, CompoundTag $nbt){
$this->constructed = true;
@ -2109,7 +2111,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* WARNING: Entities are unusable after this has been executed!
*/
public function close() : void{
if($this->closeInFlight){
return;
}
if(!$this->closed){
$this->closeInFlight = true;
(new EntityDespawnEvent($this))->call();
$this->closed = true;
@ -2128,6 +2135,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->namedtag = null;
$this->lastDamageCause = null;
$this->closeInFlight = false;
}
}

View File

@ -60,6 +60,7 @@ use function array_merge;
use function array_rand;
use function array_values;
use function ceil;
use function in_array;
use function max;
use function min;
use function mt_rand;
@ -98,18 +99,36 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
public function __construct(Level $level, CompoundTag $nbt){
if($this->skin === null){
$skinTag = $nbt->getCompoundTag("Skin");
if($skinTag === null or !self::isValidSkin($skinTag->hasTag("Data", ByteArrayTag::class) ?
$skinTag->getByteArray("Data") :
$skinTag->getString("Data", "")
)){
if($skinTag === null){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
$this->skin = self::deserializeSkinNBT($skinTag); //this throws if the skin is invalid
}
parent::__construct($level, $nbt);
}
/**
* @param CompoundTag $skinTag
*
* @return Skin
* @throws \InvalidArgumentException
*/
protected static function deserializeSkinNBT(CompoundTag $skinTag) : Skin{
$skin = new Skin(
$skinTag->getString("Name"),
$skinTag->hasTag("Data", StringTag::class) ? $skinTag->getString("Data") : $skinTag->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
$skinTag->getByteArray("CapeData", ""),
$skinTag->getString("GeometryName", ""),
$skinTag->getByteArray("GeometryData", "")
);
$skin->validate();
return $skin;
}
/**
* @deprecated
*
* Checks the length of a supplied skin bitmap and returns whether the length is valid.
*
* @param string $skin
@ -117,7 +136,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @return bool
*/
public static function isValidSkin(string $skin) : bool{
return strlen($skin) === 64 * 64 * 4 or strlen($skin) === 64 * 32 * 4 or strlen($skin) === 128 * 128 * 4;
return in_array(strlen($skin), Skin::ACCEPTED_SKIN_SIZES, true);
}
/**
@ -149,10 +168,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @param Skin $skin
*/
public function setSkin(Skin $skin) : void{
if(!$skin->isValid()){
throw new \InvalidStateException("Specified skin is not valid, must be 8KiB or 16KiB");
}
$skin->validate();
$this->skin = $skin;
$this->skin->debloatGeometryData();
}
@ -587,17 +603,6 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$this->setNameTag($this->namedtag->getString("NameTag"));
}
$skin = $this->namedtag->getCompoundTag("Skin");
if($skin !== null){
$this->setSkin(new Skin(
$skin->getString("Name"),
$skin->hasTag("Data", StringTag::class) ? $skin->getString("Data") : $skin->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
$skin->getByteArray("CapeData", ""),
$skin->getString("GeometryName", ""),
$skin->getByteArray("GeometryData", "")
));
}
$this->uuid = UUID::fromData((string) $this->getId(), $this->skin->getSkinData(), $this->getNameTag());
}
@ -836,9 +841,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
protected function sendSpawnPacket(Player $player) : void{
if(!$this->skin->isValid()){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
$this->skin->validate();
if(!($this instanceof Player)){
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */

View File

@ -23,11 +23,18 @@ declare(strict_types=1);
namespace pocketmine\entity;
use function implode;
use function in_array;
use function json_decode;
use function json_encode;
use function strlen;
class Skin{
public const ACCEPTED_SKIN_SIZES = [
64 * 32 * 4,
64 * 64 * 4,
128 * 128 * 4
];
/** @var string */
private $skinId;
@ -48,12 +55,34 @@ class Skin{
$this->geometryData = $geometryData;
}
/**
* @deprecated
* @return bool
*/
public function isValid() : bool{
return (
$this->skinId !== "" and
(($s = strlen($this->skinData)) === 16384 or $s === 8192 or $s === 65536) and
($this->capeData === "" or strlen($this->capeData) === 8192)
);
try{
$this->validate();
return true;
}catch(\InvalidArgumentException $e){
return false;
}
}
/**
* @throws \InvalidArgumentException
*/
public function validate() : void{
if($this->skinId === ""){
throw new \InvalidArgumentException("Skin ID must not be empty");
}
$len = strlen($this->skinData);
if(!in_array($len, self::ACCEPTED_SKIN_SIZES, true)){
throw new \InvalidArgumentException("Invalid skin data size $len bytes (allowed sizes: " . implode(", ", self::ACCEPTED_SKIN_SIZES) . ")");
}
if($this->capeData !== "" and strlen($this->capeData) !== 8192){
throw new \InvalidArgumentException("Invalid cape data size " . strlen($this->capeData) . " bytes (must be exactly 8192 bytes)");
}
//TODO: validate geometry
}
/**

View File

@ -67,10 +67,7 @@ class PlayerChangeSkinEvent extends PlayerEvent implements Cancellable{
* @throws \InvalidArgumentException if the specified skin is not valid
*/
public function setNewSkin(Skin $skin) : void{
if(!$skin->isValid()){
throw new \InvalidArgumentException("Skin format is invalid");
}
$skin->validate();
$this->newSkin = $skin;
}
}

View File

@ -94,11 +94,12 @@ class ArmorInventory extends BaseInventory{
$target = [$target];
}
$armor = $this->getContents(true);
$pk = new MobArmorEquipmentPacket();
$pk->entityRuntimeId = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->head = $this->getHelmet();
$pk->chest = $this->getChestplate();
$pk->legs = $this->getLeggings();
$pk->feet = $this->getBoots();
$pk->encode();
foreach($target as $player){
@ -121,18 +122,19 @@ class ArmorInventory extends BaseInventory{
$target = [$target];
}
$armor = $this->getContents(true);
$pk = new MobArmorEquipmentPacket();
$pk->entityRuntimeId = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->head = $this->getHelmet();
$pk->chest = $this->getChestplate();
$pk->legs = $this->getLeggings();
$pk->feet = $this->getBoots();
$pk->encode();
foreach($target as $player){
if($player === $this->getHolder()){
$pk2 = new InventoryContentPacket();
$pk2->windowId = $player->getWindowId($this);
$pk2->items = $armor;
$pk2->items = $this->getContents(true);
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);

View File

@ -53,6 +53,7 @@ class CraftingManager{
public function init() : void{
$recipes = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla" . DIRECTORY_SEPARATOR . "recipes.json"), true);
$itemDeserializerFunc = \Closure::fromCallable([Item::class, 'jsonDeserialize']);
foreach($recipes as $recipe){
switch($recipe["type"]){
case "shapeless":
@ -60,8 +61,8 @@ class CraftingManager{
break;
}
$this->registerRecipe(new ShapelessRecipe(
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "shaped":
@ -70,8 +71,8 @@ class CraftingManager{
}
$this->registerRecipe(new ShapedRecipe(
$recipe["shape"],
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "smelting":

View File

@ -28,12 +28,16 @@ use pocketmine\utils\UUID;
class MultiRecipe{
public const TYPE_REPAIR_ITEM = "00000000-0000-0000-0000-000000000001";
public const TYPE_MAP_EXTENDING = "D392B075-4BA1-40AE-8789-AF868D56F6CE";
public const TYPE_MAP_EXTENDING_CARTOGRAPHY = "8B36268C-1829-483C-A0F1-993B7156A8F2";
public const TYPE_MAP_CLONING = "85939755-BA10-4D9D-A4CC-EFB7A8E943C4";
public const TYPE_MAP_CLONING_CARTOGRAPHY = "442D85ED-8272-4543-A6F1-418F90DED05D";
public const TYPE_MAP_UPGRADING = "AECD2294-4B94-434B-8667-4499BB2C9327";
public const TYPE_MAP_UPGRADING_CARTOGRAPHY = "98C84B38-1085-46BD-B1CE-DD38C159E6CC";
public const TYPE_BOOK_CLONING = "D1CA6B84-338E-4F2F-9C6B-76CC8B4BD98D";
public const TYPE_BANNER_DUPLICATE = "B5C5D105-75A2-4076-AF2B-923EA2BF4BF0";
public const TYPE_BANNER_ADD_PATTERN = "D81AAEAF-E172-4440-9225-868DF030D27B";
public const TYPE_FIREWORKS = "00000000-0000-0000-0000-000000000002";
public const TYPE_MAP_LOCKING_CARTOGRAPHY = "602234E4-CAC1-4353-8BB7-B1EBFF70024B";
private $uuid;

View File

@ -32,6 +32,21 @@ use function array_pop;
use function count;
use function intdiv;
/**
* This transaction type is specialized for crafting validation. It shares most of the same semantics of the base
* inventory transaction type, but the requirement for validity is slightly different.
*
* It is expected that the actions in this transaction type will produce an **unbalanced result**, i.e. some inputs won't
* have corresponding outputs, and vice versa. The reason why is because the unmatched inputs are recipe inputs, and
* the unmatched outputs are recipe results.
*
* Therefore, the validity requirement is that the imbalance of the transaction should match the expected inputs and
* outputs of a registered crafting recipe.
*
* This transaction allows multiple repetitions of the same recipe to be crafted in a single batch. In the case of batch
* crafting, the number of unmatched inputs and outputs must be exactly divisible by the expected recipe ingredients and
* results, with no remainder. Any leftovers are expected to be emitted back to the crafting grid.
*/
class CraftingTransaction extends InventoryTransaction{
/** @var CraftingRecipe|null */
protected $recipe;

View File

@ -29,14 +29,28 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\Player;
use function array_keys;
use function assert;
use function count;
use function get_class;
use function min;
use function shuffle;
use function spl_object_hash;
/**
* This InventoryTransaction only allows doing Transaction between one / two inventories
* This is the basic type for an inventory transaction. This is used for moving items between inventories, dropping
* items and more. It allows transactions with multiple inputs and outputs.
*
* Validation **does not** depend on ordering. This means that the actions can appear in any order and still be valid.
* The only validity requirement for this transaction type is that the balance of items must add up to zero. This means:
* - No new outputs without matching input amounts
* - No inputs without matching output amounts
* - No userdata changes (item state, NBT, etc)
*
* A transaction is composed of "actions", which are pairs of inputs and outputs which target a specific itemstack in
* a specific location. There are multiple types of inventory actions which might be involved in a transaction.
*
* @see InventoryAction
*/
class InventoryTransaction{
protected $hasExecuted = false;
@ -75,6 +89,11 @@ class InventoryTransaction{
}
/**
* Returns an **unordered** set of actions involved in this transaction.
*
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
* significance and should not be relied on.
*
* @return InventoryAction[]
*/
public function getActions() : array{
@ -93,6 +112,19 @@ class InventoryTransaction{
}
}
/**
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
*/
private function shuffleActions() : void{
$keys = array_keys($this->actions);
shuffle($keys);
$actions = [];
foreach($keys as $key){
$actions[$key] = $this->actions[$key];
}
$this->actions = $actions;
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
@ -271,6 +303,8 @@ class InventoryTransaction{
return false;
}
$this->shuffleActions();
$this->validate();
if(!$this->callExecuteEvent()){

View File

@ -46,7 +46,6 @@ use pocketmine\utils\Binary;
use function array_map;
use function base64_decode;
use function base64_encode;
use function bin2hex;
use function file_get_contents;
use function get_class;
use function hex2bin;
@ -874,7 +873,7 @@ class Item implements ItemIds, \JsonSerializable{
* @return string
*/
final public function __toString() : string{
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:" . base64_encode($this->getCompoundTag()) : "");
}
/**

View File

@ -68,6 +68,7 @@ use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\timings\Timings;
use function base64_encode;
use function bin2hex;
use function implode;
use function json_decode;
@ -107,7 +108,7 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
$ev = new DataPacketReceiveEvent($this->player, $packet);
$ev->call();
if(!$ev->isCancelled() and !$packet->handle($this)){
$this->server->getLogger()->debug("Unhandled " . $packet->getName() . " received from " . $this->player->getName() . ": 0x" . bin2hex($packet->buffer));
$this->server->getLogger()->debug("Unhandled " . $packet->getName() . " received from " . $this->player->getName() . ": " . base64_encode($packet->buffer));
}
$timings->stopTiming();

View File

@ -40,7 +40,7 @@ use raklib\server\ServerHandler;
use raklib\server\ServerInstance;
use raklib\utils\InternetAddress;
use function addcslashes;
use function bin2hex;
use function base64_encode;
use function get_class;
use function implode;
use function rtrim;
@ -170,7 +170,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
}
}catch(\Throwable $e){
$logger = $this->server->getLogger();
$logger->debug("Packet " . (isset($pk) ? get_class($pk) : "unknown") . " 0x" . bin2hex($packet->buffer));
$logger->debug("Packet " . (isset($pk) ? get_class($pk) : "unknown") . ": " . base64_encode($packet->buffer));
$logger->logException($e);
$player->close($player->getLeaveMessage(), "Internal server error");

View File

@ -34,7 +34,7 @@ class ContainerSetDataPacket extends DataPacket{
public const PROPERTY_FURNACE_TICK_COUNT = 0;
public const PROPERTY_FURNACE_LIT_TIME = 1;
public const PROPERTY_FURNACE_LIT_DURATION = 2;
//TODO: check property 3
public const PROPERTY_FURNACE_STORED_XP = 3;
public const PROPERTY_FURNACE_FUEL_AUX = 4;
public const PROPERTY_BREWING_STAND_BREW_TIME = 0;

View File

@ -90,6 +90,8 @@ class LevelEventPacket extends DataPacket{
public const EVENT_STOP_RAIN = 3003;
public const EVENT_STOP_THUNDER = 3004;
public const EVENT_PAUSE_GAME = 3005; //data: 1 to pause, 0 to resume
public const EVENT_PAUSE_GAME_NO_SCREEN = 3006; //data: 1 to pause, 0 to resume - same effect as normal pause but without screen
public const EVENT_SET_GAME_SPEED = 3007; //x coordinate of pos = scale factor (default 1.0)
public const EVENT_REDSTONE_TRIGGER = 3500;
public const EVENT_CAULDRON_EXPLODE = 3501;

View File

@ -34,21 +34,32 @@ class MobArmorEquipmentPacket extends DataPacket{
/** @var int */
public $entityRuntimeId;
/** @var Item[] */
public $slots = [];
//this intentionally doesn't use an array because we don't want any implicit dependencies on internal order
/** @var Item */
public $head;
/** @var Item */
public $chest;
/** @var Item */
public $legs;
/** @var Item */
public $feet;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
for($i = 0; $i < 4; ++$i){
$this->slots[$i] = $this->getSlot();
}
$this->head = $this->getSlot();
$this->chest = $this->getSlot();
$this->legs = $this->getSlot();
$this->feet = $this->getSlot();
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
for($i = 0; $i < 4; ++$i){
$this->putSlot($this->slots[$i]);
}
$this->putSlot($this->head);
$this->putSlot($this->chest);
$this->putSlot($this->legs);
$this->putSlot($this->feet);
}
public function handle(NetworkSession $session) : bool{

View File

@ -30,7 +30,7 @@ namespace pocketmine\network\query;
use pocketmine\network\AdvancedSourceInterface;
use pocketmine\Server;
use pocketmine\utils\Binary;
use function bin2hex;
use function base64_encode;
use function chr;
use function hash;
use function microtime;
@ -134,7 +134,7 @@ class QueryHandler{
$interface->sendRawPacket($address, $port, $reply);
break;
default:
$this->debug("Unhandled packet from $address $port: 0x" . bin2hex($packet));
$this->debug("Unhandled packet from $address $port: " . base64_encode($packet));
break;
}
}

View File

@ -58,11 +58,11 @@ abstract class DefaultPermissions{
$whitelist = self::registerPermission(new Permission(self::ROOT . ".command.whitelist", "Allows the user to modify the server whitelist", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.add", "Allows the user to add a player to the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.remove", "Allows the user to remove a player to the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.remove", "Allows the user to remove a player from the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.reload", "Allows the user to reload the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.enable", "Allows the user to enable the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.disable", "Allows the user to disable the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.list", "Allows the user to list all the players on the server whitelist"), $whitelist);
self::registerPermission(new Permission(self::ROOT . ".command.whitelist.list", "Allows the user to list all players on the server whitelist"), $whitelist);
$whitelist->recalculatePermissibles();
$ban = self::registerPermission(new Permission(self::ROOT . ".command.ban", "Allows the user to ban people", Permission::DEFAULT_OP), $commands);

View File

@ -55,7 +55,9 @@ use function is_bool;
use function is_dir;
use function is_string;
use function is_subclass_of;
use function iterator_to_array;
use function mkdir;
use function shuffle;
use function stripos;
use function strpos;
use function strtolower;
@ -236,12 +238,11 @@ class PluginManager{
}else{
$loaders = $this->fileAssociations;
}
$files = iterator_to_array(new \FilesystemIterator($directory, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
shuffle($files); //this prevents plugins implicitly relying on the filesystem name order when they should be using dependency properties
foreach($loaders as $loader){
foreach(new \DirectoryIterator($directory) as $file){
if($file === "." or $file === ".."){
continue;
}
$file = $directory . $file;
foreach($files as $file){
if(!$loader->canLoadPlugin($file)){
continue;
}

View File

@ -47,7 +47,6 @@ use function file;
use function file_exists;
use function file_get_contents;
use function function_exists;
use function get_class;
use function get_current_user;
use function get_loaded_extensions;
use function getenv;
@ -83,7 +82,6 @@ use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function strval;
use function substr;
use function sys_get_temp_dir;
use function trim;
@ -591,10 +589,11 @@ class Utils{
/**
* @param array $trace
* @param int $maxStringLength
*
* @return array
*/
public static function printableTrace(array $trace) : array{
public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
$messages = [];
for($i = 0; isset($trace[$i]); ++$i){
$params = "";
@ -605,8 +604,17 @@ class Utils{
$args = $trace[$i]["params"];
}
$params = implode(", ", array_map(function($value){
return (is_object($value) ? get_class($value) . " object" : gettype($value) . " " . (is_array($value) ? "Array()" : Utils::printable(@strval($value))));
$params = implode(", ", array_map(function($value) use($maxStringLength){
if(is_object($value)){
return "object " . self::getNiceClassName($value);
}
if(is_array($value)){
return "array[" . count($value) . "]";
}
if(is_string($value)){
return "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength);
}
return gettype($value) . " " . Utils::printable((string) $value);
}, $args));
}
$messages[] = "#$i " . (isset($trace[$i]["file"]) ? self::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")";

@ -1 +0,0 @@
Subproject commit b01f50c50ef6546d000bdde16dc868b4147b31ba

View File

@ -22,8 +22,9 @@ if [ $? -ne 0 ]; then
fi
#Run PHPUnit tests
curl https://phar.phpunit.de/phpunit-7.phar --silent --location -o phpunit.phar
"$PHP_BINARY" phpunit.phar --bootstrap vendor/autoload.php tests/phpunit || exit 1
#7.5.12 introduces changes that set the build on fire because we don't ship libxml - TODO FIX
curl https://phar.phpunit.de/phpunit-7.5.11.phar --silent --location -o phpunit.phar
"$PHP_BINARY" phpunit.phar --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit || exit 1
#Run-the-server tests
DATA_DIR="test_data"