Merge branch 'master' into api3/network

This commit is contained in:
Dylan K. Taylor 2017-03-18 16:29:38 +00:00
commit 8114ceaf68
21 changed files with 495 additions and 127 deletions

View File

@ -1,8 +1,14 @@
### Issue description
<!--- use our forum https://forums.pmmp.io for questions -->
<!--- Any issues requesting updates to new versions of MCPE will be treated as spam. We do not need issues to tell us that there is a new version available. -->
<!---
Write a short description about the issue
If you are reporting a regression or unexpected behaviour, please include the below information:
Expected result: What were you expecting to happen?
Actual result: What actually happened?
-->
### Steps to reproduce the issue
<!--- help us find the problem by adding steps to reproduce the issue -->
1. ...

View File

@ -611,16 +611,14 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
}
public function sendCommandData(){
$data = new \stdClass();
$count = 0;
$data = [];
foreach($this->server->getCommandMap()->getCommands() as $command){
if(($cmdData = $command->generateCustomCommandData($this)) !== null){
++$count;
$data->{$command->getName()}->versions[0] = $cmdData;
if(count($cmdData = $command->generateCustomCommandData($this)) > 0){
$data[$command->getName()]["versions"][0] = $cmdData;
}
}
if($count > 0){
if(count($data) > 0){
//TODO: structure checking
$pk = new AvailableCommandsPacket();
$pk->commands = json_encode($data);
@ -2147,7 +2145,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
if($this->canInteract($vector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 6) and $this->level->useBreakOn($vector, $item, $this, true)){
if($this->isSurvival()){
if(!$item->deepEquals($oldItem) or $item->getCount() !== $oldItem->getCount()){
if(!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()){
$this->inventory->setItemInHand($item);
$this->inventory->sendHeldItem($this->hasSpawned);
}
@ -2418,14 +2416,14 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){
return true;
}
}elseif(!$this->inventory->getItemInHand()->deepEquals($packet->item)){
}elseif(!$this->inventory->getItemInHand()->equals($packet->item)){
$this->inventory->sendHeldItem($this);
}else{
$item = $this->inventory->getItemInHand();
$oldItem = clone $item;
//TODO: Implement adventure mode checks
if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this)){
if(!$item->deepEquals($oldItem) or $item->getCount() !== $oldItem->getCount()){
if(!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()){
$this->inventory->setItemInHand($item);
$this->inventory->sendHeldItem($this->hasSpawned);
}
@ -2452,7 +2450,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
if($this->isCreative()){
$item = $this->inventory->getItemInHand();
}elseif(!$this->inventory->getItemInHand()->deepEquals($packet->item)){
}elseif(!$this->inventory->getItemInHand()->equals($packet->item)){
$this->inventory->sendHeldItem($this);
return true;
}else{
@ -2868,7 +2866,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
break;
}
if($transaction->getSourceItem()->deepEquals($transaction->getTargetItem()) and $transaction->getTargetItem()->getCount() === $transaction->getSourceItem()->getCount()){ //No changes!
if($transaction->getSourceItem()->equals($transaction->getTargetItem()) and $transaction->getTargetItem()->getCount() === $transaction->getSourceItem()->getCount()){ //No changes!
//No changes, just a local inventory update sent by the client
return true;
}
@ -2947,7 +2945,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$item = $packet->input[$y * 3 + $x];
$ingredient = $recipe->getIngredient($x, $y);
if($item->getCount() > 0){
if($ingredient === null or !$ingredient->deepEquals($item, !$ingredient->hasAnyDamageValue(), $ingredient->hasCompoundTag())){
if($ingredient === null or !$ingredient->equals($item, !$ingredient->hasAnyDamageValue(), $ingredient->hasCompoundTag())){
$canCraft = false;
break;
}
@ -2963,7 +2961,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$item = clone $packet->input[$y * 3 + $x];
foreach($needed as $k => $n){
if($n->deepEquals($item, !$n->hasAnyDamageValue(), $n->hasCompoundTag())){
if($n->equals($item, !$n->hasAnyDamageValue(), $n->hasCompoundTag())){
$remove = min($n->getCount(), $item->getCount());
$n->setCount($n->getCount() - $remove);
$item->setCount($item->getCount() - $remove);
@ -2992,7 +2990,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$ingredients = $packet->input;
$result = $packet->output[0];
if(!$canCraft or !$recipe->getResult()->deepEquals($result)){
if(!$canCraft or !$recipe->getResult()->equals($result)){
$this->server->getLogger()->debug("Unmatched recipe " . $recipe->getId() . " from player " . $this->getName() . ": expected " . $recipe->getResult() . ", got " . $result . ", using: " . implode(", ", $ingredients));
$this->inventory->sendContents($this);
return true;

View File

@ -1098,6 +1098,31 @@ class Server{
return true;
}
/**
* Searches all levels for the entity with the specified ID.
* Useful for tracking entities across multiple worlds without needing strong references.
*
* @param int $entityId
* @param Level|null $expectedLevel Level to look in first for the target
*
* @return Entity|null
*/
public function findEntity(int $entityId, Level $expectedLevel = null){
$levels = $this->levels;
if($expectedLevel !== null){
array_unshift($levels, $expectedLevel);
}
foreach($levels as $level){
assert(!$level->isClosed());
if(($entity = $level->getEntity($entityId)) instanceof Entity){
return $entity;
}
}
return null;
}
/**
* @param string $variable
* @param string $defaultValue

View File

@ -32,12 +32,12 @@ use pocketmine\Server;
use pocketmine\utils\TextFormat;
abstract class Command{
/** @var \stdClass */
/** @var array */
private static $defaultDataTemplate = null;
/** @var string */
private $name;
/** @var \stdClass */
/** @var array */
protected $commandData = null;
/** @var string */
@ -87,11 +87,11 @@ abstract class Command{
}
/**
* Returns an \stdClass containing command data
* Returns an array containing command data
*
* @return \stdClass
* @return array
*/
public function getDefaultCommandData() : \stdClass{
public function getDefaultCommandData() : array{
return $this->commandData;
}
@ -101,25 +101,28 @@ abstract class Command{
*
* @param Player $player
*
* @return \stdClass|null
* @return array
*/
public function generateCustomCommandData(Player $player){
//TODO: fix command permission filtering on join
/*if(!$this->testPermissionSilent($player)){
return null;
}*/
$customData = clone $this->commandData;
$customData->aliases = $this->getAliases();
/*foreach($customData->overloads as &$overload){
if(isset($overload->pocketminePermission) and !$player->hasPermission($overload->pocketminePermission)){
unset($overload);
$customData = $this->commandData;
$customData["aliases"] = $this->getAliases();
/*foreach($customData["overloads"] as $overloadName => $overload){
if(isset($overload["pocketminePermission"]) and !$player->hasPermission($overload["pocketminePermission"])){
unset($customData["overloads"][$overloadName]);
}
}*/
return $customData;
}
public function getOverloads(): \stdClass{
return $this->commandData->overloads;
/**
* @return array
*/
public function getOverloads(): array{
return $this->commandData["overloads"];
}
/**
@ -142,7 +145,7 @@ abstract class Command{
* @return string
*/
public function getPermission(){
return $this->commandData->pocketminePermission ?? null;
return $this->commandData["pocketminePermission"] ?? null;
}
@ -151,9 +154,9 @@ abstract class Command{
*/
public function setPermission($permission){
if($permission !== null){
$this->commandData->pocketminePermission = $permission;
$this->commandData["pocketminePermission"] = $permission;
}else{
unset($this->commandData->pocketminePermission);
unset($this->commandData["pocketminePermission"]);
}
}
@ -239,7 +242,7 @@ abstract class Command{
public function unregister(CommandMap $commandMap){
if($this->allowChangesFrom($commandMap)){
$this->commandMap = null;
$this->activeAliases = $this->commandData->aliases;
$this->activeAliases = $this->commandData["aliases"];
$this->label = $this->nextLabel;
return true;
@ -282,7 +285,7 @@ abstract class Command{
* @return string
*/
public function getDescription(){
return $this->commandData->description;
return $this->commandData["description"];
}
/**
@ -296,7 +299,7 @@ abstract class Command{
* @param string[] $aliases
*/
public function setAliases(array $aliases){
$this->commandData->aliases = $aliases;
$this->commandData["aliases"] = $aliases;
if(!$this->isRegistered()){
$this->activeAliases = (array) $aliases;
}
@ -306,7 +309,7 @@ abstract class Command{
* @param string $description
*/
public function setDescription($description){
$this->commandData->description = $description;
$this->commandData["description"] = $description;
}
/**
@ -323,11 +326,14 @@ abstract class Command{
$this->usageMessage = $usage;
}
public static final function generateDefaultData() : \stdClass{
/**
* @return array
*/
public static final function generateDefaultData() : array{
if(self::$defaultDataTemplate === null){
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"));
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"), true);
}
return clone self::$defaultDataTemplate;
return self::$defaultDataTemplate;
}
/**

View File

@ -177,15 +177,35 @@ class SimpleCommandMap implements CommandMap{
return true;
}
public function dispatch(CommandSender $sender, $commandLine){
$args = explode(" ", $commandLine);
/**
* Returns a command to match the specified command line, or null if no matching command was found.
* This method is intended to provide capability for handling commands with spaces in their name.
* The referenced parameters will be modified accordingly depending on the resulting matched command.
*
* @param string &$commandName
* @param string[] &$args
*
* @return Command|null
*/
public function matchCommand(string &$commandName, array &$args){
$count = max(count($args), 255);
if(count($args) === 0){
return false;
for($i = 0; $i < $count; ++$i){
$commandName .= array_shift($args);
if(($command = $this->getCommand($commandName)) instanceof Command){
return $command;
}
$commandName .= " ";
}
$sentCommandLabel = strtolower(array_shift($args));
$target = $this->getCommand($sentCommandLabel);
return null;
}
public function dispatch(CommandSender $sender, $commandLine){
$args = explode(" ", $commandLine);
$sentCommandLabel = "";
$target = $this->matchCommand($sentCommandLabel, $args);
if($target === null){
return false;
@ -213,11 +233,7 @@ class SimpleCommandMap implements CommandMap{
}
public function getCommand($name){
if(isset($this->knownCommands[$name])){
return $this->knownCommands[$name];
}
return null;
return $this->knownCommands[$name] ?? null;
}
/**
@ -235,7 +251,7 @@ class SimpleCommandMap implements CommandMap{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
if(strpos($alias, ":") !== false or strpos($alias, " ") !== false){
if(strpos($alias, ":") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.illegal", [$alias]));
continue;
}
@ -243,20 +259,33 @@ class SimpleCommandMap implements CommandMap{
$targets = [];
$bad = "";
$recursive = "";
foreach($commandStrings as $commandString){
$args = explode(" ", $commandString);
$command = $this->getCommand($args[0]);
$commandName = "";
$command = $this->matchCommand($commandName, $args);
if($command === null){
if(strlen($bad) > 0){
$bad .= ", ";
}
$bad .= $commandString;
}elseif($commandName === $alias){
if($recursive !== ""){
$recursive .= ", ";
}
$recursive .= $commandString;
}else{
$targets[] = $commandString;
}
}
if($recursive !== ""){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, $recursive]));
continue;
}
if(strlen($bad) > 0){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad]));
continue;

View File

@ -1652,6 +1652,7 @@ abstract class Entity extends Location implements Metadatable{
}
$this->namedtag = null;
$this->lastDamageCause = null;
}
}

View File

@ -123,13 +123,15 @@ abstract class Living extends Entity implements Damageable{
$e = $source->getChild();
}
if($e->isOnFire() > 0){
$this->setOnFire(2 * $this->server->getDifficulty());
}
if($e !== null){
if($e->isOnFire() > 0){
$this->setOnFire(2 * $this->server->getDifficulty());
}
$deltaX = $this->x - $e->x;
$deltaZ = $this->z - $e->z;
$this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack());
$deltaX = $this->x - $e->x;
$deltaZ = $this->z - $e->z;
$this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack());
}
}
$pk = new EntityEventPacket();

View File

@ -60,7 +60,9 @@ class Squid extends WaterAnimal implements Ageable{
if($source instanceof EntityDamageByEntityEvent){
$this->swimSpeed = mt_rand(150, 350) / 2000;
$e = $source->getDamager();
$this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize();
if($e !== null){
$this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize();
}
$pk = new EntityEventPacket();
$pk->eid = $this->getId();

View File

@ -24,6 +24,9 @@ namespace pocketmine\event\entity;
use pocketmine\block\Block;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from a block.
*/
class EntityDamageByBlockEvent extends EntityDamageEvent{
/** @var Block */

View File

@ -23,10 +23,13 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from an entity sourced from another entity, for example being hit by a snowball thrown by a Player.
*/
class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
/** @var Entity */
private $childEntity;
/** @var int */
private $childEntityEid;
/**
@ -37,15 +40,17 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
* @param int|int[] $damage
*/
public function __construct(Entity $damager, Entity $childEntity, Entity $entity, $cause, $damage){
$this->childEntity = $childEntity;
$this->childEntityEid = $childEntity->getId();
parent::__construct($damager, $entity, $cause, $damage);
}
/**
* @return Entity
* Returns the entity which caused the damage, or null if the entity has been killed or closed.
*
* @return Entity|null
*/
public function getChild(){
return $this->childEntity;
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
}

View File

@ -24,10 +24,13 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from another entity.
*/
class EntityDamageByEntityEvent extends EntityDamageEvent{
/** @var Entity */
private $damager;
/** @var int */
private $damagerEid;
/** @var float */
private $knockBack;
@ -39,7 +42,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
* @param float $knockBack
*/
public function __construct(Entity $damager, Entity $entity, $cause, $damage, $knockBack = 0.4){
$this->damager = $damager;
$this->damagerEid = $damager->getId();
$this->knockBack = $knockBack;
parent::__construct($entity, $cause, $damage);
$this->addAttackerModifiers($damager);
@ -56,10 +59,12 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
}
/**
* @return Entity
* Returns the attacking entity, or null if the attacker has been killed or closed.
*
* @return Entity|null
*/
public function getDamager(){
return $this->damager;
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEid, $this->getEntity()->getLevel());
}
/**

View File

@ -25,6 +25,9 @@ use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
/**
* Called when an entity takes damage.
*/
class EntityDamageEvent extends EntityEvent implements Cancellable{
public static $handlerList = null;

View File

@ -94,8 +94,10 @@ class PlayerInventory extends BaseInventory{
}
/**
* @internal This method is intended for use in network interaction with clients only.
* @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
* Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot.
* This method is intended for use in network interaction with clients only.
*
* NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
*
* @param int $hotbarSlot
* @param int $inventorySlot

View File

@ -98,7 +98,7 @@ class SimpleTransactionGroup implements TransactionGroup{
}
$checkSourceItem = $ts->getInventory()->getItem($ts->getSlot());
$sourceItem = $ts->getSourceItem();
if(!$checkSourceItem->deepEquals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
if(!$checkSourceItem->equals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
return false;
}
if($sourceItem->getId() !== Item::AIR){
@ -108,7 +108,7 @@ class SimpleTransactionGroup implements TransactionGroup{
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->deepEquals($haveItem)){
if($needItem->equals($haveItem)){
$amount = min($needItem->getCount(), $haveItem->getCount());
$needItem->setCount($needItem->getCount() - $amount);
$haveItem->setCount($haveItem->getCount() - $amount);

View File

@ -351,6 +351,12 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* @param int $id
* @param int $meta
* @param int $count
* @param string $name
*/
public function __construct(int $id, int $meta = 0, int $count = 1, string $name = "Unknown"){
$this->id = $id & 0xffff;
$this->meta = $meta !== -1 ? $meta & 0xffff : -1;
@ -362,6 +368,13 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* Sets the Item's NBT
*
* @param CompoundTag|string $tags
*
* @return $this
*/
public function setCompoundTag($tags){
if($tags instanceof CompoundTag){
$this->setNamedTag($tags);
@ -374,16 +387,24 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* Returns the serialized NBT of the Item
* @return string
*/
public function getCompoundTag() : string{
return $this->tags;
}
/**
* Returns whether this Item has a non-empty NBT.
* @return bool
*/
public function hasCompoundTag() : bool{
return $this->tags !== "";
}
/**
* @return bool
*/
public function hasCustomBlockData() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -411,6 +432,11 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @param CompoundTag $compound
*
* @return $this
*/
public function setCustomBlockData(CompoundTag $compound){
$tags = clone $compound;
$tags->setName("BlockEntityTag");
@ -427,6 +453,9 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @return CompoundTag|null
*/
public function getCustomBlockData(){
if(!$this->hasCompoundTag()){
return null;
@ -440,6 +469,9 @@ class Item implements ItemIds, \JsonSerializable{
return null;
}
/**
* @return bool
*/
public function hasEnchantments() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -457,7 +489,7 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* @param $id
* @param int $id
*
* @return Enchantment|null
*/
@ -534,6 +566,9 @@ class Item implements ItemIds, \JsonSerializable{
return $enchantments;
}
/**
* @return bool
*/
public function hasCustomName() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -550,6 +585,9 @@ class Item implements ItemIds, \JsonSerializable{
return false;
}
/**
* @return string
*/
public function getCustomName() : string{
if(!$this->hasCompoundTag()){
return "";
@ -566,6 +604,11 @@ class Item implements ItemIds, \JsonSerializable{
return "";
}
/**
* @param string $name
*
* @return $this
*/
public function setCustomName(string $name){
if($name === ""){
$this->clearCustomName();
@ -590,6 +633,9 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @return $this
*/
public function clearCustomName(){
if(!$this->hasCompoundTag()){
return $this;
@ -621,6 +667,10 @@ class Item implements ItemIds, \JsonSerializable{
return null;
}
/**
* Returns a tree of Tag objects representing the Item's NBT
* @return null|CompoundTag
*/
public function getNamedTag(){
if(!$this->hasCompoundTag()){
return null;
@ -630,6 +680,12 @@ class Item implements ItemIds, \JsonSerializable{
return $this->cachedNBT = self::parseCompoundTag($this->tags);
}
/**
* Sets the Item's NBT from the supplied CompoundTag object.
* @param CompoundTag $tag
*
* @return $this
*/
public function setNamedTag(CompoundTag $tag){
if($tag->getCount() === 0){
return $this->clearNamedTag();
@ -641,37 +697,73 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* Removes the Item's NBT.
* @return Item
*/
public function clearNamedTag(){
return $this->setCompoundTag("");
}
/**
* @return int
*/
public function getCount() : int{
return $this->count;
}
/**
* @param int $count
*/
public function setCount(int $count){
$this->count = $count;
}
/**
* Returns the name of the item, or the custom name if it is set.
* @return string
*/
final public function getName() : string{
return $this->hasCustomName() ? $this->getCustomName() : $this->name;
}
/**
* @return bool
*/
final public function canBePlaced() : bool{
return $this->block !== null and $this->block->canBePlaced();
}
/**
* Returns whether an entity can eat or drink this item.
* @return bool
*/
public function canBeConsumed() : bool{
return false;
}
/**
* Returns whether this item can be consumed by the supplied Entity.
* @param Entity $entity
*
* @return bool
*/
public function canBeConsumedBy(Entity $entity) : bool{
return $this->canBeConsumed();
}
/**
* Called when the item is consumed by an Entity.
* @param Entity $entity
*/
public function onConsume(Entity $entity){
}
/**
* Returns the block corresponding to this Item.
* @return Block
*/
public function getBlock() : Block{
if($this->block instanceof Block){
return clone $this->block;
@ -680,22 +772,41 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* @return int
*/
final public function getId() : int{
return $this->id;
}
/**
* @return int
*/
final public function getDamage() : int{
return $this->meta;
}
/**
* @param int $meta
*/
public function setDamage(int $meta){
$this->meta = $meta !== -1 ? $meta & 0xFFFF : -1;
}
/**
* Returns whether this item can match any item with an equivalent ID with any meta value.
* Used in crafting recipes which accept multiple variants of the same item, for example crafting tables recipes.
*
* @return bool
*/
public function hasAnyDamageValue() : bool{
return $this->meta === -1;
}
/**
* Returns the highest amount of this item which will fit into one inventory slot.
* @return int
*/
public function getMaxStackSize(){
return 64;
}
@ -762,28 +873,75 @@ class Item implements ItemIds, \JsonSerializable{
return 1;
}
/**
* Called when a player uses this item on a block.
*
* @param Level $level
* @param Player $player
* @param Block $block
* @param Block $target
* @param int $face
* @param float $fx
* @param float $fy
* @param float $fz
*
* @return bool
*/
public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){
return false;
}
/**
* Compares an Item to this Item and check if they match.
*
* @param Item $item
* @param bool $checkDamage Whether to verify that the damage values match.
* @param bool $checkCompound Whether to verify that the items' NBT match.
*
* @return bool
*/
public final function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
return $this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage()) and ($checkCompound === false or $this->getCompoundTag() === $item->getCompoundTag());
}
public final function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
if($this->equals($item, $checkDamage, $checkCompound)){
return true;
}elseif($item->hasCompoundTag() and $this->hasCompoundTag()){
return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());
if($this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage())){
if($checkCompound){
if($item->getCompoundTag() === $this->getCompoundTag()){
return true;
}elseif($this->hasCompoundTag() and $item->hasCompoundTag()){
//Serialized NBT didn't match, check the cached object tree.
return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());
}
}else{
return true;
}
}
return false;
}
final public function __toString() : string{
return "Item " . $this->name . " (" . $this->id . ":" . ($this->meta === null ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
/**
* @deprecated Use {@link Item#equals} instead, this method will be removed in the future.
*
* @param Item $item
* @param bool $checkDamage
* @param bool $checkCompound
*
* @return bool
*/
public final function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
return $this->equals($item, $checkDamage, $checkCompound);
}
/**
* @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()) : "");
}
/**
* Returns an array of item stack properties that can be serialized to json.
*
* @return array
*/
final public function jsonSerialize(){
return [
"id" => $this->id,

@ -1 +1 @@
Subproject commit 8d56e216be03710208800ba31aa3b63612742623
Subproject commit b30ca5e3bdb65c446e22bf777d1dcb04d78b6f7d

View File

@ -40,7 +40,7 @@ class FlowerPot extends Spawnable{
parent::__construct($level, $nbt);
}
public function canAddItem(Item $item): bool{
public function canAddItem(Item $item) : bool{
if(!$this->isEmpty()){
return false;
}
@ -63,7 +63,7 @@ class FlowerPot extends Spawnable{
}
}
public function getItem(): Item{
public function getItem() : Item{
return Item::get((int) ($this->namedtag["item"] ?? 0), (int) ($this->namedtag["mData"] ?? 0), 1);
}
@ -77,11 +77,11 @@ class FlowerPot extends Spawnable{
$this->setItem(Item::get(Item::AIR));
}
public function isEmpty(): bool{
public function isEmpty() : bool{
return $this->getItem()->getId() === Item::AIR;
}
public function getSpawnCompound(): CompoundTag{
public function getSpawnCompound() : CompoundTag{
return new CompoundTag("", [
new StringTag("id", Tile::FLOWER_POT),
new IntTag("x", (int) $this->x),

View File

@ -173,11 +173,14 @@ abstract class Tile extends Position{
unset($this->level->updateTiles[$this->id]);
if($this->chunk instanceof Chunk){
$this->chunk->removeTile($this);
$this->chunk = null;
}
if(($level = $this->getLevel()) instanceof Level){
$level->removeTile($this);
$this->setLevel(null);
}
$this->level = null;
$this->namedtag = null;
}
}

View File

@ -31,57 +31,60 @@ class AutoUpdater{
/** @var Server */
protected $server;
/** @var string */
protected $endpoint;
/** @var bool */
protected $hasUpdate = false;
/** @var array|null */
protected $updateInfo = null;
/**
* @param Server $server
* @param string $endpoint
*/
public function __construct(Server $server, $endpoint){
$this->server = $server;
$this->endpoint = "http://$endpoint/api/";
if($server->getProperty("auto-updater.enabled", true)){
$this->check();
if($this->hasUpdate()){
if($this->server->getProperty("auto-updater.on-update.warn-console", true)){
$this->showConsoleUpdate();
}
}elseif($this->server->getProperty("auto-updater.preferred-channel", true)){
$version = new VersionString();
if(!$version->isDev() and $this->getChannel() !== "stable"){
$this->showChannelSuggestionStable();
}elseif($version->isDev() and $this->getChannel() === "stable"){
$this->showChannelSuggestionBeta();
}
$this->doCheck();
}
}
/**
* Callback used at the end of the update checking task
*
* @param array $updateInfo
*/
public function checkUpdateCallback(array $updateInfo){
$this->updateInfo = $updateInfo;
$this->checkUpdate();
if($this->hasUpdate()){
if($this->server->getProperty("auto-updater.on-update.warn-console", true)){
$this->showConsoleUpdate();
}
}elseif($this->server->getProperty("auto-updater.preferred-channel", true)){
$version = new VersionString();
if(!$version->isDev() and $this->getChannel() !== "stable"){
$this->showChannelSuggestionStable();
}elseif($version->isDev() and $this->getChannel() === "stable"){
$this->showChannelSuggestionBeta();
}
}
}
protected function check(){
$response = Utils::getURL($this->endpoint . "?channel=" . $this->getChannel(), 4);
$response = json_decode($response, true);
if(!is_array($response)){
return;
}
$this->updateInfo = [
"version" => $response["version"],
"api_version" => $response["api_version"],
"build" => $response["build"],
"date" => $response["date"],
"details_url" => $response["details_url"] ?? null,
"download_url" => $response["download_url"]
];
$this->checkUpdate();
}
/**
* Returns whether there is an update available.
*
* @return bool
*/
public function hasUpdate(){
return $this->hasUpdate;
}
/**
* Posts a warning to the console to tell the user there is an update available
*/
public function showConsoleUpdate(){
$logger = $this->server->getLogger();
$newVersion = new VersionString($this->updateInfo["version"]);
@ -94,6 +97,10 @@ class AutoUpdater{
$logger->warning("----- -------------------------- -----");
}
/**
* Shows a warning to a player to tell them there is an update available
* @param Player $player
*/
public function showPlayerUpdate(Player $player){
$player->sendMessage(TextFormat::DARK_PURPLE . "The version of PocketMine-MP that this server is running is out of date. Please consider updating to the latest version.");
$player->sendMessage(TextFormat::DARK_PURPLE . "Check the console for more details.");
@ -115,14 +122,25 @@ class AutoUpdater{
$logger->info("----- -------------------------- -----");
}
/**
* Returns the last retrieved update data.
*
* @return array|null
*/
public function getUpdateInfo(){
return $this->updateInfo;
}
/**
* Schedules an AsyncTask to check for an update.
*/
public function doCheck(){
$this->check();
$this->server->getScheduler()->scheduleAsyncTask(new UpdateCheckTask($this->endpoint, $this->getChannel()));
}
/**
* Checks the update information against the current server version to decide if there's an update
*/
protected function checkUpdate(){
if($this->updateInfo === null){
return;
@ -138,6 +156,11 @@ class AutoUpdater{
}
/**
* Returns the channel used for update checking (stable, beta, dev)
*
* @return string
*/
public function getChannel(){
$channel = strtolower($this->server->getProperty("auto-updater.preferred-channel", "stable"));
if($channel !== "stable" and $channel !== "beta" and $channel !== "development"){
@ -146,4 +169,13 @@ class AutoUpdater{
return $channel;
}
/**
* Returns the host used for update checks.
*
* @return string
*/
public function getEndpoint() : string{
return $this->endpoint;
}
}

View File

@ -0,0 +1,82 @@
<?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/
*
*
*/
namespace pocketmine\updater;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\utils\Utils;
class UpdateCheckTask extends AsyncTask{
/** @var string */
private $endpoint;
/** @var string */
private $channel;
/** @var string */
private $error;
public function __construct(string $endpoint, string $channel){
$this->endpoint = $endpoint;
$this->channel = $channel;
}
public function onRun(){
$this->error = "";
$response = Utils::getURL($this->endpoint . "?channel=" . $this->channel, 4, [], $this->error);
if($this->error !== ""){
return;
}else{
$response = json_decode($response, true);
if(is_array($response)){
$this->setResult(
[
"version" => $response["version"],
"api_version" => $response["api_version"],
"build" => $response["build"],
"date" => $response["date"],
"details_url" => $response["details_url"] ?? null,
"download_url" => $response["download_url"]
],
true
);
}else{
$this->error = "Invalid response data";
}
}
}
public function onCompletion(Server $server){
if($this->error !== ""){
$server->getLogger()->debug("[AutoUpdater] Async update check failed due to \"$this->error\"");
}else{
$updateInfo = $this->getResult();
if(is_array($updateInfo)){
$server->getUpdater()->checkUpdateCallback($updateInfo);
}else{
$server->getLogger()->debug("[AutoUpdater] Update info error");
}
}
}
}

View File

@ -348,14 +348,16 @@ class Utils{
/**
* GETs an URL using cURL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param $page
* @param int $timeout default 10
* @param array $extraHeaders
* @param $page
* @param int $timeout default 10
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
*
* @return bool|mixed
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function getURL($page, $timeout = 10, array $extraHeaders = []){
public static function getURL($page, $timeout = 10, array $extraHeaders = [], &$err = null){
if(Utils::$online === false){
return false;
}
@ -372,6 +374,7 @@ class Utils{
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout);
$ret = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
return $ret;
@ -379,15 +382,17 @@ class Utils{
/**
* POSTs data to an URL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param $page
* @param array|string $args
* @param int $timeout
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
*
* @return bool|mixed
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function postURL($page, $args, $timeout = 10, array $extraHeaders = []){
public static function postURL($page, $args, $timeout = 10, array $extraHeaders = [], &$err = null){
if(Utils::$online === false){
return false;
}
@ -406,6 +411,7 @@ class Utils{
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout);
$ret = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
return $ret;