Compare commits

...

67 Commits
3.4.1 ... 3.5.3

Author SHA1 Message Date
9abaa42cd7 Release 3.5.3 2018-12-30 18:44:16 +00:00
77b9feb3c0 Player: don't waste CPU time ordering chunks for non-moving players 2018-12-30 18:40:36 +00:00
e0e2e1775f Player: Fixed sluggish chunk updates when not moving
Always order chunks ASAP on chunk change, not just during the spawn sequence. This fixes the sluggishness observed in BlockSniper when doing async chunk modifications.
2018-12-30 17:32:38 +00:00
d2d65ce6cc Attribute: Fix exception messages, make them less useless 2018-12-30 13:44:30 +00:00
ff2e982f22 Updated BinaryUtils dependency 2018-12-30 12:44:08 +00:00
daf56e990b Get rid of some network-layer asserts
NEVER assert on user data. 🤦
2018-12-30 12:42:52 +00:00
3f5e83a322 Backport 23954c4cda to 3.5 branch 2018-12-29 16:39:56 +00:00
5ecc5ed7e0 Get rid of catch-all on chunk unload
god only knows what the fuck is going on in here that warrants this catch-all... so let's remove it and find out!
2018-12-29 16:37:59 +00:00
cd80ae00d4 Handle errors properly on chunk load
Only CorruptedChunkException and UnsupportedChunkFormatException are expected. Anything else should crash the server.
2018-12-29 16:37:10 +00:00
beb5d72299 RegionLoader: fix off-by-one bug with large chunks, closes #2615 2018-12-29 00:02:54 +00:00
0eef634aab Player: Give me ALLLLL your crashdumps
I suspect this is going to cause a firestorm, but once it does we'll be able to see what needs fixing.
2018-12-28 19:30:05 +00:00
0ea166a551 Prevent placement of unknown blocks, closes #2260
I don't know why I ever allowed this in the first place... stupid idea...
2018-12-28 13:03:34 +00:00
6417cff618 Fixed resource packs with comments in manifest
MOJANG, THERE'S NO SUCH THING AS COMMENTS IN STANDARD JSON
2018-12-27 15:50:51 +00:00
a71af952ba Sign: simplify network data reading, ensure text is always 4 lines, closes #2610 2018-12-26 22:57:42 +00:00
93dd05a03e Fixed ender chest sounds, closes #2611 2018-12-26 22:33:51 +00:00
98f903783c Chest: remove pairx and pairz on blockpick, fixes #2612 2018-12-26 22:26:17 +00:00
5d47ea4337 Merge branch 'release/3.4' into release/3.5 2018-12-23 14:04:13 +00:00
c242d6213a Rewrite documentation for PlayerPreLogin, PlayerLogin and PlayerJoin events
this is some of the most awful documentation I've ever seen. No documentation would have been better.
2018-12-23 14:03:19 +00:00
4ad1093fd7 3.5.3 is next 2018-12-22 17:36:29 +00:00
fc0782df02 Release 3.5.2 2018-12-22 17:29:31 +00:00
bfaa224f6b Merge branch 'release/3.4' into release/3.5 2018-12-22 17:29:11 +00:00
de88f0fce1 3.4.4 is next 2018-12-22 17:28:47 +00:00
9b078854c4 Release 3.4.3 2018-12-22 17:17:24 +00:00
42f8e061a5 Merge branch 'release/3.4' into release/3.5 2018-12-22 13:29:41 +00:00
1455c38dbe Utils: fixed crash in getCoreCount(), closes #2600
this should just default to 2 instead of shitting its pants.
2018-12-22 13:27:11 +00:00
75df6973df LevelDB: Account for 2D maps tag being missing
I don't know why this would be missing, but in some cases it is, as seen in the crash archive. Whatever the case, we shouldn't be shitting the bed because of this.
2018-12-22 13:13:14 +00:00
4763360e9e Update BinaryUtils dependency 2018-12-22 13:07:45 +00:00
0299191e64 Attribute: fit value when resetting to default, closes #2599 2018-12-22 11:44:36 +00:00
2664a1b4d8 Merge branch 'release/3.4' into release/3.5 2018-12-21 18:39:42 +00:00
4249c00c3e Level: Fixed generation/send race condition causing blocks to be missing on the client
this FINALLY fixes the remaining occurrences of half-trees.
2018-12-21 18:39:33 +00:00
517c4e5143 Merge branch 'release/3.4' into release/3.5 2018-12-21 17:26:32 +00:00
69c343bb9b Level: fix setChunk() deleting tiles when replacing a chunk with the same chunk
this can be desirable to trigger events related to chunks changing, such as chunk sending.
2018-12-21 17:24:08 +00:00
70df1579a8 Merge branch 'release/3.4' into release/3.5 2018-12-20 20:02:00 +00:00
ea9f9aa250 Update some non-critical protocol magic numbers 2018-12-20 19:59:42 +00:00
34a899e28b Clean up Utils error handling functions (internal) 2018-12-16 17:50:00 +00:00
b80868040e Utils: fixed getTrace() including itself in trace when no alt trace is given
it always seemed a little strange that crashdump trace would pop 4 frames when only 3 are written in the comment...
2018-12-16 17:15:16 +00:00
a7f1181335 Merge branch 'release/3.4' into release/3.5 2018-12-16 14:13:19 +00:00
bf8a8b386e Allow ~relative coordinates to work in /particle 2018-12-16 14:12:46 +00:00
4b518f2a58 Merge branch 'release/3.4' into release/3.5 2018-12-14 17:32:34 +00:00
60b1f0a6e9 Fixed burning TNT setting affected mobs on fire when exploding, closes #2561 2018-12-14 17:32:11 +00:00
5934399a0d 3.5.2 is next 2018-12-14 09:46:12 +00:00
b42132a7c3 Release 3.5.1 2018-12-14 09:45:47 +00:00
c05697f506 Merge branch 'release/3.4' into release/3.5 2018-12-14 09:39:21 +00:00
d4fe1b8ece 3.4.3 is next 2018-12-14 09:30:44 +00:00
9b2653fb6f Release 3.4.2 2018-12-14 09:30:14 +00:00
ed88684e71 Fixed invisible FloatingTextParticle crashing the server, closes #2560 2018-12-14 09:30:14 +00:00
cbb9c4f298 Entity: require scale > 0 in setScale(), fixes #2563 2018-12-14 09:30:14 +00:00
660d42e8d1 Backport usage of SetLocalPlayerAsInitializedPacket to 3.4 (#2558)
This fixes various problems, such as forms not working on PlayerJoinEvent.
2018-12-13 20:07:17 +00:00
dbeceb02f9 fixup 1.8 crafting, take 2 2018-12-13 10:54:15 +00:00
fd77dd0066 Revert "Fixed crafting grid transaction handling, close #2559"
This reverts commit dfeb62491a.
2018-12-13 10:38:04 +00:00
87ce87112b Merge branch 'release/3.4' into release/3.5 2018-12-13 09:56:21 +00:00
1d71f5edb3 DataPacket: more detail in error messages for undefined fields 2018-12-13 09:55:50 +00:00
0f620157e8 3.5.1 is next 2018-12-12 19:20:40 +00:00
2323601f98 Release 3.5.0 2018-12-12 19:03:07 +00:00
d34b94302f fixed lava fizz sound 2018-12-12 18:00:43 +00:00
ec4c61e113 fix extradata defaults for broadcastLevelSoundEvent
fixes TNT sounds not working, amongst other things
2018-12-12 17:42:52 +00:00
231e491bb9 Fixed black spawn eggs 2018-12-12 17:14:13 +00:00
69cdc6f13a Remove misleading default value for NetworkInventoryAction windowId 2018-12-12 16:08:47 +00:00
dfeb62491a Fixed crafting grid transaction handling, close #2559 2018-12-12 15:41:54 +00:00
178eedb536 Merge branch 'release/3.4' into release/3.5 2018-12-12 10:12:12 +00:00
4975da2aae NetworkInventoryAction: additional validity checks 2018-12-12 10:11:44 +00:00
5946ec8819 fix inventory bug, silence debug spam, shut the fuck up MCPE 2018-12-11 21:57:07 +00:00
abf0dee426 bump version 2018-12-11 21:07:56 +00:00
30f5a8fac6 Protocol changes for 1.8.0 release 2018-12-11 21:05:03 +00:00
f704061618 Tree: fixed being able to overwrite other trees
this was observable by planting a sapling underneath an existing tree and punching it with bone meal.

This change will also prevent trees generating too close together.
2018-12-09 19:26:48 +00:00
23dc6e09d8 Sync DevTools submodule 2018-12-09 15:34:06 +00:00
15b7fc978e 3.4.2 is next 2018-12-08 17:00:36 +00:00
55 changed files with 988 additions and 314 deletions

View File

@ -30,7 +30,8 @@
"pocketmine/nbt": "^0.2.1",
"pocketmine/math": "^0.2.0",
"pocketmine/snooze": "^0.1.0",
"daverandom/callback-validator": "dev-master"
"daverandom/callback-validator": "dev-master",
"adhocore/json-comment": "^0.0.7"
},
"autoload": {
"psr-4": {

58
composer.lock generated
View File

@ -4,8 +4,52 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2d120a3dd7d68958809c3662d1cb7c99",
"content-hash": "d3fb809caf4d5a5c99054f47f28ff271",
"packages": [
{
"name": "adhocore/json-comment",
"version": "v0.0.7",
"source": {
"type": "git",
"url": "https://github.com/adhocore/php-json-comment.git",
"reference": "135356c7e7336ef59924f1d921c770045f937a76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/adhocore/php-json-comment/zipball/135356c7e7336ef59924f1d921c770045f937a76",
"reference": "135356c7e7336ef59924f1d921c770045f937a76",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Ahc\\Json\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jitendra Adhikari",
"email": "jiten.adhikary@gmail.com"
}
],
"description": "Lightweight JSON comment stripper library for PHP",
"keywords": [
"comment",
"json",
"strip-comment"
],
"time": "2018-08-01T12:27:26+00:00"
},
{
"name": "daverandom/callback-validator",
"version": "dev-master",
@ -48,16 +92,16 @@
},
{
"name": "pocketmine/binaryutils",
"version": "0.1.1",
"version": "0.1.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be"
"reference": "925001c8eff97a36519b16bbd095964b0fc52772"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/54efeb978be0ff9335022729fe63c1e2077bf1be",
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/925001c8eff97a36519b16bbd095964b0fc52772",
"reference": "925001c8eff97a36519b16bbd095964b0fc52772",
"shasum": ""
},
"require": {
@ -75,10 +119,10 @@
],
"description": "Classes and methods for conveniently handling binary data",
"support": {
"source": "https://github.com/pmmp/BinaryUtils/tree/master",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.3",
"issues": "https://github.com/pmmp/BinaryUtils/issues"
},
"time": "2018-08-26T18:11:05+00:00"
"time": "2018-12-30T12:17:51+00:00"
},
{
"name": "pocketmine/math",

View File

@ -159,7 +159,7 @@ class CrashDump{
$error = $lastExceptionError;
}else{
$error = (array) error_get_last();
$error["trace"] = Utils::getTrace(4); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
$error["trace"] = Utils::printableCurrentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
$errorConversion = [
E_ERROR => "E_ERROR",
E_WARNING => "E_WARNING",

View File

@ -101,6 +101,7 @@ use pocketmine\network\mcpe\PlayerNetworkSessionAdapter;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
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\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
@ -120,6 +121,7 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
@ -276,7 +278,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var int */
protected $spawnThreshold;
/** @var int */
protected $chunkLoadCount = 0;
protected $spawnChunkLoadCount = 0;
/** @var int */
protected $chunksPerTick;
@ -962,8 +964,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->usedChunks[Level::chunkHash($x, $z)] = true;
$this->chunkLoadCount++;
$this->dataPacket($payload);
if($this->spawned){
@ -974,8 +974,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($this->chunkLoadCount >= $this->spawnThreshold and !$this->spawned){
$this->doFirstSpawn();
if($this->spawnChunkLoadCount !== -1 and ++$this->spawnChunkLoadCount >= $this->spawnThreshold){
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
$this->spawnChunkLoadCount = -1;
}
}
@ -1013,11 +1014,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
Timings::$playerChunkSendTimer->stopTiming();
}
protected function doFirstSpawn(){
public function doFirstSpawn(){
if($this->spawned){
return; //avoid player spawning twice (this can only happen on 3.x with a custom malicious client)
}
$this->spawned = true;
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
@ -1071,8 +1073,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
Timings::$playerChunkOrderTimer->startTiming();
$this->nextChunkOrderRun = 200;
$radius = $this->server->getAllowedViewDistance($this->viewDistance);
$radiusSquared = $radius ** 2;
@ -1149,6 +1149,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->loadQueue = $newOrder;
if(!empty($this->loadQueue) or !empty($unloadChunks)){
$pk = new NetworkChunkPublisherUpdatePacket();
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
$pk->z = $this->getFloorZ();
$pk->radius = $this->viewDistance * 16; //blocks, not chunks >.>
$this->dataPacket($pk);
}
Timings::$playerChunkOrderTimer->stopTiming();
}
@ -1762,7 +1770,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return;
}
if($this->nextChunkOrderRun-- <= 0){
if($this->nextChunkOrderRun !== PHP_INT_MAX and $this->nextChunkOrderRun-- <= 0){
$this->nextChunkOrderRun = PHP_INT_MAX;
$this->orderChunks();
}
@ -2087,6 +2096,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk->worldName = $this->server->getMotd();
$this->dataPacket($pk);
$this->sendDataPacket(new AvailableEntityIdentifiersPacket());
$this->level->sendTime($this);
$this->sendAttributes(true);
@ -2619,6 +2630,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(!$this->spawned or !$this->isAlive()){
return true;
}
if($packet->action === InteractPacket::ACTION_MOUSEOVER and $packet->target === 0){
//TODO HACK: silence useless spam (MCPE 1.8)
//this packet is EXPECTED to only be sent when interacting with an entity, but due to some messy Mojang
//hacks, it also sends it when changing the held item now, which causes us to think the inventory was closed
//when it wasn't.
return true;
}
$this->doCloseInventory();
@ -3379,92 +3397,82 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*/
final public function close($message = "", string $reason = "generic reason", bool $notify = true) : void{
if($this->isConnected() and !$this->closed){
if($notify and strlen($reason) > 0){
$pk = new DisconnectPacket();
$pk->message = $reason;
$this->directDataPacket($pk);
}
$this->interface->close($this, $notify ? $reason : "");
$this->sessionAdapter = null;
try{
if($notify and strlen($reason) > 0){
$pk = new DisconnectPacket();
$pk->message = $reason;
$this->directDataPacket($pk);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$this->stopSleep();
if($this->spawned){
$ev = new PlayerQuitEvent($this, $message, $reason);
$ev->call();
if($ev->getQuitMessage() != ""){
$this->server->broadcastMessage($ev->getQuitMessage());
}
$this->interface->close($this, $notify ? $reason : "");
$this->sessionAdapter = null;
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$this->save();
}
$this->stopSleep();
if($this->spawned){
$ev = new PlayerQuitEvent($this, $message, $reason);
$ev->call();
if($ev->getQuitMessage() != ""){
$this->server->broadcastMessage($ev->getQuitMessage());
if($this->isValid()){
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $chunkX, $chunkZ);
$this->level->unregisterChunkLoader($this, $chunkX, $chunkZ);
foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){
$entity->despawnFrom($this);
}
unset($this->usedChunks[$index]);
}
}
$this->usedChunks = [];
$this->loadQueue = [];
try{
$this->save();
}catch(\Throwable $e){
$this->server->getLogger()->critical("Failed to save player data for " . $this->getName());
$this->server->getLogger()->logException($e);
if($this->loggedIn){
$this->server->onPlayerLogout($this);
foreach($this->server->getOnlinePlayers() as $player){
if(!$player->canSee($this)){
$player->showPlayer($this);
}
}
$this->hiddenPlayers = [];
}
if($this->isValid()){
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $chunkX, $chunkZ);
$this->level->unregisterChunkLoader($this, $chunkX, $chunkZ);
foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){
$entity->despawnFrom($this);
}
unset($this->usedChunks[$index]);
}
}
$this->usedChunks = [];
$this->loadQueue = [];
$this->removeAllWindows(true);
$this->windows = [];
$this->windowIndex = [];
$this->cursorInventory = null;
$this->craftingGrid = null;
if($this->loggedIn){
$this->server->onPlayerLogout($this);
foreach($this->server->getOnlinePlayers() as $player){
if(!$player->canSee($this)){
$player->showPlayer($this);
}
}
$this->hiddenPlayers = [];
}
if($this->constructed){
parent::close();
}
$this->spawned = false;
$this->removeAllWindows(true);
$this->windows = [];
$this->windowIndex = [];
$this->cursorInventory = null;
$this->craftingGrid = null;
if($this->loggedIn){
$this->loggedIn = false;
$this->server->removeOnlinePlayer($this);
}
if($this->constructed){
parent::close();
}
$this->spawned = false;
$this->server->removePlayer($this);
if($this->loggedIn){
$this->loggedIn = false;
$this->server->removeOnlinePlayer($this);
}
$this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logOut", [
TextFormat::AQUA . $this->getName() . TextFormat::WHITE,
$this->ip,
$this->port,
$this->getServer()->getLanguage()->translateString($reason)
]));
$this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logOut", [
TextFormat::AQUA . $this->getName() . TextFormat::WHITE,
$this->ip,
$this->port,
$this->getServer()->getLanguage()->translateString($reason)
]));
$this->spawnPosition = null;
$this->spawnPosition = null;
if($this->perm !== null){
$this->perm->clearPermissions();
$this->perm = null;
}
}catch(\Throwable $e){
$this->server->getLogger()->logException($e);
}finally{
$this->server->removePlayer($this);
if($this->perm !== null){
$this->perm->clearPermissions();
$this->perm = null;
}
}
}
@ -3877,9 +3885,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function onChunkChanged(Chunk $chunk){
if(isset($this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())])){
$this->usedChunks[$hash] = false;
if(!$this->spawned){
$this->nextChunkOrderRun = 0;
}
$this->nextChunkOrderRun = 0;
}
}

View File

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

View File

@ -2173,7 +2173,7 @@ class Server{
"fullFile" => $e->getFile(),
"file" => $errfile,
"line" => $errline,
"trace" => Utils::getTrace(0, $trace)
"trace" => Utils::printableTrace($trace)
];
global $lastExceptionError, $lastError;

View File

@ -28,9 +28,9 @@ use pocketmine\event\block\BlockFormEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\level\Level;
use pocketmine\level\sound\FizzSound;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
abstract class Liquid extends Transparent{
@ -435,7 +435,7 @@ abstract class Liquid extends Transparent{
$ev->call();
if(!$ev->isCancelled()){
$this->level->setBlock($this, $ev->getNewState(), true, true);
$this->level->broadcastLevelSoundEvent($this->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_FIZZ, (int) ((2.6 + (lcg_value() - lcg_value()) * 0.8) * 1000));
$this->level->addSound(new FizzSound($this->add(0.5, 0.5, 0.5), 2.6 + (lcg_value() - lcg_value()) * 0.8));
}
return true;
}

View File

@ -31,6 +31,10 @@ class UnknownBlock extends Transparent{
return 0;
}
public function canBePlaced() : bool{
return false;
}
public function getDrops(Item $item) : array{
return [];
}

View File

@ -29,6 +29,7 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\lang\TranslationContainer;
use pocketmine\level\Level;
use pocketmine\level\particle\AngryVillagerParticle;
use pocketmine\level\particle\BlockForceFieldParticle;
use pocketmine\level\particle\BubbleParticle;
@ -84,14 +85,18 @@ class ParticleCommand extends VanillaCommand{
if($sender instanceof Player){
$level = $sender->getLevel();
$pos = new Vector3(
$this->getRelativeDouble($sender->getX(), $sender, $args[1]),
$this->getRelativeDouble($sender->getY(), $sender, $args[2], 0, Level::Y_MAX),
$this->getRelativeDouble($sender->getZ(), $sender, $args[3])
);
}else{
$level = $sender->getServer()->getDefaultLevel();
$pos = new Vector3((float) $args[1], (float) $args[2], (float) $args[3]);
}
$name = strtolower($args[0]);
$pos = new Vector3((float) $args[1], (float) $args[2], (float) $args[3]);
$xd = (float) $args[4];
$yd = (float) $args[5];
$zd = (float) $args[6];

View File

@ -127,8 +127,8 @@ class Attribute{
}
public function setMinValue(float $minValue){
if($minValue > $this->getMaxValue()){
throw new \InvalidArgumentException("Value $minValue is bigger than the maxValue!");
if($minValue > ($max = $this->getMaxValue())){
throw new \InvalidArgumentException("Minimum $minValue is greater than the maximum $max");
}
if($this->minValue != $minValue){
@ -143,8 +143,8 @@ class Attribute{
}
public function setMaxValue(float $maxValue){
if($maxValue < $this->getMinValue()){
throw new \InvalidArgumentException("Value $maxValue is bigger than the minValue!");
if($maxValue < ($min = $this->getMinValue())){
throw new \InvalidArgumentException("Maximum $maxValue is less than the minimum $min");
}
if($this->maxValue != $maxValue){
@ -160,7 +160,7 @@ class Attribute{
public function setDefaultValue(float $defaultValue){
if($defaultValue > $this->getMaxValue() or $defaultValue < $this->getMinValue()){
throw new \InvalidArgumentException("Value $defaultValue exceeds the range!");
throw new \InvalidArgumentException("Default $defaultValue is outside the range " . $this->getMinValue() . " - " . $this->getMaxValue());
}
if($this->defaultValue !== $defaultValue){
@ -171,7 +171,7 @@ class Attribute{
}
public function resetToDefault() : void{
$this->setValue($this->getDefaultValue());
$this->setValue($this->getDefaultValue(), true);
}
public function getValue() : float{
@ -188,7 +188,7 @@ class Attribute{
public function setValue(float $value, bool $fit = false, bool $forceSend = false){
if($value > $this->getMaxValue() or $value < $this->getMinValue()){
if(!$fit){
throw new \InvalidArgumentException("Value $value exceeds the range!");
throw new \InvalidArgumentException("Value $value is outside the range " . $this->getMinValue() . " - " . $this->getMaxValue());
}
$value = min(max($value, $this->getMinValue()), $this->getMaxValue());
}

View File

@ -175,6 +175,14 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_SCORE_TAG = 83; //string
public const DATA_BALLOON_ATTACHED_ENTITY = 84; //int64, entity unique ID of owner
public const DATA_PUFFERFISH_SIZE = 85; //byte
public const DATA_BOAT_BUBBLE_TIME = 86; //int (time in bubble column)
public const DATA_PLAYER_AGENT_EID = 87; //long
/* 88 (float) related to panda sitting
* 89 (float) related to panda sitting
* 90 (unknown) */
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_FLAG_ONFIRE = 0;
@ -231,13 +239,23 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_ENCHANTED = 51;
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 52; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
public const DATA_FLAG_CONTAINER_PRIVATE = 53; //inventory is private, doesn't drop contents when killed if true
//54 TransformationComponent
public const DATA_FLAG_TRANSFORMING = 54;
public const DATA_FLAG_SPIN_ATTACK = 55;
public const DATA_FLAG_SWIMMING = 56;
public const DATA_FLAG_BRIBED = 57; //dolphins have this set when they go to find treasure for the player
public const DATA_FLAG_PREGNANT = 58;
public const DATA_FLAG_LAYING_EGG = 59;
//60 ??
public const DATA_FLAG_RIDER_CAN_PICK = 60; //???
public const DATA_FLAG_TRANSITION_SITTING = 61;
public const DATA_FLAG_EATING = 62;
public const DATA_FLAG_LAYING_DOWN = 63;
public const DATA_FLAG_SNEEZING = 64;
public const DATA_FLAG_TRUSTING = 65;
public const DATA_FLAG_ROLLING = 66;
public const DATA_FLAG_SCARED = 67;
public const DATA_FLAG_IN_SCAFFOLDING = 68;
public const DATA_FLAG_OVER_SCAFFOLDING = 69;
public const DATA_FLAG_FALL_THROUGH_SCAFFOLDING = 70;
public static $entityCount = 1;
/** @var Entity[] */
@ -637,6 +655,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param float $value
*/
public function setScale(float $value) : void{
if($value <= 0){
throw new \InvalidArgumentException("Scale must be greater than 0");
}
$multiplier = $value / $this->getScale();
$this->width *= $multiplier;
@ -2078,7 +2099,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @return bool
*/
public function getGenericFlag(int $flagId) : bool{
return $this->getDataFlag(self::DATA_FLAGS, $flagId);
return $this->getDataFlag($flagId >= 64 ? self::DATA_FLAGS2 : self::DATA_FLAGS, $flagId % 64);
}
/**
@ -2088,7 +2109,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param bool $value
*/
public function setGenericFlag(int $flagId, bool $value = true) : void{
$this->setDataFlag(self::DATA_FLAGS, $flagId, $value, self::DATA_TYPE_LONG);
$this->setDataFlag($flagId >= 64 ? self::DATA_FLAGS2 : self::DATA_FLAGS, $flagId % 64, $value, self::DATA_TYPE_LONG);
}
/**

View File

@ -71,8 +71,9 @@ interface EntityIds{
public const ENDER_DRAGON = 53;
public const SHULKER = 54;
public const ENDERMITE = 55;
public const LEARN_TO_CODE_MASCOT = 56;
public const AGENT = 56, LEARN_TO_CODE_MASCOT = 56;
public const VINDICATOR = 57;
public const PHANTOM = 58;
public const ARMOR_STAND = 61;
public const TRIPOD_CAMERA = 62;
@ -86,8 +87,9 @@ interface EntityIds{
public const EYE_OF_ENDER_SIGNAL = 70;
public const ENDER_CRYSTAL = 71;
public const FIREWORKS_ROCKET = 72;
public const TRIDENT = 73;
public const THROWN_TRIDENT = 73, TRIDENT = 73;
public const TURTLE = 74;
public const CAT = 75;
public const SHULKER_BULLET = 76;
public const FISHING_HOOK = 77;
public const CHALKBOARD = 78;
@ -97,7 +99,7 @@ interface EntityIds{
public const EGG = 82;
public const PAINTING = 83;
public const MINECART = 84;
public const LARGE_FIREBALL = 85;
public const FIREBALL = 85, LARGE_FIREBALL = 85;
public const SPLASH_POTION = 86;
public const ENDER_PEARL = 87;
public const LEASH_KNOT = 88;
@ -122,6 +124,7 @@ interface EntityIds{
public const PUFFERFISH = 108;
public const SALMON = 109;
public const DROWNED = 110;
public const TROPICAL_FISH = 111;
public const FISH = 112;
public const TROPICALFISH = 111, TROPICAL_FISH = 111;
public const COD = 112, FISH = 112;
public const PANDA = 113;
}

View File

@ -450,7 +450,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
private function playLevelUpSound(int $newLevel) : void{
$volume = 0x10000000 * (min(30, $newLevel) / 5); //No idea why such odd numbers, but this works...
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_LEVELUP, 1, (int) $volume);
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_LEVELUP, (int) $volume);
}
/**

View File

@ -571,7 +571,10 @@ abstract class Living extends Entity implements Damageable{
}
if($e !== null){
if($e->isOnFire()){
if((
$source->getCause() === EntityDamageEvent::CAUSE_PROJECTILE or
$source->getCause() === EntityDamageEvent::CAUSE_ENTITY_ATTACK
) and $e->isOnFire()){
$this->setOnFire(2 * $this->level->getDifficulty());
}

View File

@ -27,7 +27,11 @@ use pocketmine\lang\TextContainer;
use pocketmine\Player;
/**
* Called when a player joins the server, after sending all the spawn packets
* Called when the player spawns in the world after logging in, when they first see the terrain.
*
* Note: A lot of data is sent to the player between login and this event. Disconnecting the player during this event
* will cause this data to be wasted. Prefer disconnecting at login-time if possible to minimize bandwidth wastage.
* @see PlayerLoginEvent
*/
class PlayerJoinEvent extends PlayerEvent{
/** @var string|TextContainer */

View File

@ -27,7 +27,9 @@ use pocketmine\event\Cancellable;
use pocketmine\Player;
/**
* Called when a player joins, after things have been correctly set up (you can change anything now)
* Called after the player has successfully authenticated, before it spawns. The player is on the loading screen when
* this is called.
* Cancelling this event will cause the player to be disconnected with the kick message set.
*/
class PlayerLoginEvent extends PlayerEvent implements Cancellable{
/** @var string */

View File

@ -27,7 +27,17 @@ use pocketmine\event\Cancellable;
use pocketmine\Player;
/**
* Called when the player logs in, before things have been set up
* Called when a player connects to the server, prior to authentication taking place.
* Cancelling this event will cause the player to be disconnected with the kick message set.
*
* This event should be used to decide if the player may continue to login to the server. Do things like checking
* bans, whitelisting, server-full etc here.
*
* WARNING: Any information about the player CANNOT be trusted at this stage, because they are not authenticated and
* could be a hacker posing as another player.
*
* WARNING: Due to internal bad architecture, the player is not fully constructed at this stage, and errors might occur
* when calling API methods on the player. Tread with caution.
*/
class PlayerPreLoginEvent extends PlayerEvent implements Cancellable{
/** @var string */

View File

@ -61,19 +61,29 @@ class ChestInventory extends ContainerInventory{
return $this->holder;
}
protected function getOpenSound() : int{
return LevelSoundEventPacket::SOUND_CHEST_OPEN;
}
protected function getCloseSound() : int{
return LevelSoundEventPacket::SOUND_CHEST_CLOSED;
}
public function onOpen(Player $who) : void{
parent::onOpen($who);
if(count($this->getViewers()) === 1 and $this->getHolder()->isValid()){
//TODO: this crap really shouldn't be managed by the inventory
$this->broadcastBlockEventPacket(true);
$this->getHolder()->getLevel()->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_CHEST_OPEN);
$this->getHolder()->getLevel()->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), $this->getOpenSound());
}
}
public function onClose(Player $who) : void{
if(count($this->getViewers()) === 1 and $this->getHolder()->isValid()){
//TODO: this crap really shouldn't be managed by the inventory
$this->broadcastBlockEventPacket(false);
$this->getHolder()->getLevel()->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_CHEST_CLOSED);
$this->getHolder()->getLevel()->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), $this->getCloseSound());
}
parent::onClose($who);
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\level\Position;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\tile\EnderChest;
@ -58,6 +59,14 @@ class EnderChestInventory extends ChestInventory{
$this->holder->setLevel($enderChest->getLevel());
}
protected function getOpenSound() : int{
return LevelSoundEventPacket::SOUND_ENDERCHEST_OPEN;
}
protected function getCloseSound() : int{
return LevelSoundEventPacket::SOUND_ENDERCHEST_CLOSED;
}
/**
* This override is here for documentation and code completion purposes only.
* @return Position

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\EntityIds;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\math\Vector3;
@ -65,9 +66,7 @@ abstract class ProjectileItem extends Item{
}else{
$projectile->spawnToAll();
//319 is the Player's entity type ID in MCPE, with all its flags (which we don't know)
//without this, it doesn't work at all.
$player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_THROW, 319);
$player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_THROW, 0, EntityIds::PLAYER);
}
}elseif($projectile !== null){
$projectile->spawnToAll();

View File

@ -49,6 +49,8 @@ use pocketmine\level\format\ChunkException;
use pocketmine\level\format\EmptySubChunk;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\io\ChunkRequestTask;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
use pocketmine\level\format\io\LevelProvider;
use pocketmine\level\generator\Generator;
use pocketmine\level\generator\GeneratorManager;
@ -69,6 +71,7 @@ use pocketmine\metadata\Metadatable;
use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
@ -450,13 +453,14 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($sound, $e);
if(!empty($pk)){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($sound, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}
@ -465,13 +469,14 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($particle, $e);
if(!empty($pk)){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($particle, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}
@ -500,16 +505,16 @@ class Level implements ChunkManager, Metadatable{
*
* @param Vector3 $pos
* @param int $soundId
* @param int $pitch
* @param int $extraData
* @param int $entityTypeId
* @param bool $isBabyMob
* @param bool $disableRelativeVolume If true, all players receiving this sound-event will hear the sound at full volume regardless of distance
*/
public function broadcastLevelSoundEvent(Vector3 $pos, int $soundId, int $pitch = 1, int $extraData = -1, bool $isBabyMob = false, bool $disableRelativeVolume = false){
public function broadcastLevelSoundEvent(Vector3 $pos, int $soundId, int $extraData = -1, int $entityTypeId = -1, bool $isBabyMob = false, bool $disableRelativeVolume = false){
$pk = new LevelSoundEventPacket();
$pk->sound = $soundId;
$pk->pitch = $pitch;
$pk->extraData = $extraData;
$pk->entityType = AddEntityPacket::LEGACY_ID_MAP_BC[$entityTypeId] ?? ":";
$pk->isBabyMob = $isBabyMob;
$pk->disableRelativeVolume = $disableRelativeVolume;
$pk->position = $pos->asVector3();
@ -1935,7 +1940,7 @@ class Level implements ChunkManager, Metadatable{
}
if($playSound){
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, $hand->getRuntimeId());
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, $hand->getRuntimeId());
}
$item->pop();
@ -2438,7 +2443,7 @@ class Level implements ChunkManager, Metadatable{
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
$oldChunk = $this->getChunk($chunkX, $chunkZ, false);
if($oldChunk !== null){
if($oldChunk !== null and $oldChunk !== $chunk){
if($unload){
$this->unloadChunk($chunkX, $chunkZ, false, false);
}else{
@ -2460,6 +2465,10 @@ class Level implements ChunkManager, Metadatable{
unset($this->blockCache[$chunkHash]);
unset($this->chunkCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
if(isset($this->chunkSendTasks[$chunkHash])){ //invalidate pending caches
$this->chunkSendTasks[$chunkHash]->cancelRun();
unset($this->chunkSendTasks[$chunkHash]);
}
$chunk->setChanged();
if(!$this->isChunkInUse($chunkX, $chunkZ)){
@ -2743,10 +2752,9 @@ class Level implements ChunkManager, Metadatable{
try{
$chunk = $this->provider->loadChunk($x, $z);
}catch(\Exception $e){
}catch(CorruptedChunkException | UnsupportedChunkFormatException $e){
$logger = $this->server->getLogger();
$logger->critical("An error occurred while loading chunk x=$x z=$z: " . $e->getMessage());
$logger->logException($e);
$logger->critical("Failed to load chunk x=$x z=$z: " . $e->getMessage());
}
if($chunk === null and $create){
@ -2827,23 +2835,17 @@ class Level implements ChunkManager, Metadatable{
return false;
}
try{
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){
$this->provider->saveChunk($chunk);
}
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){
$this->provider->saveChunk($chunk);
}
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkUnloaded($chunk);
}
$chunk->onUnload();
}catch(\Throwable $e){
$logger = $this->server->getLogger();
$logger->error($this->server->getLanguage()->translateString("pocketmine.level.chunkUnloadError", [$e->getMessage()]));
$logger->logException($e);
}
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkUnloaded($chunk);
}
$chunk->onUnload();
}
unset($this->chunks[$chunkHash]);

View File

@ -24,6 +24,8 @@ declare(strict_types=1);
namespace pocketmine\level\format\io;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
use pocketmine\level\LevelException;
use pocketmine\math\Vector3;
use pocketmine\nbt\BigEndianNBTStream;
@ -152,6 +154,14 @@ abstract class BaseLevelProvider implements LevelProvider{
file_put_contents($this->getPath() . "level.dat", $buffer);
}
/**
* @param int $chunkX
* @param int $chunkZ
*
* @return Chunk|null
* @throws CorruptedChunkException
* @throws UnsupportedChunkFormatException
*/
public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk{
return $this->readChunk($chunkX, $chunkZ);
}
@ -163,6 +173,14 @@ abstract class BaseLevelProvider implements LevelProvider{
$this->writeChunk($chunk);
}
/**
* @param int $chunkX
* @param int $chunkZ
*
* @return Chunk|null
* @throws UnsupportedChunkFormatException
* @throws CorruptedChunkException
*/
abstract protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk;
abstract protected function writeChunk(Chunk $chunk) : void;

View File

@ -24,6 +24,8 @@ declare(strict_types=1);
namespace pocketmine\level\format\io;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
use pocketmine\math\Vector3;
interface LevelProvider{
@ -100,8 +102,8 @@ interface LevelProvider{
*
* @return null|Chunk
*
* @throws \Exception any of a range of exceptions that could be thrown while reading chunks. See individual
* implementations for details.
* @throws CorruptedChunkException
* @throws UnsupportedChunkFormatException
*/
public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk;

View File

@ -283,6 +283,11 @@ class LevelDB extends BaseLevelProvider{
/** @var SubChunk[] $subChunks */
$subChunks = [];
/** @var int[] $heightMap */
$heightMap = [];
/** @var string $biomeIds */
$biomeIds = "";
/** @var bool $lightPopulated */
$lightPopulated = true;
@ -325,10 +330,12 @@ class LevelDB extends BaseLevelProvider{
}
}
$binaryStream->setBuffer($this->db->get($index . self::TAG_DATA_2D), 0);
if(($maps2d = $this->db->get($index . self::TAG_DATA_2D)) !== false){
$binaryStream->setBuffer($maps2d, 0);
$heightMap = array_values(unpack("v*", $binaryStream->get(512)));
$biomeIds = $binaryStream->get(256);
$heightMap = array_values(unpack("v*", $binaryStream->get(512)));
$biomeIds = $binaryStream->get(256);
}
break;
case 2: // < MCPE 1.0
$binaryStream->setBuffer($this->db->get($index . self::TAG_LEGACY_TERRAIN));

View File

@ -24,8 +24,8 @@ declare(strict_types=1);
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\ChunkException;
use pocketmine\level\format\io\ChunkUtils;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\SubChunk;
use pocketmine\nbt\BigEndianNBTStream;
use pocketmine\nbt\NBT;
@ -99,7 +99,7 @@ class Anvil extends McRegion{
$nbt = new BigEndianNBTStream();
$chunk = $nbt->readCompressed($data);
if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){
throw new ChunkException("Invalid NBT format");
throw new CorruptedChunkException("'Level' key is missing from chunk NBT");
}
$chunk = $chunk->getCompoundTag("Level");

View File

@ -24,9 +24,9 @@ declare(strict_types=1);
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\ChunkException;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\io\ChunkUtils;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\SubChunk;
use pocketmine\level\generator\GeneratorManager;
use pocketmine\level\Level;
@ -107,12 +107,13 @@ class McRegion extends BaseLevelProvider{
* @param string $data
*
* @return Chunk
* @throws CorruptedChunkException
*/
protected function nbtDeserialize(string $data) : Chunk{
$nbt = new BigEndianNBTStream();
$chunk = $nbt->readCompressed($data);
if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){
throw new ChunkException("Invalid NBT format");
throw new CorruptedChunkException("'Level' key is missing from chunk NBT");
}
$chunk = $chunk->getCompoundTag("Level");
@ -348,6 +349,14 @@ class McRegion extends BaseLevelProvider{
}
}
/**
* @param int $chunkX
* @param int $chunkZ
*
* @return Chunk|null
*
* @throws CorruptedChunkException
*/
protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{
$regionX = $regionZ = null;
self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ);

View File

@ -33,7 +33,7 @@ class RegionLoader{
public const COMPRESSION_GZIP = 1;
public const COMPRESSION_ZLIB = 2;
public const MAX_SECTOR_LENGTH = 256 << 12; //256 sectors, (1 MiB)
public const MAX_SECTOR_LENGTH = 255 << 12; //255 sectors (~0.996 MiB)
public const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
public static $COMPRESSION_LEVEL = 7;
@ -198,81 +198,6 @@ class RegionLoader{
}
}
public function doSlowCleanUp() : int{
for($i = 0; $i < 1024; ++$i){
if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){
continue;
}
fseek($this->filePointer, $this->locationTable[$i][0] << 12);
$chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12);
$length = Binary::readInt(substr($chunk, 0, 4));
if($length <= 1){
$this->locationTable[$i] = [0, 0, 0]; //Non-generated chunk, remove it from index
}
try{
$chunk = zlib_decode(substr($chunk, 5));
}catch(\Throwable $e){
$this->locationTable[$i] = [0, 0, 0]; //Corrupted chunk, remove it
continue;
}
$chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, ZLIB_ENCODING_DEFLATE, 9);
$chunk = Binary::writeInt(strlen($chunk)) . $chunk;
$sectors = (int) ceil(strlen($chunk) / 4096);
if($sectors > $this->locationTable[$i][1]){
$this->locationTable[$i][0] = $this->lastSector + 1;
$this->lastSector += $sectors;
}
fseek($this->filePointer, $this->locationTable[$i][0] << 12);
fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT));
}
$this->writeLocationTable();
$n = $this->cleanGarbage();
$this->writeLocationTable();
return $n;
}
private function cleanGarbage() : int{
$sectors = [];
foreach($this->locationTable as $index => $data){ //Calculate file usage
if($data[0] === 0 or $data[1] === 0){
$this->locationTable[$index] = [0, 0, 0];
continue;
}
for($i = 0; $i < $data[1]; ++$i){
$sectors[$data[0]] = $index;
}
}
if(count($sectors) === ($this->lastSector - 2)){ //No collection needed
return 0;
}
ksort($sectors);
$shift = 0;
$lastSector = 1; //First chunk - 1
fseek($this->filePointer, 8192);
$sector = 2;
foreach($sectors as $sector => $index){
if(($sector - $lastSector) > 1){
$shift += $sector - $lastSector - 1;
}
if($shift > 0){
fseek($this->filePointer, $sector << 12);
$old = fread($this->filePointer, 4096);
fseek($this->filePointer, ($sector - $shift) << 12);
fwrite($this->filePointer, $old, 4096);
}
$this->locationTable[$index][0] -= $shift;
$lastSector = $sector;
}
ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written
return $shift;
}
protected function loadLocationTable(){
fseek($this->filePointer, 0);
$this->lastSector = 1;

View File

@ -33,10 +33,8 @@ abstract class Tree{
public $overridable = [
Block::AIR => true,
Block::SAPLING => true,
Block::LOG => true,
Block::LEAVES => true,
Block::SNOW_LAYER => true,
Block::LOG2 => true,
Block::LEAVES2 => true
];

View File

@ -72,6 +72,16 @@ abstract class Particle extends Vector3{
public const TYPE_SPIT = 42;
public const TYPE_TOTEM = 43;
public const TYPE_FOOD = 44;
public const TYPE_FIREWORKS_STARTER = 45;
public const TYPE_FIREWORKS_SPARK = 46;
public const TYPE_FIREWORKS_OVERLAY = 47;
public const TYPE_BALLOON_GAS = 48;
public const TYPE_COLORED_FLAME = 49;
public const TYPE_SPARKLER = 50;
public const TYPE_CONDUIT = 51;
public const TYPE_BUBBLE_COLUMN_UP = 52;
public const TYPE_BUBBLE_COLUMN_DOWN = 53;
public const TYPE_SNEEZE = 54;
/**
* @return DataPacket|DataPacket[]

View File

@ -32,6 +32,8 @@ use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
@ -69,6 +71,7 @@ use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -79,6 +82,7 @@ use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveEntityDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
@ -126,6 +130,7 @@ use pocketmine\network\mcpe\protocol\ShowProfilePacket;
use pocketmine\network\mcpe\protocol\ShowStoreOfferPacket;
use pocketmine\network\mcpe\protocol\SimpleEventPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\SpawnParticleEffectPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\StopSoundPacket;
use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket;
@ -237,7 +242,7 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
return false;
}
@ -608,4 +613,25 @@ abstract class NetworkSession{
public function handleScriptCustomEvent(ScriptCustomEventPacket $packet) : bool{
return false;
}
public function handleSpawnParticleEffect(SpawnParticleEffectPacket $packet) : bool{
return false;
}
public function handleAvailableEntityIdentifiers(AvailableEntityIdentifiersPacket $packet) : bool{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return false;
}
public function handleNetworkChunkPublisherUpdate(NetworkChunkPublisherUpdatePacket $packet) : bool{
return false;
}
public function handleBiomeDefinitionList(BiomeDefinitionListPacket $packet) : bool{
return false;
}
}

View File

@ -44,6 +44,7 @@ use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -58,6 +59,7 @@ use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
@ -125,8 +127,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleMovePlayer($packet);
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
return true; //useless leftover from 1.8
}
public function handleEntityEvent(EntityEventPacket $packet) : bool{
@ -280,4 +282,13 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false; //TODO: GUI stuff
}
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
$this->player->doFirstSpawn();
return true;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\entity\Attribute;
use pocketmine\entity\EntityIds;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\EntityLink;
@ -33,6 +34,114 @@ use pocketmine\network\mcpe\protocol\types\EntityLink;
class AddEntityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_ENTITY_PACKET;
/*
* Really really really really really nasty hack, to preserve backwards compatibility.
* We can't transition to string IDs within 3.x because the network IDs (the integer ones) are exposed
* to the API in some places (for god's sake shoghi).
*
* TODO: remove this on 4.0
*/
public const LEGACY_ID_MAP_BC = [
EntityIds::NPC => "minecraft:npc",
EntityIds::PLAYER => "minecraft:player",
EntityIds::WITHER_SKELETON => "minecraft:wither_skeleton",
EntityIds::HUSK => "minecraft:husk",
EntityIds::STRAY => "minecraft:stray",
EntityIds::WITCH => "minecraft:witch",
EntityIds::ZOMBIE_VILLAGER => "minecraft:zombie_villager",
EntityIds::BLAZE => "minecraft:blaze",
EntityIds::MAGMA_CUBE => "minecraft:magma_cube",
EntityIds::GHAST => "minecraft:ghast",
EntityIds::CAVE_SPIDER => "minecraft:cave_spider",
EntityIds::SILVERFISH => "minecraft:silverfish",
EntityIds::ENDERMAN => "minecraft:enderman",
EntityIds::SLIME => "minecraft:slime",
EntityIds::ZOMBIE_PIGMAN => "minecraft:zombie_pigman",
EntityIds::SPIDER => "minecraft:spider",
EntityIds::SKELETON => "minecraft:skeleton",
EntityIds::CREEPER => "minecraft:creeper",
EntityIds::ZOMBIE => "minecraft:zombie",
EntityIds::SKELETON_HORSE => "minecraft:skeleton_horse",
EntityIds::MULE => "minecraft:mule",
EntityIds::DONKEY => "minecraft:donkey",
EntityIds::DOLPHIN => "minecraft:dolphin",
EntityIds::TROPICALFISH => "minecraft:tropicalfish",
EntityIds::WOLF => "minecraft:wolf",
EntityIds::SQUID => "minecraft:squid",
EntityIds::DROWNED => "minecraft:drowned",
EntityIds::SHEEP => "minecraft:sheep",
EntityIds::MOOSHROOM => "minecraft:mooshroom",
EntityIds::PANDA => "minecraft:panda",
EntityIds::SALMON => "minecraft:salmon",
EntityIds::PIG => "minecraft:pig",
EntityIds::VILLAGER => "minecraft:villager",
EntityIds::COD => "minecraft:cod",
EntityIds::PUFFERFISH => "minecraft:pufferfish",
EntityIds::COW => "minecraft:cow",
EntityIds::CHICKEN => "minecraft:chicken",
EntityIds::BALLOON => "minecraft:balloon",
EntityIds::LLAMA => "minecraft:llama",
EntityIds::IRON_GOLEM => "minecraft:iron_golem",
EntityIds::RABBIT => "minecraft:rabbit",
EntityIds::SNOW_GOLEM => "minecraft:snow_golem",
EntityIds::BAT => "minecraft:bat",
EntityIds::OCELOT => "minecraft:ocelot",
EntityIds::HORSE => "minecraft:horse",
EntityIds::CAT => "minecraft:cat",
EntityIds::POLAR_BEAR => "minecraft:polar_bear",
EntityIds::ZOMBIE_HORSE => "minecraft:zombie_horse",
EntityIds::TURTLE => "minecraft:turtle",
EntityIds::PARROT => "minecraft:parrot",
EntityIds::GUARDIAN => "minecraft:guardian",
EntityIds::ELDER_GUARDIAN => "minecraft:elder_guardian",
EntityIds::VINDICATOR => "minecraft:vindicator",
EntityIds::WITHER => "minecraft:wither",
EntityIds::ENDER_DRAGON => "minecraft:ender_dragon",
EntityIds::SHULKER => "minecraft:shulker",
EntityIds::ENDERMITE => "minecraft:endermite",
EntityIds::MINECART => "minecraft:minecart",
EntityIds::HOPPER_MINECART => "minecraft:hopper_minecart",
EntityIds::TNT_MINECART => "minecraft:tnt_minecart",
EntityIds::CHEST_MINECART => "minecraft:chest_minecart",
EntityIds::COMMAND_BLOCK_MINECART => "minecraft:command_block_minecart",
EntityIds::ARMOR_STAND => "minecraft:armor_stand",
EntityIds::ITEM => "minecraft:item",
EntityIds::TNT => "minecraft:tnt",
EntityIds::FALLING_BLOCK => "minecraft:falling_block",
EntityIds::XP_BOTTLE => "minecraft:xp_bottle",
EntityIds::XP_ORB => "minecraft:xp_orb",
EntityIds::EYE_OF_ENDER_SIGNAL => "minecraft:eye_of_ender_signal",
EntityIds::ENDER_CRYSTAL => "minecraft:ender_crystal",
EntityIds::SHULKER_BULLET => "minecraft:shulker_bullet",
EntityIds::FISHING_HOOK => "minecraft:fishing_hook",
EntityIds::DRAGON_FIREBALL => "minecraft:dragon_fireball",
EntityIds::ARROW => "minecraft:arrow",
EntityIds::SNOWBALL => "minecraft:snowball",
EntityIds::EGG => "minecraft:egg",
EntityIds::PAINTING => "minecraft:painting",
EntityIds::THROWN_TRIDENT => "minecraft:thrown_trident",
EntityIds::FIREBALL => "minecraft:fireball",
EntityIds::SPLASH_POTION => "minecraft:splash_potion",
EntityIds::ENDER_PEARL => "minecraft:ender_pearl",
EntityIds::LEASH_KNOT => "minecraft:leash_knot",
EntityIds::WITHER_SKULL => "minecraft:wither_skull",
EntityIds::WITHER_SKULL_DANGEROUS => "minecraft:wither_skull_dangerous",
EntityIds::BOAT => "minecraft:boat",
EntityIds::LIGHTNING_BOLT => "minecraft:lightning_bolt",
EntityIds::SMALL_FIREBALL => "minecraft:small_fireball",
EntityIds::LLAMA_SPIT => "minecraft:llama_spit",
EntityIds::AREA_EFFECT_CLOUD => "minecraft:area_effect_cloud",
EntityIds::LINGERING_POTION => "minecraft:lingering_potion",
EntityIds::FIREWORKS_ROCKET => "minecraft:fireworks_rocket",
EntityIds::EVOCATION_FANG => "minecraft:evocation_fang",
EntityIds::EVOCATION_ILLAGER => "minecraft:evocation_illager",
EntityIds::VEX => "minecraft:vex",
EntityIds::AGENT => "minecraft:agent",
EntityIds::ICE_BOMB => "minecraft:ice_bomb",
EntityIds::PHANTOM => "minecraft:phantom",
EntityIds::TRIPOD_CAMERA => "minecraft:tripod_camera"
];
/** @var int|null */
public $entityUniqueId = null; //TODO
/** @var int */
@ -60,7 +169,10 @@ class AddEntityPacket extends DataPacket{
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->type = $this->getUnsignedVarInt();
$this->type = array_search($t = $this->getString(), self::LEGACY_ID_MAP_BC);
if($this->type === false){
throw new \UnexpectedValueException("Can't map ID $t to legacy ID");
}
$this->position = $this->getVector3();
$this->motion = $this->getVector3();
$this->pitch = $this->getLFloat();
@ -95,7 +207,7 @@ class AddEntityPacket extends DataPacket{
protected function encodePayload(){
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putUnsignedVarInt($this->type);
$this->putString(self::LEGACY_ID_MAP_BC[$this->type]);
$this->putVector3($this->position);
$this->putVector3Nullable($this->motion);
$this->putLFloat($this->pitch);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,47 @@
<?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 BiomeDefinitionListPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::BIOME_DEFINITION_LIST_PACKET;
/** @var string */
public $namedtag;
protected function decodePayload(){
$this->namedtag = $this->getRemaining();
}
protected function encodePayload(){
$this->put($this->namedtag);
}
public function handle(NetworkSession $session) : bool{
return $session->handleBiomeDefinitionList($this);
}
}

View File

@ -114,7 +114,9 @@ class ClientboundMapItemDataPacket extends DataPacket{
$this->yOffset = $this->getVarInt();
$count = $this->getUnsignedVarInt();
assert($count === $this->width * $this->height);
if($count !== $this->width * $this->height){
throw new \UnexpectedValueException("Expected colour count of " . ($this->height * $this->width) . " (height $this->height * width $this->width), got $count");
}
for($y = 0; $y < $this->height; ++$y){
for($x = 0; $x < $this->width; ++$x){

View File

@ -76,7 +76,9 @@ abstract class DataPacket extends NetworkBinaryStream{
protected function decodeHeader(){
$pid = $this->getUnsignedVarInt();
assert($pid === static::NETWORK_ID);
if($pid !== static::NETWORK_ID){
throw new \UnexpectedValueException("Expected " . static::NETWORK_ID . " for packet ID, got $pid");
}
}
/**
@ -139,10 +141,10 @@ abstract class DataPacket extends NetworkBinaryStream{
}
public function __get($name){
throw new \Error("Cannot read non-existing field \"$name\"");
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
}
public function __set($name, $value){
throw new \Error("Cannot write non-existing field \"$name\"");
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
}
}

View File

@ -242,7 +242,48 @@ class LevelSoundEventPacket extends DataPacket{
public const SOUND_CONVERT_TO_DROWNED = 211;
public const SOUND_BUCKET_FILL_FISH = 212;
public const SOUND_BUCKET_EMPTY_FISH = 213;
public const SOUND_UNDEFINED = 214;
public const SOUND_BUBBLE_UP = 214;
public const SOUND_BUBBLE_DOWN = 215;
public const SOUND_BUBBLE_POP = 216;
public const SOUND_BUBBLE_UPINSIDE = 217;
public const SOUND_BUBBLE_DOWNINSIDE = 218;
public const SOUND_HURT_BABY = 219;
public const SOUND_DEATH_BABY = 220;
public const SOUND_STEP_BABY = 221;
public const SOUND_BORN = 223;
public const SOUND_BLOCK_TURTLE_EGG_BREAK = 224;
public const SOUND_BLOCK_TURTLE_EGG_CRACK = 225;
public const SOUND_BLOCK_TURTLE_EGG_HATCH = 226;
public const SOUND_BLOCK_TURTLE_EGG_ATTACK = 228;
public const SOUND_BEACON_ACTIVATE = 229;
public const SOUND_BEACON_AMBIENT = 230;
public const SOUND_BEACON_DEACTIVATE = 231;
public const SOUND_BEACON_POWER = 232;
public const SOUND_CONDUIT_ACTIVATE = 233;
public const SOUND_CONDUIT_AMBIENT = 234;
public const SOUND_CONDUIT_ATTACK = 235;
public const SOUND_CONDUIT_DEACTIVATE = 236;
public const SOUND_CONDUIT_SHORT = 237;
public const SOUND_SWOOP = 238;
public const SOUND_BLOCK_BAMBOO_SAPLING_PLACE = 239;
public const SOUND_PRESNEEZE = 240;
public const SOUND_SNEEZE = 241;
public const SOUND_AMBIENT_TAME = 242;
public const SOUND_SCARED = 243;
public const SOUND_BLOCK_SCAFFOLDING_CLIMB = 244;
public const SOUND_CROSSBOW_LOADING_START = 245;
public const SOUND_CROSSBOW_LOADING_MIDDLE = 246;
public const SOUND_CROSSBOW_LOADING_END = 247;
public const SOUND_CROSSBOW_SHOOT = 248;
public const SOUND_CROSSBOW_QUICK_CHARGE_START = 249;
public const SOUND_CROSSBOW_QUICK_CHARGE_MIDDLE = 250;
public const SOUND_CROSSBOW_QUICK_CHARGE_END = 251;
public const SOUND_AMBIENT_AGGRESSIVE = 252;
public const SOUND_AMBIENT_WORRIED = 253;
public const SOUND_CANT_BREED = 254;
public const SOUND_UNDEFINED = 255;
/** @var int */
public $sound;
@ -250,8 +291,8 @@ class LevelSoundEventPacket extends DataPacket{
public $position;
/** @var int */
public $extraData = -1;
/** @var int */
public $pitch = 1;
/** @var string */
public $entityType = ":"; //???
/** @var bool */
public $isBabyMob = false; //...
/** @var bool */
@ -261,7 +302,7 @@ class LevelSoundEventPacket extends DataPacket{
$this->sound = $this->getByte();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->pitch = $this->getVarInt();
$this->entityType = $this->getString();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
@ -270,7 +311,7 @@ class LevelSoundEventPacket extends DataPacket{
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putVarInt($this->pitch);
$this->putString($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}

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.8 refactor, does nothing
*/
class LevelSoundEventPacketV1 extends DataPacket{
public const NETWORK_ID = ProtocolInfo::LEVEL_SOUND_EVENT_PACKET_V1;
/** @var int */
public $sound;
/** @var Vector3 */
public $position;
/** @var int */
public $extraData = 0;
/** @var int */
public $entityType = 1;
/** @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->getVarInt();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
protected function encodePayload(){
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putVarInt($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}
public function handle(NetworkSession $session) : bool{
return $session->handleLevelSoundEventPacketV1($this);
}
}

View File

@ -88,7 +88,7 @@ class LoginPacket extends DataPacket{
$logger = MainLogger::getLogger();
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version " . ($this->protocol ?? "unknown") . "): " . $e->getMessage());
foreach(Utils::getTrace(0, $e->getTrace()) as $line){
foreach(Utils::printableTrace($e->getTrace()) as $line){
$logger->debug($line);
}
}

View File

@ -0,0 +1,55 @@
<?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 NetworkChunkPublisherUpdatePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $radius;
protected function decodePayload(){
$this->getSignedBlockPosition($this->x, $this->y, $this->z);
$this->radius = $this->getUnsignedVarInt();
}
protected function encodePayload(){
$this->putSignedBlockPosition($this->x, $this->y, $this->z);
$this->putUnsignedVarInt($this->radius);
}
public function handle(NetworkSession $session) : bool{
return $session->handleNetworkChunkPublisherUpdate($this);
}
}

View File

@ -54,7 +54,7 @@ class PacketPool{
static::registerPacket(new UpdateBlockPacket());
static::registerPacket(new AddPaintingPacket());
static::registerPacket(new ExplodePacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new LevelSoundEventPacketV1());
static::registerPacket(new LevelEventPacket());
static::registerPacket(new BlockEventPacket());
static::registerPacket(new EntityEventPacket());
@ -147,6 +147,11 @@ class PacketPool{
static::registerPacket(new UpdateSoftEnumPacket());
static::registerPacket(new NetworkStackLatencyPacket());
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new SpawnParticleEffectPacket());
static::registerPacket(new AvailableEntityIdentifiersPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new NetworkChunkPublisherUpdatePacket());
static::registerPacket(new BiomeDefinitionListPacket());
static::registerPacket(new BatchPacket());
}
@ -178,4 +183,5 @@ class PacketPool{
return $pk;
}
}

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 291;
public const CURRENT_PROTOCOL = 313;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.7.0';
public const MINECRAFT_VERSION = 'v1.8.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.7.0';
public const MINECRAFT_VERSION_NETWORK = '1.8.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -72,7 +72,7 @@ interface ProtocolInfo{
public const UPDATE_BLOCK_PACKET = 0x15;
public const ADD_PAINTING_PACKET = 0x16;
public const EXPLODE_PACKET = 0x17;
public const LEVEL_SOUND_EVENT_PACKET = 0x18;
public const LEVEL_SOUND_EVENT_PACKET_V1 = 0x18;
public const LEVEL_EVENT_PACKET = 0x19;
public const BLOCK_EVENT_PACKET = 0x1a;
public const ENTITY_EVENT_PACKET = 0x1b;
@ -164,6 +164,12 @@ interface ProtocolInfo{
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
public const SPAWN_PARTICLE_EFFECT_PACKET = 0x76;
public const AVAILABLE_ENTITY_IDENTIFIERS_PACKET = 0x77;
public const LEVEL_SOUND_EVENT_PACKET = 0x78;
public const NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET = 0x79;
public const BIOME_DEFINITION_LIST_PACKET = 0x7a;
}

View File

@ -41,6 +41,9 @@ class ResourcePackStackPacket extends DataPacket{
/** @var ResourcePack[] */
public $resourcePackStack = [];
/** @var bool */
public $isExperimental = false;
protected function decodePayload(){
$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getUnsignedVarInt();
@ -56,6 +59,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->getString();
$this->getString();
}
$this->isExperimental = $this->getBool();
}
protected function encodePayload(){
@ -74,6 +79,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->putString($entry->getPackVersion());
$this->putString(""); //TODO: subpack name
}
$this->putBool($this->isExperimental);
}
public function handle(NetworkSession $session) : bool{

View File

@ -0,0 +1,57 @@
<?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;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
class SpawnParticleEffectPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SPAWN_PARTICLE_EFFECT_PACKET;
/** @var int */
public $dimensionId = DimensionIds::OVERWORLD; //wtf mojang
/** @var Vector3 */
public $position;
/** @var string */
public $particleName;
protected function decodePayload(){
$this->dimensionId = $this->getByte();
$this->position = $this->getVector3();
$this->particleName = $this->getString();
}
protected function encodePayload(){
$this->putByte($this->dimensionId);
$this->putVector3($this->position);
$this->putString($this->particleName);
}
public function handle(NetworkSession $session) : bool{
return $session->handleSpawnParticleEffect($this);
}
}

View File

@ -118,6 +118,10 @@ class StartGamePacket extends DataPacket{
public $isFromLockedWorldTemplate = false;
/** @var bool */
public $useMsaGamertagsOnly = false;
/** @var bool */
public $isFromWorldTemplate = false;
/** @var bool */
public $isWorldTemplateOptionLocked = false;
/** @var string */
public $levelId = ""; //base64 string, usually the same as world folder name in vanilla
@ -176,6 +180,8 @@ class StartGamePacket extends DataPacket{
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
$this->useMsaGamertagsOnly = $this->getBool();
$this->isFromWorldTemplate = $this->getBool();
$this->isWorldTemplateOptionLocked = $this->getBool();
$this->levelId = $this->getString();
$this->worldName = $this->getString();
@ -236,6 +242,8 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);
$this->putBool($this->useMsaGamertagsOnly);
$this->putBool($this->isFromWorldTemplate);
$this->putBool($this->isWorldTemplateOptionLocked);
$this->putString($this->levelId);
$this->putString($this->worldName);

View File

@ -43,6 +43,8 @@ class UpdateTradePacket extends DataPacket{
public $varint1;
/** @var int */
public $varint2;
/** @var int */
public $varint3;
/** @var bool */
public $isWilling;
/** @var int */
@ -59,6 +61,7 @@ class UpdateTradePacket extends DataPacket{
$this->windowType = $this->getByte();
$this->varint1 = $this->getVarInt();
$this->varint2 = $this->getVarInt();
$this->varint3 = $this->getVarInt();
$this->isWilling = $this->getBool();
$this->traderEid = $this->getEntityUniqueId();
$this->playerEid = $this->getEntityUniqueId();
@ -71,6 +74,7 @@ class UpdateTradePacket extends DataPacket{
$this->putByte($this->windowType);
$this->putVarInt($this->varint1);
$this->putVarInt($this->varint2);
$this->putVarInt($this->varint3);
$this->putBool($this->isWilling);
$this->putEntityUniqueId($this->traderEid);
$this->putEntityUniqueId($this->playerEid);

View File

@ -36,6 +36,7 @@ class NetworkInventoryAction{
public const SOURCE_WORLD = 2; //drop/pickup item entity
public const SOURCE_CREATIVE = 3;
public const SOURCE_CRAFTING_GRID = 100;
public const SOURCE_TODO = 99999;
/**
@ -80,7 +81,7 @@ class NetworkInventoryAction{
/** @var int */
public $sourceType;
/** @var int */
public $windowId = ContainerIds::NONE;
public $windowId;
/** @var int */
public $sourceFlags = 0;
/** @var int */
@ -107,6 +108,7 @@ class NetworkInventoryAction{
break;
case self::SOURCE_CREATIVE:
break;
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
$this->windowId = $packet->getVarInt();
switch($this->windowId){
@ -118,6 +120,8 @@ class NetworkInventoryAction{
break;
}
break;
default:
throw new \UnexpectedValueException("Unknown inventory action source type $this->sourceType");
}
$this->inventorySlot = $packet->getUnsignedVarInt();
@ -142,9 +146,12 @@ class NetworkInventoryAction{
break;
case self::SOURCE_CREATIVE:
break;
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
$packet->putVarInt($this->windowId);
break;
default:
throw new \UnexpectedValueException("Unknown inventory action source type $this->sourceType");
}
$packet->putUnsignedVarInt($this->inventorySlot);
@ -186,27 +193,17 @@ class NetworkInventoryAction{
}
return new CreativeInventoryAction($this->oldItem, $this->newItem, $type);
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
//These types need special handling.
switch($this->windowId){
case self::SOURCE_TYPE_CRAFTING_ADD_INGREDIENT:
case self::SOURCE_TYPE_CRAFTING_REMOVE_INGREDIENT:
$window = $player->getCraftingGrid();
return new SlotChangeAction($window, $this->inventorySlot, $this->oldItem, $this->newItem);
case self::SOURCE_TYPE_CONTAINER_DROP_CONTENTS: //TODO: this type applies to all fake windows, not just crafting
return new SlotChangeAction($player->getCraftingGrid(), $this->inventorySlot, $this->oldItem, $this->newItem);
case self::SOURCE_TYPE_CRAFTING_RESULT:
case self::SOURCE_TYPE_CRAFTING_USE_INGREDIENT:
return null;
case self::SOURCE_TYPE_CONTAINER_DROP_CONTENTS:
//TODO: this type applies to all fake windows, not just crafting
$window = $player->getCraftingGrid();
//DROP_CONTENTS doesn't bother telling us what slot the item is in, so we find it ourselves
$inventorySlot = $window->first($this->oldItem, true);
if($inventorySlot === -1){
throw new \InvalidStateException("Fake container " . get_class($window) . " for " . $player->getName() . " does not contain $this->oldItem");
}
return new SlotChangeAction($window, $inventorySlot, $this->oldItem, $this->newItem);
}
//TODO: more stuff

View File

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace pocketmine\resourcepacks;
use Ahc\Json\Comment as CommentedJsonDecoder;
class ZippedResourcePack implements ResourcePack{
/**
@ -86,10 +88,13 @@ class ZippedResourcePack implements ResourcePack{
$archive->close();
$manifest = json_decode($manifestData);
if($manifest === null){
throw new ResourcePackException("Failed to parse manifest.json: " . json_last_error_msg());
//maybe comments in the json, use stripped decoder (thanks mojang)
try{
$manifest = (new CommentedJsonDecoder())->decode($manifestData);
}catch(\RuntimeException $e){
throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), $e->getCode(), $e);
}
if(!self::verifyManifest($manifest)){
throw new ResourcePackException("manifest.json is missing required fields");
}

File diff suppressed because one or more lines are too long

View File

@ -70,6 +70,15 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
$this->saveItems($nbt);
}
public function getCleanedNBT() : ?CompoundTag{
$tag = parent::getCleanedNBT();
if($tag !== null){
//TODO: replace this with a purpose flag on writeSaveData()
$tag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ);
}
return $tag;
}
public function close() : void{
if(!$this->closed){
$this->inventory->removeAllViewers(true);

View File

@ -130,14 +130,9 @@ class Sign extends Spawnable{
}
if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){
$lines = array_pad(explode("\n", $nbt->getString(self::TAG_TEXT_BLOB)), 4, "");
$lines = array_slice(array_pad(explode("\n", $nbt->getString(self::TAG_TEXT_BLOB)), 4, ""), 0, 4);
}else{
$lines = [
$nbt->getString(sprintf(self::TAG_TEXT_LINE, 1)),
$nbt->getString(sprintf(self::TAG_TEXT_LINE, 2)),
$nbt->getString(sprintf(self::TAG_TEXT_LINE, 3)),
$nbt->getString(sprintf(self::TAG_TEXT_LINE, 4))
];
return false;
}
$removeFormat = $player->getRemoveFormat();

View File

@ -210,7 +210,7 @@ class MainLogger extends \AttachableThreadedLogger{
$errfile = Utils::cleanPath($errfile);
$message = get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
$stack = Utils::getTrace(0, $trace);
$stack = Utils::printableTrace($trace);
$this->synchronized(function() use ($type, $message, $stack) : void{
$this->log($type, $message);

View File

@ -313,8 +313,8 @@ class Utils{
++$processors;
}
}
}else{
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(@file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){
}elseif(is_readable("/sys/devices/system/cpu/present")){
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){
$processors = (int) ($matches[2] - $matches[1]);
}
}
@ -525,24 +525,13 @@ class Utils{
}
/**
* @param int $start
* @param array|null $trace
* @param array $trace
*
* @return array
*/
public static function getTrace($start = 0, $trace = null){
if($trace === null){
if(function_exists("xdebug_get_function_stack")){
$trace = array_reverse(xdebug_get_function_stack());
}else{
$e = new \Exception();
$trace = $e->getTrace();
}
}
public static function printableTrace(array $trace) : array{
$messages = [];
$j = 0;
for($i = (int) $start; isset($trace[$i]); ++$i, ++$j){
for($i = 0; isset($trace[$i]); ++$i){
$params = "";
if(isset($trace[$i]["args"]) or isset($trace[$i]["params"])){
if(isset($trace[$i]["args"])){
@ -555,12 +544,39 @@ class Utils{
return (is_object($value) ? get_class($value) . " object" : gettype($value) . " " . (is_array($value) ? "Array()" : Utils::printable(@strval($value))));
}, $args));
}
$messages[] = "#$j " . (isset($trace[$i]["file"]) ? self::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")";
$messages[] = "#$i " . (isset($trace[$i]["file"]) ? self::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")";
}
return $messages;
}
/**
* @param int $skipFrames
*
* @return array
*/
public static function currentTrace(int $skipFrames = 0) : array{
++$skipFrames; //omit this frame from trace, in addition to other skipped frames
if(function_exists("xdebug_get_function_stack")){
$trace = array_reverse(xdebug_get_function_stack());
}else{
$e = new \Exception();
$trace = $e->getTrace();
}
for($i = 0; $i < $skipFrames; ++$i){
unset($trace[$i]);
}
return array_values($trace);
}
/**
* @param int $skipFrames
*
* @return array
*/
public static function printableCurrentTrace(int $skipFrames = 0) : array{
return self::printableTrace(self::currentTrace(++$skipFrames));
}
public static function cleanPath($path){
return str_replace(["\\", ".php", "phar://", str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PATH), str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PLUGIN_PATH)], ["/", "", "", "", ""], $path);
}

View File

@ -0,0 +1,52 @@
<?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\level\format\io\region;
use PHPUnit\Framework\TestCase;
use pocketmine\level\format\ChunkException;
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();
$this->expectException(ChunkException::class);
$r->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();
$r->writeChunk(0, 0, $data);
$r->close();
$r = new RegionLoader($path, 0, 0);
$r->open();
self::assertSame($data, $r->readChunk(0, 0));
}
}