Compare commits

..

46 Commits
3.6.2 ... 3.7.0

Author SHA1 Message Date
14a6779e08 Release 3.7.0 2019-03-20 19:17:41 +00:00
cce99b07af 1.10.0 updates 2019-03-20 19:17:41 +00:00
baa094a2d1 3.6.6 is next 2019-03-10 11:49:31 +00:00
f2ff510597 Release 3.6.5 2019-03-10 11:49:06 +00:00
562179bdd6 Backport 58cafc853f: s/level/world (strings only)
we should look at doing this for code too, but for now I'm not planning to break everyone's plugins.
2019-03-10 11:35:46 +00:00
5c12bee874 Backport other part of 2bffd5cc1c: Add timer measurements for autosave 2019-03-10 11:20:51 +00:00
99606bbe23 beware possible API break 2019-03-09 19:33:46 +00:00
a1d50de12e OOF 2019-03-09 19:31:13 +00:00
4252c5914b Backport 93cd00ae8f: Remove dead settings from pocketmine.yml 2019-03-09 19:21:39 +00:00
0659d2fbef Backport 6bd43a8215: Firehose auto-tick-rate anti-feature, closes #2665 2019-03-09 19:20:53 +00:00
10612acace Partial backport of 2bffd5cc1c to 3.6 2019-03-09 19:10:09 +00:00
1d810f8aeb Backport c3e66e0adc to 3.6 2019-03-09 19:09:05 +00:00
414104851a LevelDB: Mark chunks as changed when upgraded from an older chunk format 2019-03-09 19:02:34 +00:00
c0bed03a2a Update PlayerRespawnEvent.php (#2797)
removed obsolete comment
2019-03-05 16:28:58 +00:00
d25c84acff Fixed RegionLoader corrupting location table when too-large chunks are discovered
this was making the location table point to an offset that did not yet exist, which caused the region header consistency check to discard the region as corrupted the next time it was loaded.
2019-03-05 12:09:27 +00:00
55994e08db RegionLoader: make some error messages more detailed 2019-03-05 11:18:02 +00:00
6f5d4d6b80 RegionLoader: fixed handling of invalid chunk coordinates 2019-03-05 10:52:36 +00:00
df1ef7fe0c Improve RegionLoader tests 2019-03-05 10:51:44 +00:00
20a25a69df Force emission of output buffer contents on crash 2019-03-04 12:01:30 +00:00
faca610594 NetworkBinaryStream: remove dead field 2019-03-03 12:38:03 +00:00
91603dc2d6 3.6.5 is next 2019-03-03 11:50:10 +00:00
af90e18b18 Release 3.6.4 2019-03-03 11:49:07 +00:00
ab5b4d112b BaseInventory: fixed items with userdata stacking with items without userdata 2019-03-03 11:12:36 +00:00
a30b1fb6d5 Inventory: Add failing test case for itemstack NBT duplication 2019-03-03 11:11:33 +00:00
20b4723728 Player: fixed held slot being out of sync after dying, closes #2788
it appears this premature optimization dates back to the days when PlayerHotbarPacket was not useless.
2019-02-27 09:26:56 +00:00
d1ced0ffc6 Player: fixed XP not dropping on death 2019-02-27 09:22:52 +00:00
2164dbae67 Fixed reloaded arrows not despawning, closes #2781 2019-02-26 19:58:21 +00:00
6c92a2e88b Ladder: be more strict about resetting fall distance
closes #2790
2019-02-26 19:54:57 +00:00
97deadc59f PackedIce: fixed dropping without silk touch, closes #2789 2019-02-26 19:48:18 +00:00
0c3b136a8d Player: fixed removeWindow() sometimes removing GUI / crashing clients 2019-02-24 12:14:19 +00:00
79b7e08e60 Silence NetworkStackLatencyPacket spam from dev builds 2019-02-23 11:03:50 +00:00
2540dacdd7 PluginManager: fixed suffix split handling 2019-02-23 10:51:06 +00:00
f1078e3909 3.6.4 is next 2019-02-22 18:07:48 +00:00
2f43b054de Release 3.6.3 2019-02-22 18:07:00 +00:00
23b5d64535 Merge branch '3.5' into 3.6 2019-02-22 18:03:51 +00:00
9afa0e5483 Release 3.5.13 2019-02-22 17:56:44 +00:00
4eaea54b0e TaskScheduler: fixed wrong typehints
These methods never return null.
2019-02-22 17:47:28 +00:00
6b51bf4a80 Merge branch '3.5' into 3.6 2019-02-18 13:11:16 +00:00
cba8d86c4f Fixed Fire Aspect not working on TNT 2019-02-18 13:11:08 +00:00
2e834c8f5c Merge branch '3.5' into 3.6 2019-02-17 16:10:47 -05:00
f9873e9108 ItemFrame: fixed hardness 2019-02-17 16:10:21 -05:00
074baf7e1c Merge branch '3.5' into 3.6 2019-02-17 17:08:15 +00:00
2e0dd574e0 Set default health attribute value when changing max health, closes #2771 2019-02-17 17:06:32 +00:00
e16d8e31af Merge branch '3.5' into 3.6 2019-02-17 11:33:46 +00:00
3c93a57397 Entity: add a hack to prevent client-side movement when entity is not moving
this fixes #2227.
2019-02-17 11:33:34 +00:00
e2e927b328 3.6.3 is next 2019-02-14 17:17:18 +00:00
43 changed files with 682 additions and 148 deletions

View File

@ -104,6 +104,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket;
@ -2147,6 +2148,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->dataPacket($pk);
$this->sendDataPacket(new AvailableEntityIdentifiersPacket());
$this->sendDataPacket(new BiomeDefinitionListPacket());
$this->level->sendTime($this);
@ -3627,7 +3629,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
if($this->inventory !== null){
$this->inventory->setHeldItemIndex(0, false); //This is already handled when sending contents, don't send it twice
$this->inventory->setHeldItemIndex(0);
$this->inventory->clearAll();
}
if($this->armorInventory !== null){
@ -3635,6 +3637,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
//TODO: allow this number to be manipulated during PlayerDeathEvent
$this->level->dropExperience($this, $this->getXpDropAmount());
$this->setXpAndProgress(0, 0);
if($ev->getDeathMessage() != ""){
$this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS);
}
@ -3904,8 +3910,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
throw new \InvalidArgumentException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
}
$inventory->close($this);
if($id !== null){
$inventory->close($this);
unset($this->windows[$hash], $this->windowIndex[$id], $this->permanentWindows[$id]);
}
}

View File

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

View File

@ -122,7 +122,6 @@ use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function floor;
use function function_exists;
use function get_class;
use function getmypid;
@ -142,6 +141,7 @@ use function max;
use function microtime;
use function min;
use function mkdir;
use function ob_end_flush;
use function pcntl_signal;
use function pcntl_signal_dispatch;
use function preg_replace;
@ -294,15 +294,6 @@ class Server{
/** @var int */
public $networkCompressionLevel = 7;
/** @var bool */
private $autoTickRate = true;
/** @var int */
private $autoTickRateLimit = 20;
/** @var bool */
private $alwaysTickPlayers = false;
/** @var int */
private $baseTickRate = 1;
/** @var int */
private $autoSaveTicker = 0;
/** @var int */
@ -1055,7 +1046,7 @@ class Server{
*/
public function unloadLevel(Level $level, bool $forceUnload = false) : bool{
if($level === $this->getDefaultLevel() and !$forceUnload){
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
throw new \InvalidStateException("The default world cannot be unloaded while running, please switch worlds.");
}
return $level->unload($forceUnload);
@ -1081,7 +1072,7 @@ class Server{
*/
public function loadLevel(string $name) : bool{
if(trim($name) === ""){
throw new LevelException("Invalid empty level name");
throw new LevelException("Invalid empty world name");
}
if($this->isLevelLoaded($name)){
return true;
@ -1108,8 +1099,6 @@ class Server{
(new LevelLoadEvent($level))->call();
$level->setTickRate($this->baseTickRate);
return true;
}
@ -1141,7 +1130,7 @@ class Server{
if(($providerClass = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "pmanvil"))) === null){
$providerClass = LevelProviderManager::getProviderByName("pmanvil");
if($providerClass === null){
throw new \InvalidStateException("Default level provider has not been registered");
throw new \InvalidStateException("Default world provider has not been registered");
}
}
@ -1153,8 +1142,6 @@ class Server{
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
$level->setTickRate($this->baseTickRate);
(new LevelInitEvent($level))->call();
(new LevelLoadEvent($level))->call();
@ -1592,11 +1579,6 @@ class Server{
}
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
$this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true);
$this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20);
$this->alwaysTickPlayers = (bool) $this->getProperty("level-settings.always-tick-players", false);
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
@ -2037,7 +2019,7 @@ class Server{
}
public function reload(){
$this->logger->info("Saving levels...");
$this->logger->info("Saving worlds...");
foreach($this->levels as $level){
$level->save();
@ -2115,7 +2097,7 @@ class Server{
$player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed"));
}
$this->getLogger()->debug("Unloading all levels");
$this->getLogger()->debug("Unloading all worlds");
foreach($this->getLevels() as $level){
$this->unloadLevel($level, true);
}
@ -2217,6 +2199,7 @@ class Server{
* @param array|null $trace
*/
public function exceptionHandler(\Throwable $e, $trace = null){
while(@ob_end_flush()){}
global $lastError;
if($trace === null){
@ -2248,6 +2231,7 @@ class Server{
}
public function crashDump(){
while(@ob_end_flush()){}
if(!$this->isRunning){
return;
}
@ -2428,8 +2412,6 @@ class Server{
foreach($this->players as $p){
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers and $p->spawned){
$p->onUpdate($currentTick);
}
}
@ -2439,32 +2421,13 @@ class Server{
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
continue;
}
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
continue;
}
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$level->tickRateCounter = $level->getTickRate();
}
if($tickMs >= 50){
$this->getLogger()->debug(sprintf("World \"%s\" took too long to tick: %gms (%g ticks)", $level->getName(), $tickMs, round($tickMs / 50, 2)));
}
}
}
@ -2617,7 +2580,10 @@ class Server{
if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){
$this->autoSaveTicker = 0;
$this->getLogger()->debug("[Auto Save] Saving worlds...");
$start = microtime(true);
$this->doAutoSave();
$this->getLogger()->debug("[Auto Save] Save completed in " . round(microtime(true) - $start, 3) . "s");
}
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){

View File

@ -112,4 +112,8 @@ class ItemFrame extends Flowable{
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getHardness() : float{
return 0.25;
}
}

View File

@ -58,8 +58,10 @@ class Ladder extends Transparent{
}
public function onEntityCollide(Entity $entity) : void{
$entity->resetFallDistance();
$entity->onGround = true;
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
class PackedIce extends Solid{
protected $id = self::PACKED_ICE;
@ -46,4 +48,8 @@ class PackedIce extends Solid{
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
}

View File

@ -25,6 +25,8 @@ namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FlintSteel;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -51,8 +53,10 @@ class TNT extends Solid{
}
public function onActivate(Item $item, Player $player = null) : bool{
if($item instanceof FlintSteel){
$item->applyDamage(1);
if($item instanceof FlintSteel or $item->hasEnchantment(Enchantment::FIRE_ASPECT)){
if($item instanceof Durable){
$item->applyDamage(1);
}
$this->ignite();
return true;
}

View File

@ -26,6 +26,8 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\lang\TranslationContainer;
use function microtime;
use function round;
class SaveCommand extends VanillaCommand{
@ -43,7 +45,8 @@ class SaveCommand extends VanillaCommand{
return true;
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.start"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.start"));
$start = microtime(true);
foreach($sender->getServer()->getOnlinePlayers() as $player){
$player->save();
@ -53,7 +56,7 @@ class SaveCommand extends VanillaCommand{
$level->save(true);
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.success"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.success", [round(microtime(true) - $start, 3)]));
return true;
}

View File

@ -108,13 +108,11 @@ class StatusCommand extends VanillaCommand{
foreach($server->getLevels() as $level){
$levelName = $level->getFolderName() !== $level->getName() ? " (" . $level->getName() . ")" : "";
$timeColor = ($level->getTickRate() > 1 or $level->getTickRateTime() > 40) ? TextFormat::RED : TextFormat::YELLOW;
$tickRate = $level->getTickRate() > 1 ? " (tick rate " . $level->getTickRate() . ")" : "";
$timeColor = $level->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
$sender->sendMessage(TextFormat::GOLD . "World \"{$level->getFolderName()}\"$levelName: " .
TextFormat::RED . number_format(count($level->getChunks())) . TextFormat::GREEN . " chunks, " .
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities, " .
TextFormat::RED . number_format(count($level->getTiles())) . TextFormat::GREEN . " tiles. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms" . $tickRate
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms"
);
}

View File

@ -1169,6 +1169,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
protected function updateMovement(bool $teleport = false) : void{
//TODO: hack for client-side AI interference: prevent client sided movement when motion is 0
$this->setImmobile($this->motion->x == 0 and $this->motion->y == 0 and $this->motion->z == 0);
$diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2;
$diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2;
@ -1637,7 +1640,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
assert(abs($dx) <= 20 and abs($dy) <= 20 and abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
$list = $this->level->getCollisionCubes($this, $this->level->getTickRate() > 1 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
//TODO: bad hack here will cause unexpected behaviour under heavy lag
$list = $this->level->getCollisionCubes($this, $this->level->getTickRateTime() > 50 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
foreach($list as $bb){
$dy = $bb->calculateYOffset($this->boundingBox, $dy);

View File

@ -152,7 +152,7 @@ abstract class Living extends Entity implements Damageable{
}
public function setMaxHealth(int $amount) : void{
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount)->setDefaultValue($amount);
}
public function getAbsorption() : float{

View File

@ -123,7 +123,7 @@ class Arrow extends Projectile{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->isCollided){
if($this->blockHit !== null){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();

View File

@ -27,7 +27,7 @@ use pocketmine\level\Position;
use pocketmine\Player;
/**
* Called when a player is respawned (or first time spawned)
* Called when a player is respawned
*/
class PlayerRespawnEvent extends PlayerEvent{
/** @var Position */

View File

@ -253,11 +253,9 @@ abstract class BaseInventory implements Inventory{
public function canAddItem(Item $item) : bool{
$item = clone $item;
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasCompoundTag();
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->equals($slot, $checkDamage, $checkTags)){
if($item->equals($slot)){
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
$item->setCount($item->getCount() - $diff);
}

View File

@ -204,6 +204,7 @@ interface ItemIds extends BlockIds{
public const DARK_OAK_DOOR = 431;
public const CHORUS_FRUIT = 432;
public const CHORUS_FRUIT_POPPED = 433;
public const BANNER_PATTERN = 434;
public const DRAGON_BREATH = 437;
public const SPLASH_POTION = 438;
@ -237,6 +238,14 @@ interface ItemIds extends BlockIds{
public const HEART_OF_THE_SEA = 467;
public const TURTLE_SHELL_PIECE = 468;
public const TURTLE_HELMET = 469;
public const PHANTOM_MEMBRANE = 470;
public const CROSSBOW = 471;
public const SPRUCE_SIGN = 472;
public const BIRCH_SIGN = 473;
public const JUNGLE_SIGN = 474;
public const ACACIA_SIGN = 475;
public const DARKOAK_SIGN = 476;
public const SWEET_BERRIES = 477;
public const COMPOUND = 499;
public const RECORD_13 = 500;
@ -252,4 +261,6 @@ interface ItemIds extends BlockIds{
public const RECORD_11 = 510;
public const RECORD_WAIT = 511;
public const SHIELD = 513;
}

View File

@ -74,7 +74,7 @@ class Explosion{
*/
public function __construct(Position $center, float $size, $what = null){
if(!$center->isValid()){
throw new \InvalidArgumentException("Position does not have a valid level");
throw new \InvalidArgumentException("Position does not have a valid world");
}
$this->source = $center;
$this->level = $center->getLevel();

View File

@ -261,11 +261,12 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelTimings */
public $timings;
/** @var int */
private $tickRate;
/** @var int */
public $tickRateTime = 0;
/** @var int */
/**
* @deprecated
* @var int
*/
public $tickRateCounter = 0;
/** @var bool */
@ -411,19 +412,26 @@ class Level implements ChunkManager, Metadatable{
$this->timings = new LevelTimings($this);
$this->temporalPosition = new Position(0, 0, 0, $this);
$this->temporalVector = new Vector3(0, 0, 0);
$this->tickRate = 1;
}
/**
* @deprecated
* @return int
*/
public function getTickRate() : int{
return $this->tickRate;
return 1;
}
public function getTickRateTime() : float{
return $this->tickRateTime;
}
/**
* @deprecated does nothing
* @param int $tickRate
*/
public function setTickRate(int $tickRate){
$this->tickRate = $tickRate;
}
public function registerGeneratorToWorker(int $worker) : void{
@ -466,7 +474,7 @@ class Level implements ChunkManager, Metadatable{
public function close(){
if($this->closed){
throw new \InvalidStateException("Tried to close a level which is already closed");
throw new \InvalidStateException("Tried to close a world which is already closed");
}
foreach($this->chunks as $chunk){
@ -580,7 +588,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function unload(bool $force = false) : bool{
if($this->doingTick and !$force){
throw new \InvalidStateException("Cannot unload a level during level tick");
throw new \InvalidStateException("Cannot unload a world during world tick");
}
$ev = new LevelUnloadEvent($this);
@ -599,7 +607,7 @@ class Level implements ChunkManager, Metadatable{
$defaultLevel = $this->server->getDefaultLevel();
foreach($this->getPlayers() as $player){
if($this === $defaultLevel or $defaultLevel === null){
$player->close($player->getLeaveMessage(), "Forced default level unload");
$player->close($player->getLeaveMessage(), "Forced default world unload");
}elseif($defaultLevel instanceof Level){
$player->teleport($this->server->getDefaultLevel()->getSafeSpawn());
}
@ -777,7 +785,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function doTick(int $currentTick){
if($this->closed){
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
throw new \InvalidStateException("Attempted to tick a world which has been closed");
}
$this->timings->doTick->startTiming();
@ -2691,10 +2699,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addEntity(Entity $entity){
if($entity->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to world");
}
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2712,7 +2720,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeEntity(Entity $entity){
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2731,10 +2739,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addTile(Tile $tile){
if($tile->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to world");
}
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
$chunkX = $tile->getFloorX() >> 4;
@ -2757,7 +2765,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeTile(Tile $tile){
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
unset($this->tiles[$tile->getId()], $this->updateTiles[$tile->getId()]);

View File

@ -64,7 +64,7 @@ class Position extends Vector3{
*/
public function getLevel(){
if($this->level !== null and $this->level->isClosed()){
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded Level");
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded world");
$this->level = null;
}
@ -82,7 +82,7 @@ class Position extends Vector3{
*/
public function setLevel(Level $level = null){
if($level !== null and $level->isClosed()){
throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used");
throw new \InvalidArgumentException("Specified world has been unloaded and cannot be used");
}
$this->level = $level;

View File

@ -74,10 +74,10 @@ class ChunkRequestTask extends AsyncTask{
$batch->isEncoded = true;
$level->chunkRequestCallback($this->chunkX, $this->chunkZ, $batch);
}else{
$server->getLogger()->error("Chunk request for level #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
$server->getLogger()->error("Chunk request for world #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
}
}else{
$server->getLogger()->debug("Dropped chunk task due to level not loaded");
$server->getLogger()->debug("Dropped chunk task due to world not loaded");
}
}
}

View File

@ -311,6 +311,7 @@ class LevelDB extends BaseLevelProvider{
$lightPopulated = true;
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
$binaryStream = new BinaryStream();
@ -326,6 +327,9 @@ class LevelDB extends BaseLevelProvider{
$binaryStream->setBuffer($data, 0);
$subChunkVersion = $binaryStream->getByte();
if($subChunkVersion < self::CURRENT_LEVEL_SUBCHUNK_VERSION){
$hasBeenUpgraded = true;
}
switch($subChunkVersion){
case 0:
@ -334,6 +338,7 @@ class LevelDB extends BaseLevelProvider{
if($chunkVersion < 4){
$blockSkyLight = $binaryStream->get(2048);
$blockLight = $binaryStream->get(2048);
$hasBeenUpgraded = true; //drop saved light
}else{
//Mojang didn't bother changing the subchunk version when they stopped saving sky light -_-
$blockSkyLight = "";
@ -453,6 +458,7 @@ class LevelDB extends BaseLevelProvider{
$chunk->setGenerated(true);
$chunk->setPopulated(true);
$chunk->setLightPopulated($lightPopulated);
$chunk->setChanged($hasBeenUpgraded); //trigger rewriting chunk to disk if it was converted from an older format
return $chunk;
}

View File

@ -124,9 +124,6 @@ class RegionLoader{
*/
public function readChunk(int $x, int $z) : ?string{
$index = self::getChunkOffset($x, $z);
if($index < 0 or $index >= 4096){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
$this->lastUsed = time();
@ -143,15 +140,13 @@ class RegionLoader{
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
if($length >= self::MAX_SECTOR_LENGTH){
$this->locationTable[$index][0] = ++$this->lastSector;
$this->locationTable[$index][1] = 1;
throw new CorruptedChunkException("Corrupted chunk header detected (sector count larger than max)");
throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")");
}
return null;
}
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)");
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)");
$this->locationTable[$index][1] = $length >> 12;
$this->writeLocationIndex($index);
}
@ -169,10 +164,25 @@ class RegionLoader{
return substr($chunkData, 1);
}
/**
* @param int $x
* @param int $z
*
* @return bool
* @throws \InvalidArgumentException
*/
public function chunkExists(int $x, int $z) : bool{
return $this->isChunkGenerated(self::getChunkOffset($x, $z));
}
/**
* @param int $x
* @param int $z
* @param string $chunkData
*
* @throws ChunkException
* @throws \InvalidArgumentException
*/
public function writeChunk(int $x, int $z, string $chunkData){
$this->lastUsed = time();
@ -202,14 +212,40 @@ class RegionLoader{
}
}
/**
* @param int $x
* @param int $z
*
* @throws \InvalidArgumentException
*/
public function removeChunk(int $x, int $z){
$index = self::getChunkOffset($x, $z);
$this->locationTable[$index][0] = 0;
$this->locationTable[$index][1] = 0;
}
/**
* @param int $x
* @param int $z
*
* @return int
* @throws \InvalidArgumentException
*/
protected static function getChunkOffset(int $x, int $z) : int{
return $x + ($z << 5);
if($x < 0 or $x > 31 or $z < 0 or $z > 31){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
return $x | ($z << 5);
}
/**
* @param int $offset
* @param int &$x
* @param int &$z
*/
protected static function getChunkCoords(int $offset, ?int &$x, ?int &$z) : void{
$x = $offset & 0x1f;
$z = ($offset >> 5) & 0x1f;
}
/**
@ -237,18 +273,23 @@ class RegionLoader{
}
$data = unpack("N*", $headerRaw);
/** @var int[] $usedOffsets */
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
if($offset !== 0){
fseek($this->filePointer, $offset << 12);
self::getChunkCoords($i, $x, $z);
$fileOffset = $offset << 12;
fseek($this->filePointer, $fileOffset);
if(fgetc($this->filePointer) === false){ //Try and read from the location
throw new CorruptedRegionException("Region file location offset points to invalid location");
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
}elseif(isset($usedOffsets[$offset])){
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
}else{
$usedOffsets[$offset] = true;
$usedOffsets[$offset] = $i;
}
}

View File

@ -29,6 +29,7 @@ use pocketmine\entity\Attribute;
use pocketmine\entity\Entity;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\ItemIds;
use pocketmine\math\Vector3;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
@ -40,8 +41,6 @@ use function count;
use function strlen;
class NetworkBinaryStream extends BinaryStream{
/** @var NetworkLittleEndianNBTStream */
private static $nbtSerializer = null;
public function getString() : string{
return $this->get($this->getUnsignedVarInt());
@ -106,6 +105,10 @@ class NetworkBinaryStream extends BinaryStream{
$this->getString();
}
if($id === ItemIds::SHIELD){
$this->getVarLong(); //"blocking tick" (ffs mojang)
}
return ItemFactory::get($id, $data, $cnt, $nbt);
}
@ -131,6 +134,10 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
$this->putVarInt(0); //CanDestroy entry count (TODO)
if($item->getId() === ItemIds::SHIELD){
$this->putVarLong(0); //"blocking tick" (ffs mojang)
}
}
/**

View File

@ -30,6 +30,7 @@ use pocketmine\network\mcpe\protocol\AddPaintingPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AutomationClientConnectPacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
@ -68,6 +69,7 @@ use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LecternUpdatePacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
@ -144,7 +146,7 @@ use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdateSoftEnumPacket;
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
use pocketmine\network\mcpe\protocol\WSConnectPacket;
use pocketmine\network\mcpe\protocol\VideoStreamConnectPacket;
abstract class NetworkSession{
@ -522,7 +524,7 @@ abstract class NetworkSession{
return false;
}
public function handleWSConnect(WSConnectPacket $packet) : bool{
public function handleAutomationClientConnect(AutomationClientConnectPacket $packet) : bool{
return false;
}
@ -634,4 +636,12 @@ abstract class NetworkSession{
return false;
}
public function handleLecternUpdate(LecternUpdatePacket $packet) : bool{
return false;
}
public function handleVideoStreamConnect(VideoStreamConnectPacket $packet) : bool{
return false;
}
}

View File

@ -51,6 +51,7 @@ use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
@ -300,4 +301,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
}
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
return true; //TODO: implement this properly - this is here to silence debug spam from MCPE dev builds
}
}

View File

@ -27,8 +27,8 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
class WSConnectPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::W_S_CONNECT_PACKET;
class AutomationClientConnectPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::AUTOMATION_CLIENT_CONNECT_PACKET;
/** @var string */
public $serverUri;
@ -42,6 +42,6 @@ class WSConnectPacket extends DataPacket{
}
public function handle(NetworkSession $session) : bool{
return $session->handleWSConnect($this);
return $session->handleAutomationClientConnect($this);
}
}

View File

@ -220,21 +220,22 @@ class AvailableCommandsPacket extends DataPacket{
$parameter->paramName = $this->getString();
$parameter->paramType = $this->getLInt();
$parameter->isOptional = $this->getBool();
$parameter->byte1 = $this->getByte();
if($parameter->paramType & self::ARG_FLAG_ENUM){
$index = ($parameter->paramType & 0xffff);
$parameter->enum = $this->enums[$index] ?? null;
if($parameter->enum === null){
throw new \UnexpectedValueException("expected enum at $index, but got none");
throw new \UnexpectedValueException("deserializing $retval->commandName parameter $parameter->paramName: expected enum at $index, but got none");
}
}elseif($parameter->paramType & self::ARG_FLAG_POSTFIX){
$index = ($parameter->paramType & 0xffff);
$parameter->postfix = $this->postfixes[$index] ?? null;
if($parameter->postfix === null){
throw new \UnexpectedValueException("expected postfix at $index, but got none");
throw new \UnexpectedValueException("deserializing $retval->commandName parameter $parameter->paramName: expected postfix at $index, but got none");
}
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
throw new \UnexpectedValueException("Invalid parameter type 0x" . dechex($parameter->paramType));
throw new \UnexpectedValueException("deserializing $retval->commandName parameter $parameter->paramName: Invalid parameter type 0x" . dechex($parameter->paramType));
}
$retval->overloads[$overloadIndex][$paramIndex] = $parameter;
@ -277,6 +278,7 @@ class AvailableCommandsPacket extends DataPacket{
$this->putLInt($type);
$this->putBool($parameter->isOptional);
$this->putByte($parameter->byte1);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -30,6 +30,7 @@ use pocketmine\network\mcpe\NetworkSession;
class BiomeDefinitionListPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::BIOME_DEFINITION_LIST_PACKET;
public const HARDCODED_NBT_BLOB = "CgAKDWJhbWJvb19qdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTYmFtYm9vX2p1bmdsZV9oaWxscwUIZG93bmZhbGxmZmY/BQt0ZW1wZXJhdHVyZTMzcz8ACgViZWFjaAUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACgxiaXJjaF9mb3Jlc3QFCGRvd25mYWxsmpkZPwULdGVtcGVyYXR1cmWamRk/AAoSYmlyY2hfZm9yZXN0X2hpbGxzBQhkb3duZmFsbJqZGT8FC3RlbXBlcmF0dXJlmpkZPwAKGmJpcmNoX2ZvcmVzdF9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFGJpcmNoX2ZvcmVzdF9tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKCmNvbGRfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw9AAoKY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgpjb2xkX3RhaWdhBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKEGNvbGRfdGFpZ2FfaGlsbHMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmUAAAC/AAoSY29sZF90YWlnYV9tdXRhdGVkBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKD2RlZXBfY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8AChFkZWVwX2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChNkZWVwX2x1a2V3YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCmRlZXBfb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAoPZGVlcF93YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKBmRlc2VydAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgxkZXNlcnRfaGlsbHMFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoOZGVzZXJ0X211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoNZXh0cmVtZV9oaWxscwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4AChJleHRyZW1lX2hpbGxzX2VkZ2UFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoVZXh0cmVtZV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbJqZmT4FC3RlbXBlcmF0dXJlzcxMPgAKGGV4dHJlbWVfaGlsbHNfcGx1c190cmVlcwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACiBleHRyZW1lX2hpbGxzX3BsdXNfdHJlZXNfbXV0YXRlZAUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACg1mbG93ZXJfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKBmZvcmVzdAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgxmb3Jlc3RfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzMzM/AAoMZnJvemVuX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAAAAKDGZyb3plbl9yaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACgRoZWxsBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDWljZV9tb3VudGFpbnMFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAAAAAoKaWNlX3BsYWlucwUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChFpY2VfcGxhaW5zX3NwaWtlcwUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZQAAAAAACgZqdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoLanVuZ2xlX2VkZ2UFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzM3M/AAoTanVuZ2xlX2VkZ2VfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzcz8ACgxqdW5nbGVfaGlsbHMFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoOanVuZ2xlX211dGF0ZWQFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTbGVnYWN5X2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACg5sdWtld2FybV9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgptZWdhX3RhaWdhBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKEG1lZ2FfdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmWamZk+AAoEbWVzYQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgptZXNhX2JyeWNlBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDG1lc2FfcGxhdGVhdQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChRtZXNhX3BsYXRlYXVfbXV0YXRlZAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChJtZXNhX3BsYXRlYXVfc3RvbmUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoabWVzYV9wbGF0ZWF1X3N0b25lX211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoPbXVzaHJvb21faXNsYW5kBQhkb3duZmFsbAAAgD8FC3RlbXBlcmF0dXJlZmZmPwAKFW11c2hyb29tX2lzbGFuZF9zaG9yZQUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZWZmZj8ACgVvY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgZwbGFpbnMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmXNzEw/AAobcmVkd29vZF90YWlnYV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKFXJlZHdvb2RfdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgVyaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACg1yb29mZWRfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFXJvb2ZlZF9mb3Jlc3RfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgdzYXZhbm5hBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlmpmZPwAKD3NhdmFubmFfbXV0YXRlZAUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZc3MjD8ACg9zYXZhbm5hX3BsYXRlYXUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAIA/AAoXc2F2YW5uYV9wbGF0ZWF1X211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAIA/AAoLc3RvbmVfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoQc3VuZmxvd2VyX3BsYWlucwUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACglzd2FtcGxhbmQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoRc3dhbXBsYW5kX211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoFdGFpZ2EFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoLdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoNdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgd0aGVfZW5kBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCndhcm1fb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAA=";
/** @var string */
public $namedtag;
@ -38,7 +39,7 @@ class BiomeDefinitionListPacket extends DataPacket{
}
protected function encodePayload(){
$this->put($this->namedtag);
$this->put($this->namedtag ?? self::HARDCODED_NBT_BLOB);
}
public function handle(NetworkSession $session) : bool{

View File

@ -92,6 +92,7 @@ class InventoryTransactionPacket extends DataPacket{
$this->trData->itemInHand = $this->getSlot();
$this->trData->playerPos = $this->getVector3();
$this->trData->clickPos = $this->getVector3();
$this->trData->blockRuntimeId = $this->getUnsignedVarInt();
break;
case self::TYPE_USE_ITEM_ON_ENTITY:
$this->trData->entityRuntimeId = $this->getEntityRuntimeId();
@ -132,6 +133,7 @@ class InventoryTransactionPacket extends DataPacket{
$this->putSlot($this->trData->itemInHand);
$this->putVector3($this->trData->playerPos);
$this->putVector3($this->trData->clickPos);
$this->putUnsignedVarInt($this->trData->blockRuntimeId);
break;
case self::TYPE_USE_ITEM_ON_ENTITY:
$this->putEntityRuntimeId($this->trData->entityRuntimeId);

View File

@ -0,0 +1,59 @@
<?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 LecternUpdatePacket extends DataPacket/* implements ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::LECTERN_UPDATE_PACKET;
/** @var int */
public $page;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var bool */
public $dropBook;
protected function decodePayload() : void{
$this->page = $this->getByte();
$this->getBlockPosition($this->x, $this->y, $this->z);
$this->dropBook = $this->getBool();
}
protected function encodePayload() : void{
$this->putByte($this->page);
$this->putBlockPosition($this->x, $this->y, $this->z);
$this->putBool($this->dropBook);
}
public function handle(NetworkSession $session) : bool{
return $session->handleLecternUpdate($this);
}
}

View File

@ -126,7 +126,7 @@ class PacketPool{
static::registerPacket(new PurchaseReceiptPacket());
static::registerPacket(new PlayerSkinPacket());
static::registerPacket(new SubClientLoginPacket());
static::registerPacket(new WSConnectPacket());
static::registerPacket(new AutomationClientConnectPacket());
static::registerPacket(new SetLastHurtByPacket());
static::registerPacket(new BookEditPacket());
static::registerPacket(new NpcRequestPacket());
@ -154,6 +154,8 @@ class PacketPool{
static::registerPacket(new NetworkChunkPublisherUpdatePacket());
static::registerPacket(new BiomeDefinitionListPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new LecternUpdatePacket());
static::registerPacket(new VideoStreamConnectPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 332;
public const CURRENT_PROTOCOL = 340;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.9.0';
public const MINECRAFT_VERSION = 'v1.10.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.9.0';
public const MINECRAFT_VERSION_NETWORK = '1.10.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -143,7 +143,7 @@ interface ProtocolInfo{
public const PURCHASE_RECEIPT_PACKET = 0x5c;
public const PLAYER_SKIN_PACKET = 0x5d;
public const SUB_CLIENT_LOGIN_PACKET = 0x5e;
public const W_S_CONNECT_PACKET = 0x5f;
public const AUTOMATION_CLIENT_CONNECT_PACKET = 0x5f;
public const SET_LAST_HURT_BY_PACKET = 0x60;
public const BOOK_EDIT_PACKET = 0x61;
public const NPC_REQUEST_PACKET = 0x62;
@ -172,5 +172,7 @@ interface ProtocolInfo{
public const NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET = 0x79;
public const BIOME_DEFINITION_LIST_PACKET = 0x7a;
public const LEVEL_SOUND_EVENT_PACKET = 0x7b;
public const LECTERN_UPDATE_PACKET = 0x7c;
public const VIDEO_STREAM_CONNECT_PACKET = 0x7d;
}

View File

@ -0,0 +1,58 @@
<?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 VideoStreamConnectPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::VIDEO_STREAM_CONNECT_PACKET;
public const ACTION_CONNECT = 0;
public const ACTION_DISCONNECT = 1;
/** @var string */
public $serverUri;
/** @var float */
public $frameSendFrequency;
/** @var int */
public $action;
protected function decodePayload() : void{
$this->serverUri = $this->getString();
$this->frameSendFrequency = $this->getLFloat();
$this->action = $this->getByte();
}
protected function encodePayload() : void{
$this->putString($this->serverUri);
$this->putLFloat($this->frameSendFrequency);
$this->putByte($this->action);
}
public function handle(NetworkSession $session) : bool{
return $session->handleVideoStreamConnect($this);
}
}

View File

@ -30,6 +30,8 @@ class CommandParameter{
public $paramType;
/** @var bool */
public $isOptional;
/** @var int */
public $byte1 = 0; //unknown, always zero except for in /gamerule command
/** @var CommandEnum|null */
public $enum;
/** @var string|null */

View File

@ -384,14 +384,14 @@ class PluginManager{
*/
public function isCompatibleApi(string ...$versions) : bool{
$serverString = $this->server->getApiVersion();
$serverApi = array_pad(explode("-", $serverString), 2, "");
$serverApi = array_pad(explode("-", $serverString, 2), 2, "");
$serverNumbers = array_map("\intval", explode(".", $serverApi[0]));
foreach($versions as $version){
//Format: majorVersion.minorVersion.patch (3.0.0)
// or: majorVersion.minorVersion.patch-devBuild (3.0.0-alpha1)
if($version !== $serverString){
$pluginApi = array_pad(explode("-", $version), 2, ""); //0 = version, 1 = suffix (optional)
$pluginApi = array_pad(explode("-", $version, 2), 2, ""); //0 = version, 1 = suffix (optional)
if(strtoupper($pluginApi[1]) !== strtoupper($serverApi[1])){ //Different release phase (alpha vs. beta) or phase build (alpha.1 vs alpha.2)
continue;

View File

@ -247,5 +247,214 @@
"minecraft:structure_block": 252,
"minecraft:hard_glass": 253,
"minecraft:hard_stained_glass": 254,
"minecraft:reserved6": 255
}
"minecraft:reserved6": 255,
"minecraft:prismarine_stairs": 257,
"minecraft:dark_prismarine_stairs": 258,
"minecraft:prismarine_bricks_stairs": 259,
"minecraft:stripped_spruce_log": 260,
"minecraft:stripped_birch_log": 261,
"minecraft:stripped_jungle_log": 262,
"minecraft:stripped_acacia_log": 263,
"minecraft:stripped_dark_oak_log": 264,
"minecraft:stripped_oak_log": 265,
"minecraft:blue_ice": 266,
"minecraft:element_1": 267,
"minecraft:element_2": 268,
"minecraft:element_3": 269,
"minecraft:element_4": 270,
"minecraft:element_5": 271,
"minecraft:element_6": 272,
"minecraft:element_7": 273,
"minecraft:element_8": 274,
"minecraft:element_9": 275,
"minecraft:element_10": 276,
"minecraft:element_11": 277,
"minecraft:element_12": 278,
"minecraft:element_13": 279,
"minecraft:element_14": 280,
"minecraft:element_15": 281,
"minecraft:element_16": 282,
"minecraft:element_17": 283,
"minecraft:element_18": 284,
"minecraft:element_19": 285,
"minecraft:element_20": 286,
"minecraft:element_21": 287,
"minecraft:element_22": 288,
"minecraft:element_23": 289,
"minecraft:element_24": 290,
"minecraft:element_25": 291,
"minecraft:element_26": 292,
"minecraft:element_27": 293,
"minecraft:element_28": 294,
"minecraft:element_29": 295,
"minecraft:element_30": 296,
"minecraft:element_31": 297,
"minecraft:element_32": 298,
"minecraft:element_33": 299,
"minecraft:element_34": 300,
"minecraft:element_35": 301,
"minecraft:element_36": 302,
"minecraft:element_37": 303,
"minecraft:element_38": 304,
"minecraft:element_39": 305,
"minecraft:element_40": 306,
"minecraft:element_41": 307,
"minecraft:element_42": 308,
"minecraft:element_43": 309,
"minecraft:element_44": 310,
"minecraft:element_45": 311,
"minecraft:element_46": 312,
"minecraft:element_47": 313,
"minecraft:element_48": 314,
"minecraft:element_49": 315,
"minecraft:element_50": 316,
"minecraft:element_51": 317,
"minecraft:element_52": 318,
"minecraft:element_53": 319,
"minecraft:element_54": 320,
"minecraft:element_55": 321,
"minecraft:element_56": 322,
"minecraft:element_57": 323,
"minecraft:element_58": 324,
"minecraft:element_59": 325,
"minecraft:element_60": 326,
"minecraft:element_61": 327,
"minecraft:element_62": 328,
"minecraft:element_63": 329,
"minecraft:element_64": 330,
"minecraft:element_65": 331,
"minecraft:element_66": 332,
"minecraft:element_67": 333,
"minecraft:element_68": 334,
"minecraft:element_69": 335,
"minecraft:element_70": 336,
"minecraft:element_71": 337,
"minecraft:element_72": 338,
"minecraft:element_73": 339,
"minecraft:element_74": 340,
"minecraft:element_75": 341,
"minecraft:element_76": 342,
"minecraft:element_77": 343,
"minecraft:element_78": 344,
"minecraft:element_79": 345,
"minecraft:element_80": 346,
"minecraft:element_81": 347,
"minecraft:element_82": 348,
"minecraft:element_83": 349,
"minecraft:element_84": 350,
"minecraft:element_85": 351,
"minecraft:element_86": 352,
"minecraft:element_87": 353,
"minecraft:element_88": 354,
"minecraft:element_89": 355,
"minecraft:element_90": 356,
"minecraft:element_91": 357,
"minecraft:element_92": 358,
"minecraft:element_93": 359,
"minecraft:element_94": 360,
"minecraft:element_95": 361,
"minecraft:element_96": 362,
"minecraft:element_97": 363,
"minecraft:element_98": 364,
"minecraft:element_99": 365,
"minecraft:element_100": 366,
"minecraft:element_101": 367,
"minecraft:element_102": 368,
"minecraft:element_103": 369,
"minecraft:element_104": 370,
"minecraft:element_105": 371,
"minecraft:element_106": 372,
"minecraft:element_107": 373,
"minecraft:element_108": 374,
"minecraft:element_109": 375,
"minecraft:element_110": 376,
"minecraft:element_111": 377,
"minecraft:element_112": 378,
"minecraft:element_113": 379,
"minecraft:element_114": 380,
"minecraft:element_115": 381,
"minecraft:element_116": 382,
"minecraft:element_117": 383,
"minecraft:element_118": 384,
"minecraft:seagrass": 385,
"minecraft:coral": 386,
"minecraft:coral_block": 387,
"minecraft:coral_fan": 388,
"minecraft:coral_fan_dead": 389,
"minecraft:coral_fan_hang": 390,
"minecraft:coral_fan_hang2": 391,
"minecraft:coral_fan_hang3": 392,
"minecraft:kelp": 393,
"minecraft:dried_kelp_block": 394,
"minecraft:acacia_button": 395,
"minecraft:birch_button": 396,
"minecraft:dark_oak_button": 397,
"minecraft:jungle_button": 398,
"minecraft:spruce_button": 399,
"minecraft:acacia_trapdoor": 400,
"minecraft:birch_trapdoor": 401,
"minecraft:dark_oak_trapdoor": 402,
"minecraft:jungle_trapdoor": 403,
"minecraft:spruce_trapdoor": 404,
"minecraft:acacia_pressure_plate": 405,
"minecraft:birch_pressure_plate": 406,
"minecraft:dark_oak_pressure_plate": 407,
"minecraft:jungle_pressure_plate": 408,
"minecraft:spruce_pressure_plate": 409,
"minecraft:carved_pumpkin": 410,
"minecraft:sea_pickle": 411,
"minecraft:conduit": 412,
"minecraft:turtle_egg": 414,
"minecraft:bubble_column": 415,
"minecraft:barrier": 416,
"minecraft:stone_slab3": 417,
"minecraft:bamboo": 418,
"minecraft:bamboo_sapling": 419,
"minecraft:scaffolding": 420,
"minecraft:stone_slab4": 421,
"minecraft:double_stone_slab3": 422,
"minecraft:double_stone_slab4": 423,
"minecraft:granite_stairs": 424,
"minecraft:diorite_stairs": 425,
"minecraft:andesite_stairs": 426,
"minecraft:polished_granite_stairs": 427,
"minecraft:polished_diorite_stairs": 428,
"minecraft:polished_andesite_stairs": 429,
"minecraft:mossy_stone_brick_stairs": 430,
"minecraft:smooth_red_sandstone_stairs": 431,
"minecraft:smooth_sandstone_stairs": 432,
"minecraft:end_brick_stairs": 433,
"minecraft:mossy_cobblestone_stairs": 434,
"minecraft:normal_stone_stairs": 435,
"minecraft:spruce_standing_sign": 436,
"minecraft:spruce_wall_sign": 437,
"minecraft:smooth_stone": 438,
"minecraft:red_nether_brick_stairs": 439,
"minecraft:smooth_quartz_stairs": 440,
"minecraft:birch_standing_sign": 441,
"minecraft:birch_wall_sign": 442,
"minecraft:jungle_standing_sign": 443,
"minecraft:jungle_wall_sign": 444,
"minecraft:acacia_standing_sign": 445,
"minecraft:acacia_wall_sign": 446,
"minecraft:darkoak_standing_sign": 447,
"minecraft:darkoak_wall_sign": 448,
"minecraft:lectern": 449,
"minecraft:grindstone": 450,
"minecraft:blast_furnace": 451,
"minecraft:stonecutter_block": 452,
"minecraft:smoker": 453,
"minecraft:cartography_table": 455,
"minecraft:fletching_table": 456,
"minecraft:smithing_table": 457,
"minecraft:barrel": 458,
"minecraft:loom": 459,
"minecraft:bell": 461,
"minecraft:sweet_berry_bush": 462,
"minecraft:lantern": 463,
"minecraft:campfire": 464,
"minecraft:lava_cauldron": 465,
"minecraft:jigsaw": 466,
"minecraft:wood": 467,
"minecraft:composter": 468
}

View File

@ -109,13 +109,6 @@ player:
level-settings:
#The default format that levels will use when created
default-format: pmanvil
#Automatically change levels tick rate to maintain 20 ticks per second
auto-tick-rate: true
auto-tick-rate-limit: 20
#Sets the base tick rate (1 = 20 ticks per second, 2 = 10 ticks per second, etc.)
base-tick-rate: 1
#Tick all players each tick even when other settings disallow this.
always-tick-players: false
chunk-sending:
#To change server normal render distance, change view-distance in server.properties.

File diff suppressed because one or more lines are too long

View File

@ -64,7 +64,7 @@ class TaskScheduler{
/**
* @param Task $task
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleTask(Task $task){
return $this->addTask($task, -1, -1);
@ -74,7 +74,7 @@ class TaskScheduler{
* @param Task $task
* @param int $delay
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleDelayedTask(Task $task, int $delay){
return $this->addTask($task, $delay, -1);
@ -84,7 +84,7 @@ class TaskScheduler{
* @param Task $task
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleRepeatingTask(Task $task, int $period){
return $this->addTask($task, -1, $period);
@ -95,7 +95,7 @@ class TaskScheduler{
* @param int $delay
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*/
public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period){
return $this->addTask($task, $delay, $period);
@ -139,7 +139,7 @@ class TaskScheduler{
* @param int $delay
* @param int $period
*
* @return null|TaskHandler
* @return TaskHandler
*
* @throws \InvalidStateException
*/

View File

@ -0,0 +1,59 @@
<?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\inventory;
use PHPUnit\Framework\TestCase;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
class BaseInventoryTest extends TestCase{
public static function setUpBeforeClass(){
ItemFactory::init();
}
public function testAddItemDifferentUserData() : void{
$inv = new class extends BaseInventory{
public function getDefaultSize() : int{
return 1;
}
public function getName() : string{
return "";
}
};
$item1 = ItemFactory::get(Item::ARROW, 0, 1);
$item2 = ItemFactory::get(Item::ARROW, 0, 1)->setCustomName("TEST");
$inv->addItem(clone $item1);
self::assertFalse($inv->canAddItem($item2), "Item WITHOUT userdata should not stack with item WITH userdata");
self::assertNotEmpty($inv->addItem($item2));
$inv->clearAll();
self::assertEmpty($inv->getContents());
$inv->addItem(clone $item2);
self::assertFalse($inv->canAddItem($item1), "Item WITH userdata should not stack with item WITHOUT userdata");
self::assertNotEmpty($inv->addItem($item1));
}
}

View File

@ -25,28 +25,94 @@ namespace pocketmine\level\format\io\region;
use PHPUnit\Framework\TestCase;
use pocketmine\level\format\ChunkException;
use function file_exists;
use function random_bytes;
use function str_repeat;
use function sys_get_temp_dir;
use function unlink;
class RegionLoaderTest extends TestCase{
public function testChunkTooBig() : void{
$r = new RegionLoader(sys_get_temp_dir() . '/chunk_too_big.testregion_' . bin2hex(random_bytes(4)), 0, 0);
$r->open();
/** @var string */
private $regionPath;
/** @var RegionLoader */
private $region;
public function setUp(){
$this->regionPath = sys_get_temp_dir() . '/test.testregion';
if(file_exists($this->regionPath)){
unlink($this->regionPath);
}
$this->region = new RegionLoader($this->regionPath, 0, 0);
$this->region->open();
}
public function tearDown(){
$this->region->close();
if(file_exists($this->regionPath)){
unlink($this->regionPath);
}
}
public function testChunkTooBig() : void{
$this->expectException(ChunkException::class);
$r->writeChunk(0, 0, str_repeat("a", 1044476));
$this->region->writeChunk(0, 0, str_repeat("a", 1044476));
}
public function testChunkMaxSize() : void{
$data = str_repeat("a", 1044475);
$path = sys_get_temp_dir() . '/chunk_just_fits.testregion_' . bin2hex(random_bytes(4));
$r = new RegionLoader($path, 0, 0);
$r->open();
$this->region->writeChunk(0, 0, $data);
$this->region->close();
$r->writeChunk(0, 0, $data);
$r->close();
$r = new RegionLoader($path, 0, 0);
$r = new RegionLoader($this->regionPath, 0, 0);
$r->open();
self::assertSame($data, $r->readChunk(0, 0));
}
public function outOfBoundsCoordsProvider() : \Generator{
yield [-1, -1];
yield [32, 32];
yield [-1, 32];
yield [32, -1];
}
/**
* @dataProvider outOfBoundsCoordsProvider
* @param int $x
* @param int $z
*
* @throws ChunkException
* @throws \InvalidArgumentException
*/
public function testWriteChunkOutOfBounds(int $x, int $z) : void{
$this->expectException(\InvalidArgumentException::class);
$this->region->writeChunk($x, $z, str_repeat("\x00", 1000));
}
public function testReadWriteChunkInBounds() : void{
$dat = random_bytes(1000);
for($x = 0; $x < 32; ++$x){
for($z = 0; $z < 32; ++$z){
$this->region->writeChunk($x, $z, $dat);
}
}
for($x = 0; $x < 32; ++$x){
for($z = 0; $z < 32; ++$z){
self::assertSame($dat, $this->region->readChunk($x, $z));
}
}
}
/**
* @dataProvider outOfBoundsCoordsProvider
* @param int $x
* @param int $z
*
* @throws \InvalidArgumentException
* @throws \pocketmine\level\format\io\exception\CorruptedChunkException
*/
public function testReadChunkOutOfBounds(int $x, int $z) : void{
$this->expectException(\InvalidArgumentException::class);
$this->region->readChunk($x, $z);
}
}