Compare commits

...

77 Commits

Author SHA1 Message Date
5c26deb517 Release 3.7.1 2019-03-29 19:56:06 +00:00
8fcb44de7d Merge branch '3.6' into 3.7 2019-03-29 19:55:36 +00:00
bd2e7db3b9 Release 3.6.6 2019-03-29 19:49:30 +00:00
f4480c07ee Updated NBT dependency 2019-03-29 19:47:15 +00:00
c09e2301c8 Merge branch '3.6' into 3.7 2019-03-29 14:35:35 +00:00
8e9f787d33 Implement runtimeID table randomization, closes #2841 2019-03-29 14:35:06 +00:00
89833b3b68 Merge branch '3.6' into 3.7 2019-03-26 15:01:59 +00:00
a10a656a5d Network: add a README to mcpe namespace 2019-03-26 15:01:00 +00:00
2659ed8d91 AvailableCommandsPacket: Updated the argument type constants for 1.10. (#2824) 2019-03-22 15:07:15 +00:00
9c01ecbe7e Merge branch '3.6' into 3.7 2019-03-21 19:02:20 +00:00
6a55021779 Flat: Allow asterisk in preset layers
closes #2818
2019-03-21 18:59:50 +00:00
6ada261b04 3.7.1 is next 2019-03-20 19:39:36 +00:00
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
c3d80d711d Fixed autosave time report measurement not using correct unit (as per master) 2019-03-20 19:01:10 +00:00
1a8b33dafe AvailableCommandsPacket: Updated the ARG_TYPE constants
closes #2817
2019-03-18 08:58:07 +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
a8dab25201 Release 3.6.2 2019-02-14 17:15:19 +00:00
3de2b7969e Merge branch '3.5' into 3.6 2019-02-14 17:14:59 +00:00
8f486ea65d 3.5.13 is next 2019-02-14 17:14:34 +00:00
8e73842a93 Player: work around 1.9 command casing crash bug, closes #2761 2019-02-14 16:17:17 +00:00
e71e18fc88 Merge branch '3.5' into 3.6 2019-02-14 16:02:30 +00:00
10b72c895d Merge branch '3.5' into 3.6 2019-02-13 14:53:00 +00:00
372545e47e Merge branch '3.5' into 3.6 2019-02-12 16:56:53 +00:00
8bd8da4bc6 Merge branch '3.5' into 3.6 2019-02-10 17:15:48 +00:00
9da7c6af27 new metadata properties 2019-02-10 13:59:21 +00:00
109312284c 3.6.2 is next 2019-02-08 16:41:10 +00:00
51934614bc Release 3.6.1 2019-02-08 16:40:38 +00:00
9e89f65094 Fixed handling for some new blocks which slipped through the frontline 2019-02-08 16:38:17 +00:00
9562711b84 Updated BedrockData submodule to 1.9.0 2019-02-08 15:32:57 +00:00
30b49e0d22 Merge branch '3.5' into 3.6 2019-02-08 15:32:20 +00:00
278f37d3e0 3.6.1 is next 2019-02-07 22:07:31 +00:00
02a6ca84a9 Releass 3.6.0 2019-02-07 22:06:33 +00:00
b8703d5dff Protocol changes for 1.9.0 2019-02-07 21:56:42 +00:00
61 changed files with 1258 additions and 292 deletions

12
composer.lock generated
View File

@ -160,16 +160,16 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.6",
"version": "0.2.7",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "92eaf84dd61f700d3ec02ebd01b606cb5b1590d4"
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/92eaf84dd61f700d3ec02ebd01b606cb5b1590d4",
"reference": "92eaf84dd61f700d3ec02ebd01b606cb5b1590d4",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
"shasum": ""
},
"require": {
@ -194,10 +194,10 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.6",
"source": "https://github.com/pmmp/NBT/tree/0.2.7",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-02-07T16:28:11+00:00"
"time": "2019-03-29T19:39:42+00:00"
},
{
"name": "pocketmine/raklib",

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;
@ -720,7 +721,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$data = new CommandData();
$data->commandName = $command->getName();
//TODO: commands containing uppercase letters in the name crash 1.9.0 client
$data->commandName = strtolower($command->getName());
$data->commandDescription = $this->server->getLanguage()->translateString($command->getDescription());
$data->flags = 0;
$data->permission = 0;
@ -2146,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);
@ -2982,7 +2985,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$t = $this->level->getTile($pos);
if($t instanceof Spawnable){
$nbt = new NetworkLittleEndianNBTStream();
$compound = $nbt->read($packet->namedtag);
$_ = 0;
$compound = $nbt->read($packet->namedtag, false, $_, 512);
if(!($compound instanceof CompoundTag)){
throw new \InvalidArgumentException("Expected " . CompoundTag::class . " in block entity NBT, got " . (is_object($compound) ? get_class($compound) : gettype($compound)));
@ -3626,7 +3630,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){
@ -3634,6 +3638,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);
}
@ -3903,8 +3911,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.5.12";
const BASE_VERSION = "3.7.1";
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();
@ -1695,7 +1677,6 @@ class Server{
Entity::init();
Tile::init();
BlockFactory::init();
BlockFactory::registerStaticRuntimeIdMappings();
Enchantment::init();
ItemFactory::init();
Item::initCreativeItems();
@ -2037,7 +2018,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 +2096,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 +2198,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 +2230,7 @@ class Server{
}
public function crashDump(){
while(@ob_end_flush()){}
if(!$this->isRunning){
return;
}
@ -2428,8 +2411,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 +2420,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 +2579,11 @@ 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();
$time = (microtime(true) - $start);
$this->getLogger()->debug("[Auto Save] Save completed in " . ($time >= 1 ? round($time, 3) . "s" : round($time * 1000) . "ms"));
}
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){

View File

@ -36,6 +36,7 @@ use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\metadata\Metadatable;
use pocketmine\metadata\MetadataValue;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use function array_merge;
@ -117,7 +118,7 @@ class Block extends Position implements BlockIds, Metadatable{
* @return int
*/
public function getRuntimeId() : int{
return BlockFactory::toStaticRuntimeId($this->getId(), $this->getDamage());
return RuntimeBlockMapping::toStaticRuntimeId($this->getId(), $this->getDamage());
}
/**

View File

@ -25,9 +25,7 @@ namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\level\Position;
use function file_get_contents;
use function json_decode;
use function max;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use function min;
/**
@ -52,15 +50,6 @@ class BlockFactory{
/** @var \SplFixedArray<float> */
public static $blastResistance = null;
/** @var int[] */
public static $staticRuntimeIdMap = [];
/** @var int[] */
public static $legacyIdMap = [];
/** @var int */
private static $lastRuntimeId = 0;
/**
* Initializes the block factory. By default this is called only once on server start, however you may wish to use
* this if you need to reset the block factory back to its original defaults for whatever reason.
@ -427,16 +416,9 @@ class BlockFactory{
return $b !== null and !($b instanceof UnknownBlock);
}
public static function registerStaticRuntimeIdMappings() : void{
/** @var mixed[] $runtimeIdMap */
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
foreach($runtimeIdMap as $k => $obj){
self::registerMapping($k, $obj["id"], $obj["data"]);
}
}
/**
* @internal
* @deprecated
*
* @param int $id
* @param int $meta
@ -444,15 +426,11 @@ class BlockFactory{
* @return int
*/
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
/*
* try id+meta first
* if not found, try id+0 (strip meta)
* if still not found, return update! block
*/
return self::$staticRuntimeIdMap[($id << 4) | $meta] ?? self::$staticRuntimeIdMap[$id << 4] ?? self::$staticRuntimeIdMap[BlockIds::INFO_UPDATE << 4];
return RuntimeBlockMapping::toStaticRuntimeId($id, $meta);
}
/**
* @deprecated
* @internal
*
* @param int $runtimeId
@ -460,13 +438,6 @@ class BlockFactory{
* @return int[] [id, meta]
*/
public static function fromStaticRuntimeId(int $runtimeId) : array{
$v = self::$legacyIdMap[$runtimeId];
return [$v >> 4, $v & 0xf];
}
private static function registerMapping(int $staticRuntimeId, int $legacyId, int $legacyMeta) : void{
self::$staticRuntimeIdMap[($legacyId << 4) | $legacyMeta] = $staticRuntimeId;
self::$legacyIdMap[$staticRuntimeId] = ($legacyId << 4) | $legacyMeta;
self::$lastRuntimeId = max(self::$lastRuntimeId, $staticRuntimeId);
return RuntimeBlockMapping::fromStaticRuntimeId($runtimeId);
}
}

View File

@ -30,6 +30,18 @@ use pocketmine\math\Vector3;
class CobblestoneWall extends Transparent{
public const NONE_MOSSY_WALL = 0;
public const MOSSY_WALL = 1;
public const GRANITE_WALL = 2;
public const DIORITE_WALL = 3;
public const ANDESITE_WALL = 4;
public const SANDSTONE_WALL = 5;
public const BRICK_WALL = 6;
public const STONE_BRICK_WALL = 7;
public const MOSSY_STONE_BRICK_WALL = 8;
public const NETHER_BRICK_WALL = 9;
public const END_STONE_BRICK_WALL = 10;
public const PRISMARINE_WALL = 11;
public const RED_SANDSTONE_WALL = 12;
public const RED_NETHER_BRICK_WALL = 13;
protected $id = self::COBBLESTONE_WALL;
@ -50,11 +62,23 @@ class CobblestoneWall extends Transparent{
}
public function getName() : string{
if($this->meta === 0x01){
return "Mossy Cobblestone Wall";
}
return "Cobblestone Wall";
static $names = [
self::NONE_MOSSY_WALL => "Cobblestone",
self::MOSSY_WALL => "Mossy Cobblestone",
self::GRANITE_WALL => "Granite",
self::DIORITE_WALL => "Diorite",
self::ANDESITE_WALL => "Andesite",
self::SANDSTONE_WALL => "Sandstone",
self::BRICK_WALL => "Brick",
self::STONE_BRICK_WALL => "Stone Brick",
self::MOSSY_STONE_BRICK_WALL => "Mossy Stone Brick",
self::NETHER_BRICK_WALL => "Nether Brick",
self::END_STONE_BRICK_WALL => "End Stone Brick",
self::PRISMARINE_WALL => "Prismarine",
self::RED_SANDSTONE_WALL => "Red Sandstone",
self::RED_NETHER_BRICK_WALL => "Red Nether Brick"
];
return ($names[$this->getVariant()] ?? "Unknown") . " Wall";
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

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

@ -26,6 +26,12 @@ namespace pocketmine\block;
class StoneSlab2 extends StoneSlab{
public const TYPE_RED_SANDSTONE = 0;
public const TYPE_PURPUR = 1;
public const TYPE_PRISMARINE = 2;
public const TYPE_DARK_PRISMARINE = 3;
public const TYPE_PRISMARINE_BRICKS = 4;
public const TYPE_MOSSY_COBBLESTONE = 5;
public const TYPE_SMOOTH_SANDSTONE = 6;
public const TYPE_RED_NETHER_BRICK = 7;
protected $id = self::STONE_SLAB2;
@ -36,7 +42,13 @@ class StoneSlab2 extends StoneSlab{
public function getName() : string{
static $names = [
self::TYPE_RED_SANDSTONE => "Red Sandstone",
self::TYPE_PURPUR => "Purpur"
self::TYPE_PURPUR => "Purpur",
self::TYPE_PRISMARINE => "Prismarine",
self::TYPE_DARK_PRISMARINE => "Dark Prismarine",
self::TYPE_PRISMARINE_BRICKS => "Prismarine Bricks",
self::TYPE_MOSSY_COBBLESTONE => "Mossy Cobblestone",
self::TYPE_SMOOTH_SANDSTONE => "Smooth Sandstone",
self::TYPE_RED_NETHER_BRICK => "Red Nether Brick"
];
return (($this->meta & 0x08) > 0 ? "Upper " : "") . ($names[$this->getVariant()] ?? "") . " Slab";

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

@ -200,6 +200,11 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAGS2 = 91; //long (extended data flags)
/* 92 (float) related to panda lying down
* 93 (float) related to panda lying down */
public const DATA_AREA_EFFECT_CLOUD_DURATION = 94; //int
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 95; //int
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 96; //float, usually negative
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 97; //float
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 98; //int
public const DATA_FLAG_ONFIRE = 0;
@ -1164,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;
@ -1632,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

@ -216,7 +216,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Sets the Item's NBT
*
* @param CompoundTag|string $tags
* @param CompoundTag|string|null $tags
*
* @return Item
*/
@ -224,7 +224,7 @@ class Item implements ItemIds, \JsonSerializable{
if($tags instanceof CompoundTag){
$this->setNamedTag($tags);
}else{
$this->tags = (string) $tags;
$this->tags = $tags === null ? "" : (string) $tags;
$this->cachedNBT = null;
}
@ -232,6 +232,9 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* @deprecated This method returns NBT serialized in a network-dependent format. Prefer use of getNamedTag() instead.
* @see Item::getNamedTag()
*
* Returns the serialized NBT of the Item
* @return string
*/

View File

@ -300,16 +300,16 @@ class ItemFactory{
/**
* Returns an instance of the Item with the specified id, meta, count and NBT.
*
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string $tags
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string|null $tags
*
* @return Item
* @throws \TypeError
*/
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag)){
public static function get(int $id, int $meta = 0, int $count = 1, $tags = null) : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag) and $tags !== null){
throw new \TypeError("`tags` argument must be a string or CompoundTag instance, " . (is_object($tags) ? "instance of " . get_class($tags) : gettype($tags)) . " given");
}

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

@ -78,6 +78,7 @@ use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
@ -261,11 +262,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 +413,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 +475,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 +589,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 +608,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 +786,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();
@ -974,7 +983,7 @@ class Level implements ChunkManager, Metadatable{
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE;
@ -996,7 +1005,7 @@ class Level implements ChunkManager, Metadatable{
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->flags = $flags;
@ -2691,10 +2700,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 +2721,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 +2740,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 +2766,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

@ -106,7 +106,7 @@ class Flat extends Generator{
$split = array_map('\trim', explode(',', $layers));
$y = 0;
foreach($split as $line){
preg_match('#^(?:(\d+)x)?(.+)$#', $line, $matches);
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
if(count($matches) !== 3){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
}

View File

@ -29,7 +29,10 @@ 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;
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
use pocketmine\network\mcpe\protocol\types\EntityLink;
use pocketmine\utils\BinaryStream;
@ -79,10 +82,17 @@ class NetworkBinaryStream extends BinaryStream{
$cnt = $auxValue & 0xff;
$nbtLen = $this->getLShort();
$nbt = "";
if($nbtLen > 0){
$nbt = $this->get($nbtLen);
/** @var CompoundTag|string $nbt */
$nbt = "";
if($nbtLen === 0xffff){
$c = $this->getByte();
if($c !== 1){
throw new \UnexpectedValueException("Unexpected NBT count $c");
}
$nbt = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
}elseif($nbtLen !== 0){
throw new \UnexpectedValueException("Unexpected fake NBT length $nbtLen");
}
//TODO
@ -95,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);
}
@ -110,27 +124,20 @@ class NetworkBinaryStream extends BinaryStream{
$auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount();
$this->putVarInt($auxValue);
$nbt = $item->getCompoundTag();
$nbtLen = strlen($nbt);
if($nbtLen > 32767){
/*
* TODO: Workaround bug in the protocol (overflow)
* Encoded tags larger than 32KB overflow the length field, so we can't send these over network.
* However, it's unreasonable to randomly throw this burden off onto users by crashing their servers, so the
* next best solution is to just not send the NBT. This is also not an ideal solution (books and the like
* with too-large tags won't work on the client side) but it's better than crashing the server or client due
* to a protocol bug. Mojang have confirmed this will be resolved by a future MCPE release, so we'll just
* work around this problem until then.
*/
$nbt = "";
$nbtLen = 0;
if($item->hasCompoundTag()){
$this->putLShort(0xffff);
$this->putByte(1); //TODO: some kind of count field? always 1 as of 1.9.0
$this->put((new NetworkLittleEndianNBTStream())->write($item->getNamedTag()));
}else{
$this->putLShort(0);
}
$this->putLShort($nbtLen);
$this->put($nbt);
$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

@ -25,12 +25,12 @@ namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddHangingEntityPacket;
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
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;
@ -69,9 +69,11 @@ 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;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV2;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -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{
@ -210,10 +212,6 @@ abstract class NetworkSession{
return false;
}
public function handleAddHangingEntity(AddHangingEntityPacket $packet) : bool{
return false;
}
public function handleTakeItemEntity(TakeItemEntityPacket $packet) : bool{
return false;
}
@ -526,7 +524,7 @@ abstract class NetworkSession{
return false;
}
public function handleWSConnect(WSConnectPacket $packet) : bool{
public function handleAutomationClientConnect(AutomationClientConnectPacket $packet) : bool{
return false;
}
@ -622,7 +620,7 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
public function handleLevelSoundEventPacketV2(LevelSoundEventPacketV2 $packet) : bool{
return false;
}
@ -634,4 +632,16 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
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

@ -0,0 +1,7 @@
# Minecraft: Bedrock Edition network & protocol components
This directory (the `pocketmine\network\mcpe` namespace) contains code specific to the current version of Minecraft: Bedrock Edition.
## WARNING
This namespace should be considered **INTERNAL in its entirety**. Publicly exposed API in this namespace can and will change frequently, and without warning.
This namespace **IS NOT COVERED BY THE API VERSION SYSTEM**. If your plugin uses any code in this namespace, it can and will break without warning.

View File

@ -28,19 +28,37 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
class AddPaintingPacket extends AddHangingEntityPacket{
class AddPaintingPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_PAINTING_PACKET;
/** @var string */
public $title;
/** @var int */
public $entityRuntimeId;
/** @var int */
public $x;
/** @var int|null */
public $entityUniqueId = null;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $direction;
protected function decodePayload(){
parent::decodePayload();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->getBlockPosition($this->x, $this->y, $this->z);
$this->direction = $this->getVarInt();
$this->title = $this->getString();
}
protected function encodePayload(){
parent::encodePayload();
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putBlockPosition($this->x, $this->y, $this->z);
$this->putVarInt($this->direction);
$this->putString($this->title);
}

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

@ -55,19 +55,22 @@ class AvailableCommandsPacket extends DataPacket{
public const ARG_TYPE_FLOAT = 0x02;
public const ARG_TYPE_VALUE = 0x03;
public const ARG_TYPE_WILDCARD_INT = 0x04;
public const ARG_TYPE_TARGET = 0x05;
public const ARG_TYPE_WILDCARD_TARGET = 0x06;
public const ARG_TYPE_OPERATOR = 0x05;
public const ARG_TYPE_TARGET = 0x06;
public const ARG_TYPE_STRING = 0x0f;
public const ARG_TYPE_POSITION = 0x10;
public const ARG_TYPE_FILEPATH = 0x0e;
public const ARG_TYPE_MESSAGE = 0x13;
public const ARG_TYPE_STRING = 0x1b;
public const ARG_TYPE_RAWTEXT = 0x15;
public const ARG_TYPE_POSITION = 0x1d;
public const ARG_TYPE_JSON = 0x18;
public const ARG_TYPE_MESSAGE = 0x20;
public const ARG_TYPE_COMMAND = 0x1f;
public const ARG_TYPE_RAWTEXT = 0x22;
public const ARG_TYPE_JSON = 0x25;
public const ARG_TYPE_COMMAND = 0x2c;
/**
* Enums are a little different: they are composed as follows:
@ -220,21 +223,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 +281,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

@ -27,37 +27,33 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
class AddHangingEntityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_HANGING_ENTITY_PACKET;
class LecternUpdatePacket extends DataPacket/* implements ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::LECTERN_UPDATE_PACKET;
/** @var int|null */
public $entityUniqueId = null;
/** @var int */
public $entityRuntimeId;
public $page;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $direction;
/** @var bool */
public $dropBook;
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
protected function decodePayload() : void{
$this->page = $this->getByte();
$this->getBlockPosition($this->x, $this->y, $this->z);
$this->direction = $this->getVarInt();
$this->dropBook = $this->getBool();
}
protected function encodePayload(){
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
protected function encodePayload() : void{
$this->putByte($this->page);
$this->putBlockPosition($this->x, $this->y, $this->z);
$this->putVarInt($this->direction);
$this->putBool($this->dropBook);
}
public function handle(NetworkSession $session) : bool{
return $session->handleAddHangingEntity($this);
return $session->handleLecternUpdate($this);
}
}

View File

@ -299,7 +299,7 @@ class LevelSoundEventPacket extends DataPacket{
public $disableRelativeVolume = false;
protected function decodePayload(){
$this->sound = $this->getByte();
$this->sound = $this->getUnsignedVarInt();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->entityType = $this->getString();
@ -308,7 +308,7 @@ class LevelSoundEventPacket extends DataPacket{
}
protected function encodePayload(){
$this->putByte($this->sound);
$this->putUnsignedVarInt($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putString($this->entityType);

View File

@ -0,0 +1,71 @@
<?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\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
/**
* Useless leftover from a 1.9 refactor, does nothing
*/
class LevelSoundEventPacketV2 extends DataPacket{
public const NETWORK_ID = ProtocolInfo::LEVEL_SOUND_EVENT_PACKET_V2;
/** @var int */
public $sound;
/** @var Vector3 */
public $position;
/** @var int */
public $extraData = -1;
/** @var string */
public $entityType = ":"; //???
/** @var bool */
public $isBabyMob = false; //...
/** @var bool */
public $disableRelativeVolume = false;
protected function decodePayload(){
$this->sound = $this->getByte();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->entityType = $this->getString();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
protected function encodePayload(){
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putString($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}
public function handle(NetworkSession $session) : bool{
return $session->handleLevelSoundEventPacketV2($this);
}
}

View File

@ -32,13 +32,17 @@ class NetworkStackLatencyPacket extends DataPacket{
/** @var int */
public $timestamp;
/** @var bool */
public $needResponse;
protected function decodePayload(){
$this->timestamp = $this->getLLong();
$this->needResponse = $this->getBool();
}
protected function encodePayload(){
$this->putLLong($this->timestamp);
$this->putBool($this->needResponse);
}
public function handle(NetworkSession $session) : bool{

View File

@ -48,7 +48,6 @@ class PacketPool{
static::registerPacket(new AddEntityPacket());
static::registerPacket(new RemoveEntityPacket());
static::registerPacket(new AddItemEntityPacket());
static::registerPacket(new AddHangingEntityPacket());
static::registerPacket(new TakeItemEntityPacket());
static::registerPacket(new MoveEntityAbsolutePacket());
static::registerPacket(new MovePlayerPacket());
@ -127,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());
@ -151,9 +150,12 @@ class PacketPool{
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new SpawnParticleEffectPacket());
static::registerPacket(new AvailableEntityIdentifiersPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new LevelSoundEventPacketV2());
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 = 313;
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.8.0';
public const MINECRAFT_VERSION = 'v1.10.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.8.0';
public const MINECRAFT_VERSION_NETWORK = '1.10.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -64,7 +64,7 @@ interface ProtocolInfo{
public const ADD_ENTITY_PACKET = 0x0d;
public const REMOVE_ENTITY_PACKET = 0x0e;
public const ADD_ITEM_ENTITY_PACKET = 0x0f;
public const ADD_HANGING_ENTITY_PACKET = 0x10;
public const TAKE_ITEM_ENTITY_PACKET = 0x11;
public const MOVE_ENTITY_ABSOLUTE_PACKET = 0x12;
public const MOVE_PLAYER_PACKET = 0x13;
@ -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;
@ -168,8 +168,11 @@ interface ProtocolInfo{
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
public const SPAWN_PARTICLE_EFFECT_PACKET = 0x76;
public const AVAILABLE_ENTITY_IDENTIFIERS_PACKET = 0x77;
public const LEVEL_SOUND_EVENT_PACKET = 0x78;
public const LEVEL_SOUND_EVENT_PACKET_V2 = 0x78;
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

@ -35,6 +35,8 @@ class ResourcePacksInfoPacket extends DataPacket{
/** @var bool */
public $mustAccept = false; //if true, forces client to use selected resource packs
/** @var bool */
public $hasScripts = false; //if true, causes disconnect for any platform that doesn't support scripts yet
/** @var ResourcePack[] */
public $behaviorPackEntries = [];
/** @var ResourcePack[] */
@ -42,6 +44,7 @@ class ResourcePacksInfoPacket extends DataPacket{
protected function decodePayload(){
$this->mustAccept = $this->getBool();
$this->hasScripts = $this->getBool();
$behaviorPackCount = $this->getLShort();
while($behaviorPackCount-- > 0){
$this->getString();
@ -50,6 +53,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->getString();
$this->getString();
$this->getString();
$this->getBool();
}
$resourcePackCount = $this->getLShort();
@ -60,12 +64,13 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->getString();
$this->getString();
$this->getString();
$this->getBool();
}
}
protected function encodePayload(){
$this->putBool($this->mustAccept);
$this->putBool($this->hasScripts);
$this->putLShort(count($this->behaviorPackEntries));
foreach($this->behaviorPackEntries as $entry){
$this->putString($entry->getPackId());
@ -74,6 +79,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
$this->putBool(false); //TODO: has scripts (?)
}
$this->putLShort(count($this->resourcePackEntries));
foreach($this->resourcePackEntries as $entry){
@ -83,6 +89,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
$this->putBool(false); //TODO: seems useless for resource packs
}
}

View File

@ -34,6 +34,8 @@ class SpawnParticleEffectPacket extends DataPacket{
/** @var int */
public $dimensionId = DimensionIds::OVERWORLD; //wtf mojang
/** @var int */
public $entityUniqueId = -1; //default none
/** @var Vector3 */
public $position;
/** @var string */
@ -41,12 +43,14 @@ class SpawnParticleEffectPacket extends DataPacket{
protected function decodePayload(){
$this->dimensionId = $this->getByte();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->position = $this->getVector3();
$this->particleName = $this->getString();
}
protected function encodePayload(){
$this->putByte($this->dimensionId);
$this->putEntityUniqueId($this->entityUniqueId);
$this->putVector3($this->position);
$this->putString($this->particleName);
}

View File

@ -30,9 +30,8 @@ use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use function count;
use function file_get_contents;
use function json_decode;
class StartGamePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET;
@ -84,11 +83,15 @@ class StartGamePacket extends DataPacket{
/** @var float */
public $lightningLevel;
/** @var bool */
public $hasConfirmedPlatformLockedContent = false;
/** @var bool */
public $isMultiplayerGame = true;
/** @var bool */
public $hasLANBroadcast = true;
/** @var bool */
public $hasXboxLiveBroadcast = false;
/** @var int */
public $xboxLiveBroadcastMode = 0; //TODO: find values
/** @var int */
public $platformBroadcastMode = 0;
/** @var bool */
public $commandsEnabled;
/** @var bool */
@ -101,20 +104,12 @@ class StartGamePacket extends DataPacket{
public $hasBonusChestEnabled = false;
/** @var bool */
public $hasStartWithMapEnabled = false;
/** @var bool */
public $hasTrustPlayersEnabled = false;
/** @var int */
public $defaultPlayerPermission = PlayerPermissions::MEMBER; //TODO
/** @var int */
public $xboxLiveBroadcastMode = 0; //TODO: find values
/** @var int */
public $serverChunkTickRadius = 4; //TODO (leave as default for now)
/** @var bool */
public $hasPlatformBroadcast = false;
/** @var int */
public $platformBroadcastMode = 0;
/** @var bool */
public $xboxLiveBroadcastIntent = false;
/** @var bool */
public $hasLockedBehaviorPack = false;
/** @var bool */
@ -166,21 +161,18 @@ class StartGamePacket extends DataPacket{
$this->hasEduFeaturesEnabled = $this->getBool();
$this->rainLevel = $this->getLFloat();
$this->lightningLevel = $this->getLFloat();
$this->hasConfirmedPlatformLockedContent = $this->getBool();
$this->isMultiplayerGame = $this->getBool();
$this->hasLANBroadcast = $this->getBool();
$this->hasXboxLiveBroadcast = $this->getBool();
$this->xboxLiveBroadcastMode = $this->getVarInt();
$this->platformBroadcastMode = $this->getVarInt();
$this->commandsEnabled = $this->getBool();
$this->isTexturePacksRequired = $this->getBool();
$this->gameRules = $this->getGameRules();
$this->hasBonusChestEnabled = $this->getBool();
$this->hasStartWithMapEnabled = $this->getBool();
$this->hasTrustPlayersEnabled = $this->getBool();
$this->defaultPlayerPermission = $this->getVarInt();
$this->xboxLiveBroadcastMode = $this->getVarInt();
$this->serverChunkTickRadius = $this->getLInt();
$this->hasPlatformBroadcast = $this->getBool();
$this->platformBroadcastMode = $this->getVarInt();
$this->xboxLiveBroadcastIntent = $this->getBool();
$this->hasLockedBehaviorPack = $this->getBool();
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
@ -228,21 +220,18 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasEduFeaturesEnabled);
$this->putLFloat($this->rainLevel);
$this->putLFloat($this->lightningLevel);
$this->putBool($this->hasConfirmedPlatformLockedContent);
$this->putBool($this->isMultiplayerGame);
$this->putBool($this->hasLANBroadcast);
$this->putBool($this->hasXboxLiveBroadcast);
$this->putVarInt($this->xboxLiveBroadcastMode);
$this->putVarInt($this->platformBroadcastMode);
$this->putBool($this->commandsEnabled);
$this->putBool($this->isTexturePacksRequired);
$this->putGameRules($this->gameRules);
$this->putBool($this->hasBonusChestEnabled);
$this->putBool($this->hasStartWithMapEnabled);
$this->putBool($this->hasTrustPlayersEnabled);
$this->putVarInt($this->defaultPlayerPermission);
$this->putVarInt($this->xboxLiveBroadcastMode);
$this->putLInt($this->serverChunkTickRadius);
$this->putBool($this->hasPlatformBroadcast);
$this->putVarInt($this->platformBroadcastMode);
$this->putBool($this->xboxLiveBroadcastIntent);
$this->putBool($this->hasLockedBehaviorPack);
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);
@ -261,7 +250,7 @@ class StartGamePacket extends DataPacket{
if(self::$runtimeIdTable === null){
//this is a really nasty hack, but it'll do for now
$stream = new NetworkBinaryStream();
$data = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
$data = RuntimeBlockMapping::getBedrockKnownStates();
$stream->putUnsignedVarInt(count($data));
foreach($data as $v){
$stream->putString($v["name"]);

View File

@ -41,6 +41,7 @@ class TextPacket extends DataPacket{
public const TYPE_SYSTEM = 6;
public const TYPE_WHISPER = 7;
public const TYPE_ANNOUNCEMENT = 8;
public const TYPE_JSON = 9;
/** @var int */
public $type;
@ -69,6 +70,7 @@ class TextPacket extends DataPacket{
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
case self::TYPE_JSON:
$this->message = $this->getString();
break;
@ -99,6 +101,7 @@ class TextPacket extends DataPacket{
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
case self::TYPE_JSON:
$this->putString($this->message);
break;

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

@ -0,0 +1,118 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use pocketmine\block\BlockIds;
use function file_get_contents;
use function getmypid;
use function json_decode;
use function mt_rand;
use function mt_srand;
use function shuffle;
/**
* @internal
*/
final class RuntimeBlockMapping{
/** @var int[] */
private static $legacyToRuntimeMap = [];
/** @var int[] */
private static $runtimeToLegacyMap = [];
/** @var mixed[] */
private static $bedrockKnownStates;
private function __construct(){
//NOOP
}
public static function init() : void{
$legacyIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "legacy_id_map.json"), true);
self::$bedrockKnownStates = self::randomizeTable(json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true));
foreach(self::$bedrockKnownStates as $k => $obj){
//this has to use the json offset to make sure the mapping is consistent with what we send over network, even though we aren't using all the entries
if(!isset($legacyIdMap[$obj["name"]])){
continue;
}
self::registerMapping($k, $legacyIdMap[$obj["name"]], $obj["data"]);
}
}
/**
* Randomizes the order of the runtimeID table to prevent plugins relying on them.
* Plugins shouldn't use this stuff anyway, but plugin devs have an irritating habit of ignoring what they
* aren't supposed to do, so we have to deliberately break it to make them stop.
*
* @param array $table
*
* @return array
*/
private static function randomizeTable(array $table) : array{
$postSeed = mt_rand(); //save a seed to set afterwards, to avoid poor quality randoms
mt_srand(getmypid() ?: 0); //Use a seed which is the same on all threads. This isn't a secure seed, but we don't care.
shuffle($table);
mt_srand($postSeed); //restore a good quality seed that isn't dependent on PID
return $table;
}
/**
* @param int $id
* @param int $meta
*
* @return int
*/
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
/*
* try id+meta first
* if not found, try id+0 (strip meta)
* if still not found, return update! block
*/
return self::$legacyToRuntimeMap[($id << 4) | $meta] ?? self::$legacyToRuntimeMap[$id << 4] ?? self::$legacyToRuntimeMap[BlockIds::INFO_UPDATE << 4];
}
/**
* @param int $runtimeId
*
* @return int[] [id, meta]
*/
public static function fromStaticRuntimeId(int $runtimeId) : array{
$v = self::$runtimeToLegacyMap[$runtimeId];
return [$v >> 4, $v & 0xf];
}
private static function registerMapping(int $staticRuntimeId, int $legacyId, int $legacyMeta) : void{
self::$legacyToRuntimeMap[($legacyId << 4) | $legacyMeta] = $staticRuntimeId;
self::$runtimeToLegacyMap[$staticRuntimeId] = ($legacyId << 4) | $legacyMeta;
}
/**
* @return array
*/
public static function getBedrockKnownStates() : array{
return self::$bedrockKnownStates;
}
}
RuntimeBlockMapping::init();

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

@ -0,0 +1,460 @@
{
"minecraft:air": 0,
"minecraft:stone": 1,
"minecraft:grass": 2,
"minecraft:dirt": 3,
"minecraft:cobblestone": 4,
"minecraft:planks": 5,
"minecraft:sapling": 6,
"minecraft:bedrock": 7,
"minecraft:flowing_water": 8,
"minecraft:water": 9,
"minecraft:flowing_lava": 10,
"minecraft:lava": 11,
"minecraft:sand": 12,
"minecraft:gravel": 13,
"minecraft:gold_ore": 14,
"minecraft:iron_ore": 15,
"minecraft:coal_ore": 16,
"minecraft:log": 17,
"minecraft:leaves": 18,
"minecraft:sponge": 19,
"minecraft:glass": 20,
"minecraft:lapis_ore": 21,
"minecraft:lapis_block": 22,
"minecraft:dispenser": 23,
"minecraft:sandstone": 24,
"minecraft:noteblock": 25,
"minecraft:bed": 26,
"minecraft:golden_rail": 27,
"minecraft:detector_rail": 28,
"minecraft:sticky_piston": 29,
"minecraft:web": 30,
"minecraft:tallgrass": 31,
"minecraft:deadbush": 32,
"minecraft:piston": 33,
"minecraft:pistonArmCollision": 34,
"minecraft:wool": 35,
"minecraft:element_0": 36,
"minecraft:yellow_flower": 37,
"minecraft:red_flower": 38,
"minecraft:brown_mushroom": 39,
"minecraft:red_mushroom": 40,
"minecraft:gold_block": 41,
"minecraft:iron_block": 42,
"minecraft:double_stone_slab": 43,
"minecraft:stone_slab": 44,
"minecraft:brick_block": 45,
"minecraft:tnt": 46,
"minecraft:bookshelf": 47,
"minecraft:mossy_cobblestone": 48,
"minecraft:obsidian": 49,
"minecraft:torch": 50,
"minecraft:fire": 51,
"minecraft:mob_spawner": 52,
"minecraft:oak_stairs": 53,
"minecraft:chest": 54,
"minecraft:redstone_wire": 55,
"minecraft:diamond_ore": 56,
"minecraft:diamond_block": 57,
"minecraft:crafting_table": 58,
"minecraft:wheat": 59,
"minecraft:farmland": 60,
"minecraft:furnace": 61,
"minecraft:lit_furnace": 62,
"minecraft:standing_sign": 63,
"minecraft:wooden_door": 64,
"minecraft:ladder": 65,
"minecraft:rail": 66,
"minecraft:stone_stairs": 67,
"minecraft:wall_sign": 68,
"minecraft:lever": 69,
"minecraft:stone_pressure_plate": 70,
"minecraft:iron_door": 71,
"minecraft:wooden_pressure_plate": 72,
"minecraft:redstone_ore": 73,
"minecraft:lit_redstone_ore": 74,
"minecraft:unlit_redstone_torch": 75,
"minecraft:redstone_torch": 76,
"minecraft:stone_button": 77,
"minecraft:snow_layer": 78,
"minecraft:ice": 79,
"minecraft:snow": 80,
"minecraft:cactus": 81,
"minecraft:clay": 82,
"minecraft:reeds": 83,
"minecraft:jukebox": 84,
"minecraft:fence": 85,
"minecraft:pumpkin": 86,
"minecraft:netherrack": 87,
"minecraft:soul_sand": 88,
"minecraft:glowstone": 89,
"minecraft:portal": 90,
"minecraft:lit_pumpkin": 91,
"minecraft:cake": 92,
"minecraft:unpowered_repeater": 93,
"minecraft:powered_repeater": 94,
"minecraft:invisibleBedrock": 95,
"minecraft:trapdoor": 96,
"minecraft:monster_egg": 97,
"minecraft:stonebrick": 98,
"minecraft:brown_mushroom_block": 99,
"minecraft:red_mushroom_block": 100,
"minecraft:iron_bars": 101,
"minecraft:glass_pane": 102,
"minecraft:melon_block": 103,
"minecraft:pumpkin_stem": 104,
"minecraft:melon_stem": 105,
"minecraft:vine": 106,
"minecraft:fence_gate": 107,
"minecraft:brick_stairs": 108,
"minecraft:stone_brick_stairs": 109,
"minecraft:mycelium": 110,
"minecraft:waterlily": 111,
"minecraft:nether_brick": 112,
"minecraft:nether_brick_fence": 113,
"minecraft:nether_brick_stairs": 114,
"minecraft:nether_wart": 115,
"minecraft:enchanting_table": 116,
"minecraft:brewing_stand": 117,
"minecraft:cauldron": 118,
"minecraft:end_portal": 119,
"minecraft:end_portal_frame": 120,
"minecraft:end_stone": 121,
"minecraft:dragon_egg": 122,
"minecraft:redstone_lamp": 123,
"minecraft:lit_redstone_lamp": 124,
"minecraft:dropper": 125,
"minecraft:activator_rail": 126,
"minecraft:cocoa": 127,
"minecraft:sandstone_stairs": 128,
"minecraft:emerald_ore": 129,
"minecraft:ender_chest": 130,
"minecraft:tripwire_hook": 131,
"minecraft:tripWire": 132,
"minecraft:emerald_block": 133,
"minecraft:spruce_stairs": 134,
"minecraft:birch_stairs": 135,
"minecraft:jungle_stairs": 136,
"minecraft:command_block": 137,
"minecraft:beacon": 138,
"minecraft:cobblestone_wall": 139,
"minecraft:flower_pot": 140,
"minecraft:carrots": 141,
"minecraft:potatoes": 142,
"minecraft:wooden_button": 143,
"minecraft:skull": 144,
"minecraft:anvil": 145,
"minecraft:trapped_chest": 146,
"minecraft:light_weighted_pressure_plate": 147,
"minecraft:heavy_weighted_pressure_plate": 148,
"minecraft:unpowered_comparator": 149,
"minecraft:powered_comparator": 150,
"minecraft:daylight_detector": 151,
"minecraft:redstone_block": 152,
"minecraft:quartz_ore": 153,
"minecraft:hopper": 154,
"minecraft:quartz_block": 155,
"minecraft:quartz_stairs": 156,
"minecraft:double_wooden_slab": 157,
"minecraft:wooden_slab": 158,
"minecraft:stained_hardened_clay": 159,
"minecraft:stained_glass_pane": 160,
"minecraft:leaves2": 161,
"minecraft:log2": 162,
"minecraft:acacia_stairs": 163,
"minecraft:dark_oak_stairs": 164,
"minecraft:slime": 165,
"minecraft:iron_trapdoor": 167,
"minecraft:prismarine": 168,
"minecraft:seaLantern": 169,
"minecraft:hay_block": 170,
"minecraft:carpet": 171,
"minecraft:hardened_clay": 172,
"minecraft:coal_block": 173,
"minecraft:packed_ice": 174,
"minecraft:double_plant": 175,
"minecraft:standing_banner": 176,
"minecraft:wall_banner": 177,
"minecraft:daylight_detector_inverted": 178,
"minecraft:red_sandstone": 179,
"minecraft:red_sandstone_stairs": 180,
"minecraft:double_stone_slab2": 181,
"minecraft:stone_slab2": 182,
"minecraft:spruce_fence_gate": 183,
"minecraft:birch_fence_gate": 184,
"minecraft:jungle_fence_gate": 185,
"minecraft:dark_oak_fence_gate": 186,
"minecraft:acacia_fence_gate": 187,
"minecraft:repeating_command_block": 188,
"minecraft:chain_command_block": 189,
"minecraft:hard_glass_pane": 190,
"minecraft:hard_stained_glass_pane": 191,
"minecraft:chemical_heat": 192,
"minecraft:spruce_door": 193,
"minecraft:birch_door": 194,
"minecraft:jungle_door": 195,
"minecraft:acacia_door": 196,
"minecraft:dark_oak_door": 197,
"minecraft:grass_path": 198,
"minecraft:frame": 199,
"minecraft:chorus_flower": 200,
"minecraft:purpur_block": 201,
"minecraft:colored_torch_rg": 202,
"minecraft:purpur_stairs": 203,
"minecraft:colored_torch_bp": 204,
"minecraft:undyed_shulker_box": 205,
"minecraft:end_bricks": 206,
"minecraft:frosted_ice": 207,
"minecraft:end_rod": 208,
"minecraft:end_gateway": 209,
"minecraft:magma": 213,
"minecraft:nether_wart_block": 214,
"minecraft:red_nether_brick": 215,
"minecraft:bone_block": 216,
"minecraft:shulker_box": 218,
"minecraft:purple_glazed_terracotta": 219,
"minecraft:white_glazed_terracotta": 220,
"minecraft:orange_glazed_terracotta": 221,
"minecraft:magenta_glazed_terracotta": 222,
"minecraft:light_blue_glazed_terracotta": 223,
"minecraft:yellow_glazed_terracotta": 224,
"minecraft:lime_glazed_terracotta": 225,
"minecraft:pink_glazed_terracotta": 226,
"minecraft:gray_glazed_terracotta": 227,
"minecraft:silver_glazed_terracotta": 228,
"minecraft:cyan_glazed_terracotta": 229,
"minecraft:blue_glazed_terracotta": 231,
"minecraft:brown_glazed_terracotta": 232,
"minecraft:green_glazed_terracotta": 233,
"minecraft:red_glazed_terracotta": 234,
"minecraft:black_glazed_terracotta": 235,
"minecraft:concrete": 236,
"minecraft:concretePowder": 237,
"minecraft:chemistry_table": 238,
"minecraft:underwater_torch": 239,
"minecraft:chorus_plant": 240,
"minecraft:stained_glass": 241,
"minecraft:podzol": 243,
"minecraft:beetroot": 244,
"minecraft:stonecutter": 245,
"minecraft:glowingobsidian": 246,
"minecraft:netherreactor": 247,
"minecraft:info_update": 248,
"minecraft:info_update2": 249,
"minecraft:movingBlock": 250,
"minecraft:observer": 251,
"minecraft:structure_block": 252,
"minecraft:hard_glass": 253,
"minecraft:hard_stained_glass": 254,
"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);
}
}