Compare commits

..

60 Commits
3.2.6 ... 3.3.3

Author SHA1 Message Date
f1c071ce7f Release 3.3.3 2018-11-30 19:41:36 +00:00
e2f46a4358 Remove unused import... 2018-11-30 19:40:35 +00:00
4c08a05fae Barf on trying to read/write nonexisting fields of packets
this should make it easier to debug problems when content of packets changes during protocol updates.
2018-11-30 18:36:28 +00:00
b473ffdedc Remove async playerdata saving, closes #2515
this technically involves non-breaking API changes which should happen on a patch release, but I can't be bothered with the dust cleanup, so we'll just blow it away now. It doesn't hurt anyone anyway.
2018-11-29 19:47:15 +00:00
60dddcd12a Painting: clean up guard checks, remove unnecessary checks 2018-11-29 19:29:10 +00:00
93c26a0b0c Living: Suspend effects ticking on death
This was the cause of a bug with regeneration which caused players taking fatal damage under regeneration not to die correctly. On the server side they would die and immediately regenerate some health, which would cause the next attribute sync to not report the health drop to zero, which made the client unaware that it was dead.

Perhaps attributes should be forcibly synced in some circumstances, but nonetheless regeneration shouldn't apply post-death.
2018-11-29 18:45:46 +00:00
545ec9c881 Updated PreProcessor submodule 2018-11-26 14:02:23 +00:00
d5a1961e6b Force minimum uptime to be >= 120 seconds if a crash occurs (#2534)
This is an incremental improvement over 4a6841a5a4. This change works better because it also reduces disk spam of crashdumps.

This will now sleep if the server uptime was less than 120 seconds before crashing. If unattended, this will clamp down on automated crashdump spam. If attended, the user can simply press CTRL+C to abort the process and skip the delay.
2018-11-25 14:35:35 +00:00
6bc79149c3 SubChunk: Fixed $changed not getting set in setBlock() when only block data changed
it was comparing a string and an int. This now compares the integer values first.
2018-11-22 16:47:25 +00:00
e018311e73 Make start script errors a bit more noob-friendly 2018-11-13 18:23:54 +00:00
71d02e5870 Improve dev build error messages 2018-11-12 22:07:14 +00:00
d312aef1ac 3.3.3 is next 2018-11-11 11:58:51 +00:00
200de3fe84 Release 3.3.2 2018-11-11 11:58:25 +00:00
f560a6efea Merge tag '3.2.7' into release/3.3 2018-11-11 11:24:23 +00:00
7ecd7fd13f Release 3.2.7 2018-11-11 11:23:20 +00:00
b893645a81 Merge branch 'release/3.2' into release/3.3 2018-11-11 11:15:39 +00:00
b19b3134ad PluginManager: reduce unnecessary indentation 2018-11-11 11:15:27 +00:00
243f86b0a0 Merge branch 'release/3.2' into release/3.3 2018-11-10 22:37:02 +00:00
9156cbc269 PluginManager: Make registerEvents() check order more logical
Prioritize validating that the function is actually a handler, before trying to parse its doc comment.
2018-11-10 22:36:46 +00:00
43fe6a1934 Merge branch 'release/3.2' into release/3.3 2018-11-07 20:02:28 +00:00
342a74ffcb Level: Collect garbage from chunk internals in doChunkGarbageCollection() 2018-11-07 20:01:07 +00:00
2183bf875c Merge remote-tracking branch 'origin/release/3.2' into release/3.3 2018-11-04 23:32:33 +00:00
8cc2a4ce5d Remove start script support for source-code installations (#2495)
This was suggested recently by @TheDeibo. We don't want users running source-code installations unless they are developers, and developers should know how to boot a source-code installation anyway.
2018-11-04 23:31:57 +00:00
f61e14e341 Merge branch 'release/3.2' into release/3.3 2018-11-04 22:38:45 +00:00
7b24fbc8db Utils: fix a mistake in getNiceClassName() doc 2018-11-04 22:38:38 +00:00
c4f3426bae Merge branch 'release/3.2' into release/3.3 2018-11-04 22:15:21 +00:00
046c39b02e Remove some Throwable abuse in AsyncTasks 2018-11-04 22:15:06 +00:00
5c3eed40b3 Merge branch 'release/3.2' into release/3.3 2018-11-04 11:57:28 +00:00
3e5237b6e0 ItemEntity: remove useless instanceof 2018-11-04 11:57:22 +00:00
d9a867016c Merge branch 'release/3.2' into release/3.3 2018-11-03 19:43:45 +00:00
a50a863ab7 Chunk: be more intelligent about fast-serializing chunks
This reduces the amount of useless data that pthreads has to copy around.
2018-11-03 19:43:35 +00:00
1b03168b88 Merge branch 'release/3.2' into release/3.3 2018-11-03 15:12:40 +00:00
6b9fee05d6 Fixed performance bug with chunk sending
this process of fast-serialization, fast-deserialize, network-serialize is an order of magnitude slower than just doing the network encode directly on the main thread, and also copies more useless data.

For the main thread, the figures were something like 3x more expensive, and then an extra 7x for deserialization on the worker thread. This is a ridiculously large overhead.
2018-11-03 15:12:30 +00:00
45a18ffe1e Merge branch 'release/3.2' into release/3.3 2018-11-03 12:12:23 +00:00
f0182c9996 TaskHandler: remove incorrect internal warning
this is perfectly fine to use, and preferable to getting a cyclic ref to the scheduler. TaskScheduler->cancelTask() does pretty much the exact same thing, and the scheduler internals are designed to deal with this anyway.
2018-11-03 12:12:02 +00:00
2d88058710 Merge branch 'release/3.2' into release/3.3 2018-10-31 18:55:18 +00:00
ab48d85c35 Properly deal with anonymous tasks in timings 2018-10-31 18:51:30 +00:00
d9b7a28747 Merge branch 'release/3.2' into release/3.3 2018-10-30 15:42:52 +00:00
31ceafa111 Chest: keep inventory better in sync when paired
should fix #2502
2018-10-30 15:42:44 +00:00
694d7d4e20 Merge branch 'release/3.2' into release/3.3 2018-10-29 12:42:10 +00:00
2da2fdd6d4 Added a test for Item->equals() when both items have no NBT 2018-10-29 12:42:03 +00:00
c1c56f29bb Merge branch 'release/3.2' into release/3.3 2018-10-26 20:08:55 +01:00
9b820a0849 Guard against possible overflow bug in NetworkBinaryStream 2018-10-26 20:08:48 +01:00
3128449033 3.3.2 is next 2018-10-25 19:36:20 +01:00
a60154e0b7 Release 3.3.1 2018-10-25 19:29:57 +01:00
4cbbf2e91c Merge branch 'release/3.2' into release/3.3 2018-10-25 19:29:34 +01:00
b0624aff9f 3.2.7 is next 2018-10-25 19:28:54 +01:00
4835537886 Merge branch 'release/3.2' into release/3.3 2018-10-25 18:41:58 +01:00
925da62afa Merge branch 'release/3.2' into release/3.3 2018-10-24 15:49:09 +01:00
d1ee9eb960 Merge branch 'release/3.2' into release/3.3 2018-10-24 12:00:55 +01:00
5e68858ebf Merge branch 'release/3.2' into release/3.3 2018-10-21 18:16:59 +01:00
d749f19c73 Merge branch 'release/3.2' into release/3.3 2018-10-19 18:53:12 +01:00
58067b2ad1 Merge branch 'release/3.2' into release/3.3 2018-10-19 15:55:53 +01:00
af5637e050 PlayerListEntry: remove dead fields 2018-10-16 22:56:21 +01:00
a524b0e447 3.3.1 is next 2018-10-16 18:19:49 +01:00
88a5e92c20 Release 3.3.0 2018-10-16 17:47:35 +01:00
b876ae4ef8 Merge branch 'release/3.2' into release/3.3 2018-10-16 17:26:46 +01:00
c5cd813b76 bump PM version 2018-10-16 17:15:49 +01:00
bc2dff3f51 version numbers 2018-10-16 17:15:26 +01:00
839d5eab7b Protocol changes for 1.7
there's also some new cases in stats, but we don't care about those anyway.
2018-10-16 17:13:52 +01:00
44 changed files with 497 additions and 396 deletions

View File

@ -3515,11 +3515,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* Handles player data saving
*
* @param bool $async
*
* @throws \InvalidStateException if the player is closed
*/
public function save(bool $async = false){
public function save(){
if($this->closed){
throw new \InvalidStateException("Tried to save closed player");
}
@ -3556,7 +3554,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->namedtag->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
if($this->username != "" and $this->namedtag instanceof CompoundTag){
$this->server->saveOfflinePlayerData($this->username, $this->namedtag, $async);
$this->server->saveOfflinePlayerData($this->username, $this->namedtag);
}
}

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.2.6";
const BASE_VERSION = "3.3.3";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -91,7 +91,6 @@ use pocketmine\plugin\PluginManager;
use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\FileWriteTask;
use pocketmine\scheduler\SendUsageTask;
use pocketmine\snooze\SleeperHandler;
use pocketmine\snooze\SleeperNotifier;
@ -805,9 +804,8 @@ class Server{
/**
* @param string $name
* @param CompoundTag $nbtTag
* @param bool $async
*/
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag, bool $async = false){
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag){
$ev = new PlayerDataSaveEvent($nbtTag, $name);
$ev->setCancelled(!$this->shouldSavePlayerData());
@ -816,11 +814,7 @@ class Server{
if(!$ev->isCancelled()){
$nbt = new BigEndianNBTStream();
try{
if($async){
$this->asyncPool->submitTask(new FileWriteTask($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData())));
}else{
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
}
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
}catch(\Throwable $e){
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
$this->logger->logException($e);
@ -1449,6 +1443,7 @@ class Server{
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error5", ["https://github.com/pmmp/PocketMine-MP/releases"]));
$this->forceShutdown();
return;
}
@ -2246,6 +2241,12 @@ class Server{
$this->forceShutdown();
$this->isRunning = false;
//Force minimum uptime to be >= 120 seconds, to reduce the impact of spammy crash loops
$spacing = ((int) \pocketmine\START_TIME) - time() + 120;
if($spacing > 0){
sleep($spacing);
}
@Utils::kill(getmypid());
exit(1);
}
@ -2318,7 +2319,7 @@ class Server{
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, "", 0, $skin, $xboxUserId);
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skin, $xboxUserId);
$this->broadcastPacket($players ?? $this->playerList, $pk);
}
@ -2341,7 +2342,7 @@ class Server{
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
foreach($this->playerList as $player){
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), "", 0, $player->getSkin(), $player->getXuid());
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkin(), $player->getXuid());
}
$p->dataPacket($pk);
@ -2397,7 +2398,7 @@ class Server{
Timings::$worldSaveTimer->startTiming();
foreach($this->players as $index => $player){
if($player->spawned){
$player->save(true);
$player->save();
}elseif(!$player->isConnected()){
$this->removePlayer($player);
}

View File

@ -206,34 +206,38 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_INTERESTED = 26;
public const DATA_FLAG_CHARGED = 27;
public const DATA_FLAG_TAMED = 28;
public const DATA_FLAG_LEASHED = 29;
public const DATA_FLAG_SHEARED = 30;
public const DATA_FLAG_GLIDING = 31;
public const DATA_FLAG_ELDER = 32;
public const DATA_FLAG_MOVING = 33;
public const DATA_FLAG_BREATHING = 34;
public const DATA_FLAG_CHESTED = 35;
public const DATA_FLAG_STACKABLE = 36;
public const DATA_FLAG_SHOWBASE = 37;
public const DATA_FLAG_REARING = 38;
public const DATA_FLAG_VIBRATING = 39;
public const DATA_FLAG_IDLING = 40;
public const DATA_FLAG_EVOKER_SPELL = 41;
public const DATA_FLAG_CHARGE_ATTACK = 42;
public const DATA_FLAG_WASD_CONTROLLED = 43;
public const DATA_FLAG_CAN_POWER_JUMP = 44;
public const DATA_FLAG_LINGER = 45;
public const DATA_FLAG_HAS_COLLISION = 46;
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 47;
public const DATA_FLAG_FIRE_IMMUNE = 48;
public const DATA_FLAG_DANCING = 49;
public const DATA_FLAG_ENCHANTED = 50;
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 51; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
public const DATA_FLAG_CONTAINER_PRIVATE = 52; //inventory is private, doesn't drop contents when killed if true
//53 TransformationComponent
public const DATA_FLAG_SPIN_ATTACK = 54;
public const DATA_FLAG_SWIMMING = 55;
public const DATA_FLAG_BRIBED = 56; //dolphins have this set when they go to find treasure for the player
public const DATA_FLAG_ORPHANED = 29;
public const DATA_FLAG_LEASHED = 30;
public const DATA_FLAG_SHEARED = 31;
public const DATA_FLAG_GLIDING = 32;
public const DATA_FLAG_ELDER = 33;
public const DATA_FLAG_MOVING = 34;
public const DATA_FLAG_BREATHING = 35;
public const DATA_FLAG_CHESTED = 36;
public const DATA_FLAG_STACKABLE = 37;
public const DATA_FLAG_SHOWBASE = 38;
public const DATA_FLAG_REARING = 39;
public const DATA_FLAG_VIBRATING = 40;
public const DATA_FLAG_IDLING = 41;
public const DATA_FLAG_EVOKER_SPELL = 42;
public const DATA_FLAG_CHARGE_ATTACK = 43;
public const DATA_FLAG_WASD_CONTROLLED = 44;
public const DATA_FLAG_CAN_POWER_JUMP = 45;
public const DATA_FLAG_LINGER = 46;
public const DATA_FLAG_HAS_COLLISION = 47;
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 48;
public const DATA_FLAG_FIRE_IMMUNE = 49;
public const DATA_FLAG_DANCING = 50;
public const DATA_FLAG_ENCHANTED = 51;
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 52; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
public const DATA_FLAG_CONTAINER_PRIVATE = 53; //inventory is private, doesn't drop contents when killed if true
//54 TransformationComponent
public const DATA_FLAG_SPIN_ATTACK = 55;
public const DATA_FLAG_SWIMMING = 56;
public const DATA_FLAG_BRIBED = 57; //dolphins have this set when they go to find treasure for the player
public const DATA_FLAG_PREGNANT = 58;
public const DATA_FLAG_LAYING_EGG = 59;
//60 ??
public static $entityCount = 1;
/** @var Entity[] */

View File

@ -841,7 +841,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->getName(), 0, $this->skin)];
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->skin)];
$player->dataPacket($pk);
}

View File

@ -655,11 +655,11 @@ abstract class Living extends Entity implements Damageable{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->doEffectsTick($tickDiff)){
$hasUpdate = true;
}
if($this->isAlive()){
if($this->doEffectsTick($tickDiff)){
$hasUpdate = true;
}
if($this->isInsideOfSolid()){
$hasUpdate = true;
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1);

View File

@ -208,7 +208,7 @@ class ItemEntity extends Entity{
$item = $this->getItem();
$playerInventory = $player->getInventory();
if(!($item instanceof Item) or ($player->isSurvival() and !$playerInventory->canAddItem($item))){
if($player->isSurvival() and !$playerInventory->canAddItem($item)){
return;
}

View File

@ -37,67 +37,68 @@ class PaintingItem extends Item{
}
public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{
if(!$blockClicked->isTransparent() and $face > 1 and !$blockReplace->isSolid()){
/** @var PaintingMotive[] $motives */
$motives = [];
if($face === Vector3::SIDE_DOWN or $face === Vector3::SIDE_UP){
return false;
}
$totalDimension = 0;
foreach(PaintingMotive::getAll() as $motive){
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
if($currentTotalDimension < $totalDimension){
continue;
$motives = [];
$totalDimension = 0;
foreach(PaintingMotive::getAll() as $motive){
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
if($currentTotalDimension < $totalDimension){
continue;
}
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
if($currentTotalDimension > $totalDimension){
$totalDimension = $currentTotalDimension;
/*
* This drops all motive possibilities smaller than this
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
* when there is an L-shape of space available.
*/
$motives = [];
}
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
if($currentTotalDimension > $totalDimension){
$totalDimension = $currentTotalDimension;
/*
* This drops all motive possibilities smaller than this
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
* when there is an L-shape of space available.
*/
$motives = [];
}
$motives[] = $motive;
}
$motives[] = $motive;
}
}
if(empty($motives)){ //No space available
return false;
}
if(empty($motives)){ //No space available
return false;
}
/** @var PaintingMotive $motive */
$motive = $motives[array_rand($motives)];
/** @var PaintingMotive $motive */
$motive = $motives[array_rand($motives)];
static $directions = [
Vector3::SIDE_SOUTH => 0,
Vector3::SIDE_WEST => 1,
Vector3::SIDE_NORTH => 2,
Vector3::SIDE_EAST => 3
];
static $directions = [
Vector3::SIDE_SOUTH => 0,
Vector3::SIDE_WEST => 1,
Vector3::SIDE_NORTH => 2,
Vector3::SIDE_EAST => 3
];
$direction = $directions[$face] ?? -1;
if($direction === -1){
return false;
}
$direction = $directions[$face] ?? -1;
if($direction === -1){
return false;
}
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
$nbt->setByte("Direction", $direction);
$nbt->setString("Motive", $motive->getName());
$nbt->setInt("TileX", $blockClicked->getFloorX());
$nbt->setInt("TileY", $blockClicked->getFloorY());
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
$nbt->setByte("Direction", $direction);
$nbt->setString("Motive", $motive->getName());
$nbt->setInt("TileX", $blockClicked->getFloorX());
$nbt->setInt("TileY", $blockClicked->getFloorY());
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
if($entity instanceof Entity){
--$this->count;
$entity->spawnToAll();
if($entity instanceof Entity){
--$this->count;
$entity->spawnToAll();
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
return true;
}
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
return true;
}
return false;

View File

@ -2979,6 +2979,7 @@ class Level implements ChunkManager, Metadatable{
$this->unloadChunkRequest($X, $Z, true);
}
}
$chunk->collectGarbage();
}
$this->provider->doGarbageCollection();

View File

@ -836,18 +836,13 @@ class Chunk{
}
/**
* Disposes of empty subchunks
* Disposes of empty subchunks and frees data where possible
*/
public function pruneEmptySubChunks(){
public function collectGarbage() : void{
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
}elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
if(!($subChunk instanceof EmptySubChunk) and $subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
$this->subChunks[$y] = $this->emptySubChunk;
}else{
continue; //do not set changed
}
$this->hasChanged = true;
}
}
@ -887,20 +882,31 @@ class Chunk{
$stream = new BinaryStream();
$stream->putInt($this->x);
$stream->putInt($this->z);
$count = 0;
$subChunks = "";
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
$stream->putByte(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0));
if($this->terrainGenerated){
//subchunks
$count = 0;
$subChunks = "";
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
}
++$count;
$subChunks .= chr($y) . $subChunk->getBlockIdArray() . $subChunk->getBlockDataArray();
if($this->lightPopulated){
$subChunks .= $subChunk->getBlockSkyLightArray() . $subChunk->getBlockLightArray();
}
}
$stream->putByte($count);
$stream->put($subChunks);
//biomes
$stream->put($this->biomeIds);
if($this->lightPopulated){
$stream->put(pack("v*", ...$this->heightMap));
}
++$count;
$subChunks .= chr($y) . $subChunk->fastSerialize();
}
$stream->putByte($count);
$stream->put($subChunks);
$stream->put(pack("v*", ...$this->heightMap) .
$this->biomeIds .
chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0)));
return $stream->getBuffer();
}
@ -916,19 +922,36 @@ class Chunk{
$x = $stream->getInt();
$z = $stream->getInt();
$flags = $stream->getByte();
$lightPopulated = (bool) ($flags & 4);
$terrainPopulated = (bool) ($flags & 2);
$terrainGenerated = (bool) ($flags & 1);
$subChunks = [];
$count = $stream->getByte();
for($y = 0; $y < $count; ++$y){
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
$biomeIds = "";
$heightMap = [];
if($terrainGenerated){
$count = $stream->getByte();
for($y = 0; $y < $count; ++$y){
$subChunks[$stream->getByte()] = new SubChunk(
$stream->get(4096), //blockids
$stream->get(2048), //blockdata
$lightPopulated ? $stream->get(2048) : "", //skylight
$lightPopulated ? $stream->get(2048) : "" //blocklight
);
}
$biomeIds = $stream->get(256);
if($lightPopulated){
$heightMap = array_values(unpack("v*", $stream->get(512)));
}
}
$heightMap = array_values(unpack("v*", $stream->get(512)));
$biomeIds = $stream->get(256);
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
$flags = $stream->getByte();
$chunk->lightPopulated = (bool) ($flags & 4);
$chunk->terrainPopulated = (bool) ($flags & 2);
$chunk->terrainGenerated = (bool) ($flags & 1);
$chunk->setGenerated($terrainGenerated);
$chunk->setPopulated($terrainPopulated);
$chunk->setLightPopulated($lightPopulated);
return $chunk;
}
}

View File

@ -126,8 +126,4 @@ class EmptySubChunk implements SubChunkInterface{
public function networkSerialize() : string{
return "\x00" . str_repeat("\x00", 6144);
}
public function fastSerialize() : string{
throw new \BadMethodCallException("Should not try to serialize empty subchunks");
}
}

View File

@ -106,13 +106,14 @@ class SubChunk implements SubChunkInterface{
if($data !== null){
$i >>= 1;
$byte = ord($this->data{$i});
$oldPair = ord($this->data{$i});
if(($y & 1) === 0){
$this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f));
$newPair = ($oldPair & 0xf0) | ($data & 0x0f);
}else{
$this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f));
$newPair = (($data & 0x0f) << 4) | ($oldPair & 0x0f);
}
if($this->data{$i} !== $byte){
if($newPair !== $oldPair){
$this->data{$i} = chr($newPair);
$changed = true;
}
}
@ -222,23 +223,6 @@ class SubChunk implements SubChunkInterface{
return "\x00" . $this->ids . $this->data;
}
public function fastSerialize() : string{
return
$this->ids .
$this->data .
$this->skyLight .
$this->blockLight;
}
public static function fastDeserialize(string $data) : SubChunk{
return new SubChunk(
substr($data, 0, 4096), //ids
substr($data, 4096, 2048), //data
substr($data, 6144, 2048), //sky light
substr($data, 8192, 2048) //block light
);
}
public function __debugInfo(){
return [];
}

View File

@ -202,9 +202,4 @@ interface SubChunkInterface{
* @return string
*/
public function networkSerialize() : string;
/**
* @return string
*/
public function fastSerialize() : string;
}

View File

@ -29,7 +29,6 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\tile\Spawnable;
class ChunkRequestTask extends AsyncTask{
@ -39,36 +38,22 @@ class ChunkRequestTask extends AsyncTask{
protected $chunkX;
protected $chunkZ;
protected $tiles;
protected $compressionLevel;
public function __construct(Level $level, int $chunkX, int $chunkZ, Chunk $chunk){
$this->levelId = $level->getId();
$this->compressionLevel = $level->getServer()->networkCompressionLevel;
$this->chunk = $chunk->fastSerialize();
$this->chunk = $chunk->networkSerialize();
$this->chunkX = $chunkX;
$this->chunkZ = $chunkZ;
//TODO: serialize tiles with chunks
$tiles = "";
foreach($chunk->getTiles() as $tile){
if($tile instanceof Spawnable){
$tiles .= $tile->getSerializedSpawnCompound();
}
}
$this->tiles = $tiles;
}
public function onRun(){
$chunk = Chunk::fastDeserialize($this->chunk);
$pk = new FullChunkDataPacket();
$pk->chunkX = $this->chunkX;
$pk->chunkZ = $this->chunkZ;
$pk->data = $chunk->networkSerialize() . $this->tiles;
$pk->data = $this->chunk;
$batch = new BatchPacket();
$batch->addPacket($pk);

View File

@ -95,7 +95,7 @@ class FloatingTextParticle extends Particle{
$add = new PlayerListPacket();
$add->type = PlayerListPacket::TYPE_ADD;
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, $name, 0, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
$p[] = $add;
$pk = new AddPlayerPacket();

View File

@ -109,7 +109,12 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarInt($auxValue);
$nbt = $item->getCompoundTag();
$this->putLShort(strlen($nbt));
$nbtLen = strlen($nbt);
if($nbtLen > 32767){
throw new \InvalidArgumentException("NBT encoded length must be < 32768, got $nbtLen bytes");
}
$this->putLShort($nbtLen);
$this->put($nbt);
$this->putVarInt(0); //CanPlaceOn entry count (TODO)

View File

@ -101,6 +101,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\RiderJumpPacket;
use pocketmine\network\mcpe\protocol\ScriptCustomEventPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsResponsePacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
@ -603,4 +604,8 @@ abstract class NetworkSession{
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
return false;
}
public function handleScriptCustomEvent(ScriptCustomEventPacket $packet) : bool{
return false;
}
}

View File

@ -38,10 +38,6 @@ class AddPlayerPacket extends DataPacket{
public $uuid;
/** @var string */
public $username;
/** @var string */
public $thirdPartyName = "";
/** @var int */
public $platform = 0;
/** @var int|null */
public $entityUniqueId = null; //TODO
/** @var int */
@ -81,8 +77,6 @@ class AddPlayerPacket extends DataPacket{
protected function decodePayload(){
$this->uuid = $this->getUUID();
$this->username = $this->getString();
$this->thirdPartyName = $this->getString();
$this->platform = $this->getVarInt();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->platformChatId = $this->getString();
@ -113,8 +107,6 @@ class AddPlayerPacket extends DataPacket{
protected function encodePayload(){
$this->putUUID($this->uuid);
$this->putString($this->username);
$this->putString($this->thirdPartyName);
$this->putVarInt($this->platform);
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putString($this->platformChatId);

View File

@ -134,4 +134,12 @@ abstract class DataPacket extends NetworkBinaryStream{
return $data;
}
public function __get($name){
throw new \Error("Cannot read non-existing field \"$name\"");
}
public function __set($name, $value){
throw new \Error("Cannot write non-existing field \"$name\"");
}
}

View File

@ -40,6 +40,9 @@ class EventPacket extends DataPacket{
public const TYPE_BOSS_KILLED = 7;
public const TYPE_AGENT_COMMAND = 8;
public const TYPE_AGENT_CREATED = 9;
public const TYPE_PATTERN_REMOVED = 10; //???
public const TYPE_COMMANED_EXECUTED = 11;
public const TYPE_FISH_BUCKETED = 12;
/** @var int */
public $playerRuntimeId;

View File

@ -146,6 +146,7 @@ class PacketPool{
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
static::registerPacket(new UpdateSoftEnumPacket());
static::registerPacket(new NetworkStackLatencyPacket());
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -56,8 +56,6 @@ class PlayerListPacket extends DataPacket{
$entry->uuid = $this->getUUID();
$entry->entityUniqueId = $this->getEntityUniqueId();
$entry->username = $this->getString();
$entry->thirdPartyName = $this->getString();
$entry->platform = $this->getVarInt();
$skinId = $this->getString();
$skinData = $this->getString();
@ -90,8 +88,6 @@ class PlayerListPacket extends DataPacket{
$this->putUUID($entry->uuid);
$this->putEntityUniqueId($entry->entityUniqueId);
$this->putString($entry->username);
$this->putString($entry->thirdPartyName);
$this->putVarInt($entry->platform);
$this->putString($entry->skin->getSkinId());
$this->putString($entry->skin->getSkinData());
$this->putString($entry->skin->getCapeData());

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 282;
public const CURRENT_PROTOCOL = 291;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.6.0';
public const MINECRAFT_VERSION = 'v1.7.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.6.0';
public const MINECRAFT_VERSION_NETWORK = '1.7.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -164,5 +164,6 @@ interface ProtocolInfo{
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
}

View File

@ -0,0 +1,51 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class ScriptCustomEventPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SCRIPT_CUSTOM_EVENT_PACKET;
/** @var string */
public $eventName;
/** @var string json data */
public $eventData;
protected function decodePayload(){
$this->eventName = $this->getString();
$this->eventData = $this->getString();
}
protected function encodePayload(){
$this->putString($this->eventName);
$this->putString($this->eventData);
}
public function handle(NetworkSession $session) : bool{
return $session->handleScriptCustomEvent($this);
}
}

View File

@ -31,8 +31,8 @@ use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;
class SetScorePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SET_SCORE_PACKET;
public const TYPE_MODIFY_SCORE = 0;
public const TYPE_RESET_SCORE = 1;
public const TYPE_CHANGE = 0;
public const TYPE_REMOVE = 1;
/** @var int */
public $type;
@ -43,9 +43,23 @@ class SetScorePacket extends DataPacket{
$this->type = $this->getByte();
for($i = 0, $i2 = $this->getUnsignedVarInt(); $i < $i2; ++$i){
$entry = new ScorePacketEntry();
$entry->uuid = $this->getUUID();
$entry->scoreboardId = $this->getVarLong();
$entry->objectiveName = $this->getString();
$entry->score = $this->getLInt();
if($this->type !== self::TYPE_REMOVE){
$entry->type = $this->getByte();
switch($entry->type){
case ScorePacketEntry::TYPE_PLAYER:
case ScorePacketEntry::TYPE_ENTITY:
$entry->entityUniqueId = $this->getEntityUniqueId();
break;
case ScorePacketEntry::TYPE_FAKE_PLAYER:
$entry->customName = $this->getString();
break;
default:
throw new \UnexpectedValueException("Unknown entry type $entry->type");
}
}
}
}
@ -53,9 +67,23 @@ class SetScorePacket extends DataPacket{
$this->putByte($this->type);
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$this->putUUID($entry->uuid);
$this->putVarLong($entry->scoreboardId);
$this->putString($entry->objectiveName);
$this->putLInt($entry->score);
if($this->type !== self::TYPE_REMOVE){
$this->putByte($entry->type);
switch($entry->type){
case ScorePacketEntry::TYPE_PLAYER:
case ScorePacketEntry::TYPE_ENTITY:
$this->putEntityUniqueId($entry->entityUniqueId);
break;
case ScorePacketEntry::TYPE_FAKE_PLAYER:
$this->putString($entry->customName);
break;
default:
throw new \UnexpectedValueException("Unknown entry type $entry->type");
}
}
}
}

View File

@ -45,7 +45,7 @@ class SetScoreboardIdentityPacket extends DataPacket{
$entry = new ScoreboardIdentityPacketEntry();
$entry->scoreboardId = $this->getVarLong();
if($this->type === self::TYPE_REGISTER_IDENTITY){
$entry->uuid = $this->getUUID();
$entry->entityUniqueId = $this->getEntityUniqueId();
}
$this->entries[] = $entry;
@ -58,7 +58,7 @@ class SetScoreboardIdentityPacket extends DataPacket{
foreach($this->entries as $entry){
$this->putVarLong($entry->scoreboardId);
if($this->type === self::TYPE_REGISTER_IDENTITY){
$this->putUUID($entry->uuid);
$this->putEntityUniqueId($entry->entityUniqueId);
}
}
}

View File

@ -116,6 +116,8 @@ class StartGamePacket extends DataPacket{
public $hasLockedResourcePack = false;
/** @var bool */
public $isFromLockedWorldTemplate = false;
/** @var bool */
public $useMsaGamertagsOnly = false;
/** @var string */
public $levelId = ""; //base64 string, usually the same as world folder name in vanilla
@ -173,6 +175,7 @@ class StartGamePacket extends DataPacket{
$this->hasLockedBehaviorPack = $this->getBool();
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
$this->useMsaGamertagsOnly = $this->getBool();
$this->levelId = $this->getString();
$this->worldName = $this->getString();
@ -232,6 +235,7 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasLockedBehaviorPack);
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);
$this->putBool($this->useMsaGamertagsOnly);
$this->putString($this->levelId);
$this->putString($this->worldName);

View File

@ -48,10 +48,6 @@ class TextPacket extends DataPacket{
/** @var string */
public $sourceName;
/** @var string */
public $sourceThirdPartyName = "";
/** @var int */
public $sourcePlatform = 0;
/** @var string */
public $message;
/** @var string[] */
public $parameters = [];
@ -69,8 +65,6 @@ class TextPacket extends DataPacket{
/** @noinspection PhpMissingBreakStatementInspection */
case self::TYPE_ANNOUNCEMENT:
$this->sourceName = $this->getString();
$this->sourceThirdPartyName = $this->getString();
$this->sourcePlatform = $this->getVarInt();
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
@ -101,8 +95,6 @@ class TextPacket extends DataPacket{
/** @noinspection PhpMissingBreakStatementInspection */
case self::TYPE_ANNOUNCEMENT:
$this->putString($this->sourceName);
$this->putString($this->sourceThirdPartyName);
$this->putVarInt($this->sourcePlatform);
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:

View File

@ -34,10 +34,6 @@ class PlayerListEntry{
public $entityUniqueId;
/** @var string */
public $username;
/** @var string */
public $thirdPartyName = "";
/** @var int */
public $platform = 0;
/** @var Skin */
public $skin;
/** @var string */
@ -52,13 +48,11 @@ class PlayerListEntry{
return $entry;
}
public static function createAdditionEntry(UUID $uuid, int $entityUniqueId, string $username, string $thirdPartyName, int $platform, Skin $skin, string $xboxUserId = "", string $platformChatId = "") : PlayerListEntry{
public static function createAdditionEntry(UUID $uuid, int $entityUniqueId, string $username, Skin $skin, string $xboxUserId = "", string $platformChatId = "") : PlayerListEntry{
$entry = new PlayerListEntry();
$entry->uuid = $uuid;
$entry->entityUniqueId = $entityUniqueId;
$entry->username = $username;
$entry->thirdPartyName = $thirdPartyName;
$entry->platform = $platform;
$entry->skin = $skin;
$entry->xboxUserId = $xboxUserId;
$entry->platformChatId = $platformChatId;

View File

@ -23,13 +23,23 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use pocketmine\utils\UUID;
class ScorePacketEntry{
/** @var UUID */
public $uuid;
public const TYPE_PLAYER = 1;
public const TYPE_ENTITY = 2;
public const TYPE_FAKE_PLAYER = 3;
/** @var int */
public $scoreboardId;
/** @var string */
public $objectiveName;
/** @var int */
public $score;
/** @var int */
public $type;
/** @var int|null (if type entity or player) */
public $entityUniqueId;
/** @var string|null (if type fake player) */
public $customName;
}

View File

@ -23,12 +23,10 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use pocketmine\utils\UUID;
class ScoreboardIdentityPacketEntry{
/** @var int */
public $scoreboardId;
/** @var UUID|null */
public $uuid;
/** @var int|null */
public $entityUniqueId;
}

View File

@ -199,122 +199,140 @@ class PluginManager{
* @return Plugin[]
*/
public function loadPlugins(string $directory, array $newLoaders = null){
if(!is_dir($directory)){
return [];
}
if(is_dir($directory)){
$plugins = [];
$loadedPlugins = [];
$dependencies = [];
$softDependencies = [];
if(is_array($newLoaders)){
$loaders = [];
foreach($newLoaders as $key){
if(isset($this->fileAssociations[$key])){
$loaders[$key] = $this->fileAssociations[$key];
}
$plugins = [];
$loadedPlugins = [];
$dependencies = [];
$softDependencies = [];
if(is_array($newLoaders)){
$loaders = [];
foreach($newLoaders as $key){
if(isset($this->fileAssociations[$key])){
$loaders[$key] = $this->fileAssociations[$key];
}
}else{
$loaders = $this->fileAssociations;
}
foreach($loaders as $loader){
foreach(new \DirectoryIterator($directory) as $file){
if($file === "." or $file === ".."){
}else{
$loaders = $this->fileAssociations;
}
foreach($loaders as $loader){
foreach(new \DirectoryIterator($directory) as $file){
if($file === "." or $file === ".."){
continue;
}
$file = $directory . $file;
if(!$loader->canLoadPlugin($file)){
continue;
}
try{
$description = $loader->getPluginDescription($file);
if($description === null){
continue;
}
$file = $directory . $file;
if(!$loader->canLoadPlugin($file)){
$name = $description->getName();
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
continue;
}elseif(strpos($name, " ") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
}
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
continue;
}
try{
$description = $loader->getPluginDescription($file);
if($description instanceof PluginDescription){
$name = $description->getName();
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
continue;
}elseif(strpos($name, " ") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
}
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
continue;
}
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
]));
continue;
}
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
]));
continue;
}
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
]));
continue;
}
}
$plugins[$name] = $file;
$softDependencies[$name] = $description->getSoftDepend();
$dependencies[$name] = $description->getDepend();
foreach($description->getLoadBefore() as $before){
if(isset($softDependencies[$before])){
$softDependencies[$before][] = $name;
}else{
$softDependencies[$before] = [$name];
}
}
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
]));
continue;
}
}catch(\Throwable $e){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
$this->server->getLogger()->logException($e);
}
$plugins[$name] = $file;
$softDependencies[$name] = $description->getSoftDepend();
$dependencies[$name] = $description->getDepend();
foreach($description->getLoadBefore() as $before){
if(isset($softDependencies[$before])){
$softDependencies[$before][] = $name;
}else{
$softDependencies[$before] = [$name];
}
}
}catch(\Throwable $e){
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
$this->server->getLogger()->logException($e);
}
}
}
while(count($plugins) > 0){
$missingDependency = true;
foreach($plugins as $name => $file){
if(isset($dependencies[$name])){
foreach($dependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
unset($dependencies[$name][$key]);
}elseif(!isset($plugins[$dependency])){
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
]));
unset($plugins[$name]);
continue 2;
}
}
if(count($dependencies[$name]) === 0){
unset($dependencies[$name]);
}
}
if(isset($softDependencies[$name])){
foreach($softDependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
unset($softDependencies[$name][$key]);
}
}
if(count($softDependencies[$name]) === 0){
unset($softDependencies[$name]);
}
}
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
unset($plugins[$name]);
$missingDependency = false;
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}else{
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
}
}
}
while(count($plugins) > 0){
$missingDependency = true;
if($missingDependency){
foreach($plugins as $name => $file){
if(isset($dependencies[$name])){
foreach($dependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
unset($dependencies[$name][$key]);
}elseif(!isset($plugins[$dependency])){
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
$name,
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
]));
unset($plugins[$name]);
continue 2;
}
}
if(count($dependencies[$name]) === 0){
unset($dependencies[$name]);
}
}
if(isset($softDependencies[$name])){
foreach($softDependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
unset($softDependencies[$name][$key]);
}
}
if(count($softDependencies[$name]) === 0){
unset($softDependencies[$name]);
}
}
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
if(!isset($dependencies[$name])){
unset($softDependencies[$name]);
unset($plugins[$name]);
$missingDependency = false;
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
@ -325,34 +343,17 @@ class PluginManager{
}
}
//No plugins loaded :(
if($missingDependency){
foreach($plugins as $name => $file){
if(!isset($dependencies[$name])){
unset($softDependencies[$name]);
unset($plugins[$name]);
$missingDependency = false;
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}else{
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
}
}
}
//No plugins loaded :(
if($missingDependency){
foreach($plugins as $name => $file){
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
}
$plugins = [];
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
}
$plugins = [];
}
}
return $loadedPlugins;
}else{
return [];
}
return $loadedPlugins;
}
/**
@ -740,6 +741,24 @@ class PluginManager{
continue;
}
$parameters = $method->getParameters();
if(count($parameters) !== 1){
continue;
}
try{
$eventClass = $parameters[0]->getClass();
}catch(\ReflectionException $e){ //class doesn't exist
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
continue;
}
throw $e;
}
if($eventClass === null or !$eventClass->isSubclassOf(Event::class)){
continue;
}
try{
$priority = isset($tags["priority"]) ? EventPriority::fromString($tags["priority"]) : EventPriority::NORMAL;
}catch(\InvalidArgumentException $e){
@ -761,21 +780,7 @@ class PluginManager{
}
}
$parameters = $method->getParameters();
try{
$isHandler = count($parameters) === 1 && $parameters[0]->getClass() instanceof \ReflectionClass && is_subclass_of($parameters[0]->getClass()->getName(), Event::class);
}catch(\ReflectionException $e){
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
continue;
}
throw $e;
}
if($isHandler){
$class = $parameters[0]->getClass()->getName();
$this->registerEvent($class, $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
}
$this->registerEvent($eventClass->getName(), $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
}
}
}

View File

@ -44,10 +44,6 @@ class FileWriteTask extends AsyncTask{
}
public function onRun(){
try{
file_put_contents($this->path, $this->contents, $this->flags);
}catch(\Throwable $e){
}
file_put_contents($this->path, $this->contents, $this->flags);
}
}

View File

@ -147,13 +147,9 @@ class SendUsageTask extends AsyncTask{
}
public function onRun(){
try{
Internet::postURL($this->endpoint, $this->data, 5, [
"Content-Type: application/json",
"Content-Length: " . strlen($this->data)
]);
}catch(\Throwable $e){
}
Internet::postURL($this->endpoint, $this->data, 5, [
"Content-Type: application/json",
"Content-Length: " . strlen($this->data)
]);
}
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\utils\Utils;
abstract class Task{
/** @var TaskHandler */
@ -46,6 +48,10 @@ abstract class Task{
return -1;
}
public function getName() : string{
return Utils::getNiceClassName($this);
}
/**
* @param TaskHandler|null $taskHandler
*/

View File

@ -66,7 +66,7 @@ class TaskHandler{
$this->taskId = $taskId;
$this->delay = $delay;
$this->period = $period;
$this->taskName = get_class($task);
$this->taskName = $task->getName();
$this->ownerName = $ownerName ?? "Unknown";
$this->timings = Timings::getScheduledTaskTimings($this, $period);
$this->task->setHandler($this);
@ -135,10 +135,6 @@ class TaskHandler{
return $this->period;
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
*/
public function cancel(){
try{
if(!$this->isCancelled()){

View File

@ -74,8 +74,13 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
$this->inventory->removeAllViewers(true);
if($this->doubleInventory !== null){
$this->doubleInventory->removeAllViewers(true);
$this->doubleInventory->invalidate();
if($this->isPaired() and $this->level->isChunkLoaded($this->pairX >> 4, $this->pairZ >> 4)){
$this->doubleInventory->removeAllViewers(true);
$this->doubleInventory->invalidate();
if(($pair = $this->getPair()) !== null){
$pair->doubleInventory = null;
}
}
$this->doubleInventory = null;
}
@ -117,9 +122,9 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
$this->doubleInventory = $pair->doubleInventory;
}else{
if(($pair->x + ($pair->z << 15)) > ($this->x + ($this->z << 15))){ //Order them correctly
$this->doubleInventory = new DoubleChestInventory($pair, $this);
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair, $this);
}else{
$this->doubleInventory = new DoubleChestInventory($this, $pair);
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this, $pair);
}
}
}

View File

@ -52,6 +52,23 @@ class Utils{
}
}
/**
* Returns a readable identifier for the class of the given object. Sanitizes class names for anonymous classes.
*
* @param object $obj
*
* @return string
* @throws \ReflectionException
*/
public static function getNiceClassName(object $obj) : string{
$reflect = new \ReflectionClass($obj);
if($reflect->isAnonymous()){
return "anonymous@" . self::cleanPath($reflect->getFileName()) . "#L" . $reflect->getStartLine();
}
return $reflect->getName();
}
/**
* Gets this machine / server instance unique ID
* Returns a hash, the first 32 characters (or 16 if raw)

View File

@ -12,13 +12,10 @@ if exist bin\php\php.exe (
if exist PocketMine-MP.phar (
set POCKETMINE_FILE=PocketMine-MP.phar
) else (
if exist src\pocketmine\PocketMine.php (
set POCKETMINE_FILE=src\pocketmine\PocketMine.php
) else (
echo Couldn't find a valid PocketMine-MP installation
pause
exit 1
)
echo PocketMine-MP.phar not found
echo Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases
pause
exit 1
)
if exist bin\mintty.exe (

View File

@ -18,10 +18,9 @@ if($php -ne ""){
if($file -eq ""){
if(Test-Path "PocketMine-MP.phar"){
$file = "PocketMine-MP.phar"
}elseif(Test-Path "src\pocketmine\PocketMine.php"){
$file = "src\pocketmine\PocketMine.php"
}else{
echo "Couldn't find a valid PocketMine-MP installation"
echo "PocketMine-MP.phar not found"
echo "Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases"
pause
exit 1
}

View File

@ -36,10 +36,9 @@ fi
if [ "$POCKETMINE_FILE" == "" ]; then
if [ -f ./PocketMine-MP.phar ]; then
POCKETMINE_FILE="./PocketMine-MP.phar"
elif [ -f ./src/pocketmine/PocketMine.php ]; then
POCKETMINE_FILE="./src/pocketmine/PocketMine.php"
else
echo "Couldn't find a valid PocketMine-MP installation"
echo "PocketMine-MP.phar not found"
echo "Downloads can be found at https://github.com/pmmp/PocketMine-MP/releases"
exit 1
fi
fi

View File

@ -43,6 +43,15 @@ class ItemTest extends TestCase{
self::assertTrue($item->equals($item2));
}
/**
* Test that same items without NBT are considered equal
*/
public function testItemEqualsNoNbt() : void{
$item1 = ItemFactory::get(Item::DIAMOND_SWORD);
$item2 = clone $item1;
self::assertTrue($item1->equals($item2));
}
/**
* Tests that blocks are considered to be valid registered items
*/