mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 11:16:57 +00:00
Compare commits
218 Commits
Author | SHA1 | Date | |
---|---|---|---|
fc0782df02 | |||
bfaa224f6b | |||
de88f0fce1 | |||
9b078854c4 | |||
42f8e061a5 | |||
1455c38dbe | |||
75df6973df | |||
4763360e9e | |||
0299191e64 | |||
2664a1b4d8 | |||
4249c00c3e | |||
517c4e5143 | |||
69c343bb9b | |||
70df1579a8 | |||
ea9f9aa250 | |||
34a899e28b | |||
b80868040e | |||
a7f1181335 | |||
bf8a8b386e | |||
4b518f2a58 | |||
60b1f0a6e9 | |||
5934399a0d | |||
b42132a7c3 | |||
c05697f506 | |||
d4fe1b8ece | |||
9b2653fb6f | |||
ed88684e71 | |||
cbb9c4f298 | |||
660d42e8d1 | |||
dbeceb02f9 | |||
fd77dd0066 | |||
87ce87112b | |||
1d71f5edb3 | |||
0f620157e8 | |||
2323601f98 | |||
d34b94302f | |||
ec4c61e113 | |||
231e491bb9 | |||
69cdc6f13a | |||
dfeb62491a | |||
178eedb536 | |||
4975da2aae | |||
5946ec8819 | |||
abf0dee426 | |||
30f5a8fac6 | |||
f704061618 | |||
23dc6e09d8 | |||
15b7fc978e | |||
bb396174ba | |||
dcef3cba21 | |||
5f8a9f8747 | |||
84e41e6967 | |||
5e0e0daf7d | |||
a95694ed06 | |||
762405d16a | |||
e3f46987f5 | |||
e4223bb7dc | |||
f091446ec7 | |||
b0f891081c | |||
acd7c9b336 | |||
75482124f2 | |||
288599cbe7 | |||
aa7206126a | |||
1a6db1c7ce | |||
f1c071ce7f | |||
e2f46a4358 | |||
36c0c350a7 | |||
4c08a05fae | |||
6295ef8a81 | |||
05dba61a69 | |||
b473ffdedc | |||
60dddcd12a | |||
c010ef45ed | |||
93c26a0b0c | |||
08ec021f78 | |||
545ec9c881 | |||
b0060caaf7 | |||
c90d1faa81 | |||
d5a1961e6b | |||
449dda83fb | |||
6bc79149c3 | |||
cdf7e28251 | |||
a02f422d85 | |||
f8bfbc107d | |||
554c029fbd | |||
e018311e73 | |||
de50f02076 | |||
71d02e5870 | |||
46d9475568 | |||
788b278fc3 | |||
2e4143f57e | |||
d312aef1ac | |||
200de3fe84 | |||
f560a6efea | |||
7ecd7fd13f | |||
5284ad0346 | |||
b893645a81 | |||
b19b3134ad | |||
7cf36f460b | |||
243f86b0a0 | |||
9156cbc269 | |||
a5f776af2f | |||
43fe6a1934 | |||
342a74ffcb | |||
3d2701e775 | |||
2183bf875c | |||
8cc2a4ce5d | |||
e26af3fa1b | |||
1634dd62e3 | |||
755db3dac8 | |||
3dabf90b0e | |||
f61e14e341 | |||
7b24fbc8db | |||
0543c17849 | |||
c4f3426bae | |||
046c39b02e | |||
87b471ce0f | |||
055ba6aa7c | |||
5c3eed40b3 | |||
3e5237b6e0 | |||
af1227f154 | |||
d9a867016c | |||
a50a863ab7 | |||
9caf62778c | |||
d257d36e55 | |||
1b03168b88 | |||
6b9fee05d6 | |||
44d8a5528e | |||
45a18ffe1e | |||
f0182c9996 | |||
265b61b3e6 | |||
2d88058710 | |||
ab48d85c35 | |||
cf43f479df | |||
c143834632 | |||
d9b7a28747 | |||
31ceafa111 | |||
a0eb6e23e5 | |||
694d7d4e20 | |||
2da2fdd6d4 | |||
0aa30295af | |||
c1c56f29bb | |||
9b820a0849 | |||
c6a4bc4bf7 | |||
3128449033 | |||
a714612453 | |||
f61e099828 | |||
447b9562bb | |||
cac21c2caf | |||
6dd2597934 | |||
b35759cc25 | |||
2a40c0d82c | |||
8ac1b18b17 | |||
4aef9919dc | |||
43426a4c5c | |||
3028832cd3 | |||
9f8a2dc61a | |||
d9ebe6f321 | |||
cb1eb1ee09 | |||
d563b9e31b | |||
7c44eea625 | |||
646c8970b8 | |||
f1cd6940f9 | |||
4221e274d6 | |||
cd506bb443 | |||
4c8ffce86f | |||
df6bb2ea0e | |||
ba68192206 | |||
6579930638 | |||
a0ab996b9f | |||
b261129788 | |||
4f2f373a24 | |||
de6d62aba2 | |||
6f694b0801 | |||
a3552875cb | |||
ab5aec6c30 | |||
0e508876d2 | |||
50b89c30f8 | |||
495fdbd19f | |||
620784e4e7 | |||
1dd6591ac1 | |||
6efef3bbc7 | |||
b1e0f82cbf | |||
c065cfbeda | |||
0171095036 | |||
9d8898a4ed | |||
3bb22f9778 | |||
16c636df83 | |||
e597067a92 | |||
06f00020cd | |||
5eeaeb6c3e | |||
2712287995 | |||
6be5e75263 | |||
6b44f99dfb | |||
fa9ea6a7d7 | |||
5e94d20d79 | |||
ad9df6764d | |||
6309a242dc | |||
e58d015f14 | |||
0d65f9c4b8 | |||
22077c1fdd | |||
f33c19e77a | |||
09dea035d4 | |||
a9fc67663c | |||
519659fd2b | |||
5134c0cf5a | |||
0aa63d269a | |||
191f0038b8 | |||
99d6aa92cb | |||
90d01f5ed2 | |||
b70905b287 | |||
7a48c0b23d | |||
557fd34754 | |||
32077d96b4 | |||
e621cde8f1 | |||
2738e38aee | |||
72d447276b | |||
d0aff2ecbd |
@ -29,7 +29,8 @@
|
||||
"pocketmine/binaryutils": "^0.1.0",
|
||||
"pocketmine/nbt": "^0.2.1",
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/snooze": "^0.1.0"
|
||||
"pocketmine/snooze": "^0.1.0",
|
||||
"daverandom/callback-validator": "dev-master"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
74
composer.lock
generated
74
composer.lock
generated
@ -4,20 +4,60 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3536995c56bfc3dbd6ccc0994e88a636",
|
||||
"content-hash": "2d120a3dd7d68958809c3662d1cb7c99",
|
||||
"packages": [
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
"version": "0.1.1",
|
||||
"name": "daverandom/callback-validator",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BinaryUtils.git",
|
||||
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be"
|
||||
"url": "https://github.com/DaveRandom/CallbackValidator.git",
|
||||
"reference": "d87a08cddbc6099816ed01e50ce25cdfc43b542f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/54efeb978be0ff9335022729fe63c1e2077bf1be",
|
||||
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be",
|
||||
"url": "https://api.github.com/repos/DaveRandom/CallbackValidator/zipball/d87a08cddbc6099816ed01e50ce25cdfc43b542f",
|
||||
"reference": "d87a08cddbc6099816ed01e50ce25cdfc43b542f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-reflection": "*",
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DaveRandom\\CallbackValidator\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wright",
|
||||
"email": "cw@daverandom.com"
|
||||
}
|
||||
],
|
||||
"description": "Tools for validating callback signatures",
|
||||
"time": "2017-04-03T15:22:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
"version": "0.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BinaryUtils.git",
|
||||
"reference": "2c1628f08a9cdeca4d3c682e13f24420944ca845"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/2c1628f08a9cdeca4d3c682e13f24420944ca845",
|
||||
"reference": "2c1628f08a9cdeca4d3c682e13f24420944ca845",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -35,10 +75,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.2",
|
||||
"issues": "https://github.com/pmmp/BinaryUtils/issues"
|
||||
},
|
||||
"time": "2018-08-26T18:11:05+00:00"
|
||||
"time": "2018-12-22T12:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/math",
|
||||
@ -76,19 +116,20 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b"
|
||||
"reference": "291bf5cc2a94500eada1edbda51d15bed25a1e1c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/474f0cf0a47656d0122b4f3f71302e694ed6977b",
|
||||
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/291bf5cc2a94500eada1edbda51d15bed25a1e1c",
|
||||
"reference": "291bf5cc2a94500eada1edbda51d15bed25a1e1c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-zlib": "*",
|
||||
"php": ">=7.2.0",
|
||||
"php-64bit": "*",
|
||||
"pocketmine/binaryutils": "^0.1.0"
|
||||
@ -109,10 +150,10 @@
|
||||
],
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.2",
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.3",
|
||||
"issues": "https://github.com/pmmp/NBT/issues"
|
||||
},
|
||||
"time": "2018-10-12T08:26:44+00:00"
|
||||
"time": "2018-12-03T16:08:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
@ -226,7 +267,8 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"ext-pthreads": 20
|
||||
"ext-pthreads": 20,
|
||||
"daverandom/callback-validator": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
@ -33,6 +33,14 @@ use raklib\RakLib;
|
||||
|
||||
class CrashDump{
|
||||
|
||||
/**
|
||||
* Crashdump data format version, used by the crash archive to decide how to decode the crashdump
|
||||
* This should be incremented when backwards incompatible changes are introduced, such as fields being removed or
|
||||
* having their content changed, version format changing, etc.
|
||||
* It is not necessary to increase this when adding new fields.
|
||||
*/
|
||||
private const FORMAT_VERSION = 1;
|
||||
|
||||
/** @var Server */
|
||||
private $server;
|
||||
private $fp;
|
||||
@ -54,6 +62,7 @@ class CrashDump{
|
||||
if(!is_resource($this->fp)){
|
||||
throw new \RuntimeException("Could not create Crash Dump");
|
||||
}
|
||||
$this->data["format_version"] = self::FORMAT_VERSION;
|
||||
$this->data["time"] = $this->time;
|
||||
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", $this->time));
|
||||
$this->addLine();
|
||||
@ -150,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",
|
||||
@ -232,10 +241,10 @@ class CrashDump{
|
||||
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
|
||||
$this->data["general"] = [];
|
||||
$this->data["general"]["name"] = $this->server->getName();
|
||||
$this->data["general"]["version"] = $version->getFullVersion(false);
|
||||
$this->data["general"]["build"] = $version->getBuild();
|
||||
$this->data["general"]["base_version"] = \pocketmine\BASE_VERSION;
|
||||
$this->data["general"]["build"] = \pocketmine\BUILD_NUMBER;
|
||||
$this->data["general"]["is_dev"] = \pocketmine\IS_DEVELOPMENT_BUILD;
|
||||
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
|
||||
$this->data["general"]["api"] = \pocketmine\BASE_VERSION;
|
||||
$this->data["general"]["git"] = \pocketmine\GIT_COMMIT;
|
||||
$this->data["general"]["raklib"] = RakLib::VERSION;
|
||||
$this->data["general"]["uname"] = php_uname("a");
|
||||
|
@ -185,7 +185,7 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
$cycles = 0;
|
||||
if($this->garbageCollectionTrigger){
|
||||
|
@ -35,7 +35,7 @@ class OfflinePlayer implements IPlayer, Metadatable{
|
||||
/** @var Server */
|
||||
private $server;
|
||||
/** @var CompoundTag|null */
|
||||
private $namedtag;
|
||||
private $namedtag = null;
|
||||
|
||||
/**
|
||||
* @param Server $server
|
||||
@ -44,10 +44,8 @@ class OfflinePlayer implements IPlayer, Metadatable{
|
||||
public function __construct(Server $server, string $name){
|
||||
$this->server = $server;
|
||||
$this->name = $name;
|
||||
if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->name) . ".dat")){
|
||||
if($this->server->hasOfflinePlayerData($this->name)){
|
||||
$this->namedtag = $this->server->getOfflinePlayerData($this->name);
|
||||
}else{
|
||||
$this->namedtag = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -285,14 +287,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
/** @var Vector3|null */
|
||||
protected $newPosition;
|
||||
/** @var Vector3|null */
|
||||
public $speed = null;
|
||||
/** @var bool */
|
||||
protected $isTeleporting = false;
|
||||
/** @var int */
|
||||
protected $inAirTicks = 0;
|
||||
/** @var int */
|
||||
protected $startAirTicks = 5;
|
||||
/** @var float */
|
||||
protected $stepHeight = 0.6;
|
||||
/** @var bool */
|
||||
@ -547,9 +545,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function resetFallDistance() : void{
|
||||
parent::resetFallDistance();
|
||||
if($this->inAirTicks !== 0){
|
||||
$this->startAirTicks = 5;
|
||||
}
|
||||
$this->inAirTicks = 0;
|
||||
}
|
||||
|
||||
@ -801,7 +796,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$ev = new PlayerChangeSkinEvent($this, $this->getSkin(), $skin);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSkin([$this]);
|
||||
@ -969,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){
|
||||
@ -981,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1020,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);
|
||||
}
|
||||
@ -1032,11 +1027,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this,
|
||||
$ev = new PlayerJoinEvent($this,
|
||||
new TranslationContainer(TextFormat::YELLOW . "%multiplayer.player.joined", [
|
||||
$this->getDisplayName()
|
||||
])
|
||||
));
|
||||
);
|
||||
$ev->call();
|
||||
if(strlen(trim((string) $ev->getJoinMessage())) > 0){
|
||||
$this->server->broadcastMessage($ev->getJoinMessage());
|
||||
}
|
||||
@ -1155,6 +1151,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();
|
||||
}
|
||||
@ -1221,7 +1225,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$pos = $pos->floor();
|
||||
$b = $this->level->getBlock($pos);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerBedEnterEvent($this, $b));
|
||||
$ev = new PlayerBedEnterEvent($this, $b);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1248,7 +1253,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($b instanceof Bed){
|
||||
$b->setOccupied(false);
|
||||
}
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerBedLeaveEvent($this, $b));
|
||||
(new PlayerBedLeaveEvent($this, $b))->call();
|
||||
|
||||
$this->sleeping = null;
|
||||
$this->propertyManager->setBlockPos(self::DATA_PLAYER_BED_POSITION, null);
|
||||
@ -1288,7 +1293,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerAchievementAwardedEvent($this, $achievementId));
|
||||
$ev = new PlayerAchievementAwardedEvent($this, $achievementId);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->achievements[$achievementId] = true;
|
||||
Achievement::broadcast($this, $achievementId);
|
||||
@ -1352,7 +1358,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerGameModeChangeEvent($this, $gm));
|
||||
$ev = new PlayerGameModeChangeEvent($this, $gm);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
if($client){ //gamemode change by client in the GUI
|
||||
$this->sendGamemode();
|
||||
@ -1546,14 +1553,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->server->getLogger()->warning($this->getName() . " moved too fast, reverting movement");
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $this->newPosition);
|
||||
$revert = true;
|
||||
}else{
|
||||
$chunkX = $newPos->getFloorX() >> 4;
|
||||
$chunkZ = $newPos->getFloorZ() >> 4;
|
||||
|
||||
if(!$this->level->isChunkLoaded($chunkX, $chunkZ) or !$this->level->isChunkGenerated($chunkX, $chunkZ)){
|
||||
$revert = true;
|
||||
$this->nextChunkOrderRun = 0;
|
||||
}
|
||||
}elseif(!$this->level->isInLoadedTerrain($newPos) or !$this->level->isChunkGenerated($newPos->getFloorX() >> 4, $newPos->getFloorZ() >> 4)){
|
||||
$revert = true;
|
||||
$this->nextChunkOrderRun = 0;
|
||||
}
|
||||
|
||||
if(!$revert and $distanceSquared != 0){
|
||||
@ -1569,7 +1571,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev = new PlayerIllegalMoveEvent($this, $newPos, new Vector3($this->lastX, $this->lastY, $this->lastZ));
|
||||
$ev->setCancelled($this->allowMovementCheats);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
$revert = true;
|
||||
@ -1599,7 +1601,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$ev = new PlayerMoveEvent($this, $from, $to);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if(!($revert = $ev->isCancelled())){ //Yes, this is intended
|
||||
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
|
||||
@ -1616,10 +1618,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->speed = $to->subtract($from)->divide($tickDiff);
|
||||
}elseif($distanceSquared == 0){
|
||||
$this->speed = new Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
if($revert){
|
||||
@ -1643,7 +1641,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function jump() : void{
|
||||
$this->server->getPluginManager()->callEvent(new PlayerJumpEvent($this));
|
||||
(new PlayerJumpEvent($this))->call();
|
||||
parent::jump();
|
||||
}
|
||||
|
||||
@ -1651,10 +1649,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if(parent::setMotion($motion)){
|
||||
$this->broadcastMotion();
|
||||
|
||||
if($this->motion->y > 0){
|
||||
$this->startAirTicks = (-log($this->gravity / ($this->gravity + $this->drag * $this->motion->y)) / $this->drag) * 2 + 5;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1708,6 +1702,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($this->spawned){
|
||||
$this->processMovement($tickDiff);
|
||||
$this->motion->x = $this->motion->y = $this->motion->z = 0; //TODO: HACK! (Fixes player knockback being messed up)
|
||||
if($this->onGround){
|
||||
$this->inAirTicks = 0;
|
||||
}else{
|
||||
$this->inAirTicks += $tickDiff;
|
||||
}
|
||||
|
||||
Timings::$timerEntityBaseTick->startTiming();
|
||||
$this->entityBaseTick($tickDiff);
|
||||
@ -1717,32 +1716,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
Timings::$playerCheckNearEntitiesTimer->startTiming();
|
||||
$this->checkNearEntities();
|
||||
Timings::$playerCheckNearEntitiesTimer->stopTiming();
|
||||
|
||||
if($this->speed !== null){
|
||||
if($this->onGround){
|
||||
if($this->inAirTicks !== 0){
|
||||
$this->startAirTicks = 5;
|
||||
}
|
||||
$this->inAirTicks = 0;
|
||||
}else{
|
||||
if(!$this->allowFlight and $this->inAirTicks > 10 and !$this->isSleeping() and !$this->isImmobile()){
|
||||
$expectedVelocity = (-$this->gravity) / $this->drag - ((-$this->gravity) / $this->drag) * exp(-$this->drag * ($this->inAirTicks - $this->startAirTicks));
|
||||
$diff = ($this->speed->y - $expectedVelocity) ** 2;
|
||||
|
||||
if(!$this->hasEffect(Effect::JUMP) and !$this->hasEffect(Effect::LEVITATION) and $diff > 0.6 and $expectedVelocity < $this->speed->y and !$this->server->getAllowFlight()){
|
||||
if($this->inAirTicks < 100){
|
||||
$this->setMotion(new Vector3(0, $expectedVelocity, 0));
|
||||
}elseif($this->kick($this->server->getLanguage()->translateString("kick.reason.cheat", ["%ability.flight"]))){
|
||||
$this->timings->stopTiming();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->inAirTicks += $tickDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1900,7 +1873,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$this->setSkin($skin);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason"));
|
||||
$ev = new PlayerPreLoginEvent($this, "Plugin reason");
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->close("", $ev->getKickMessage());
|
||||
|
||||
@ -2079,7 +2053,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->level->registerChunkLoader($this, ((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4, true);
|
||||
|
||||
parent::__construct($this->level, $this->namedtag);
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason"));
|
||||
$ev = new PlayerLoginEvent($this, "Plugin reason");
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->close($this->getLeaveMessage(), $ev->getKickMessage());
|
||||
|
||||
@ -2122,6 +2097,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);
|
||||
@ -2181,8 +2158,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$ev = new PlayerCommandPreprocessEvent($this, $messagePart);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
@ -2193,7 +2169,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1));
|
||||
Timings::$playerCommandTimer->stopTiming();
|
||||
}else{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage()));
|
||||
$ev = new PlayerChatEvent($this, $ev->getMessage());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->server->broadcastMessage($this->getServer()->getLanguage()->translateString($ev->getFormat(), [$ev->getPlayer()->getDisplayName(), $ev->getMessage()]), $ev->getRecipients());
|
||||
}
|
||||
@ -2236,9 +2213,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
|
||||
//TODO: add events so plugins can change this
|
||||
if($this->chunk !== null){
|
||||
$this->getLevel()->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $packet);
|
||||
}
|
||||
$this->getLevel()->broadcastPacketToViewers($this, $packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2450,8 +2425,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->inventory->sendHeldItem($this);
|
||||
return true;
|
||||
@ -2596,7 +2570,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($this->hasItemCooldown($slot)){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled() or !$this->consumeObject($slot)){
|
||||
$this->inventory->sendContents($this);
|
||||
@ -2657,6 +2631,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();
|
||||
|
||||
@ -2699,7 +2680,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->inventory->setItemInHand($ev->getResultItem());
|
||||
}
|
||||
@ -2729,7 +2710,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->getServer()->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->inventory->sendHeldItem($this);
|
||||
break;
|
||||
@ -2808,7 +2789,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function toggleSprint(bool $sprint) : void{
|
||||
$ev = new PlayerToggleSprintEvent($this, $sprint);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->sendData($this);
|
||||
}else{
|
||||
@ -2818,7 +2799,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function toggleSneak(bool $sneak) : void{
|
||||
$ev = new PlayerToggleSneakEvent($this, $sneak);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->sendData($this);
|
||||
}else{
|
||||
@ -2831,7 +2812,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerAnimationEvent($this, $packet->action));
|
||||
$ev = new PlayerAnimationEvent($this, $packet->action);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return true;
|
||||
}
|
||||
@ -2876,7 +2858,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->doCloseInventory();
|
||||
|
||||
if(isset($this->windowIndex[$packet->windowId])){
|
||||
$this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this));
|
||||
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
|
||||
$this->removeWindow($this->windowIndex[$packet->windowId]);
|
||||
return true;
|
||||
}elseif($packet->windowId === 255){
|
||||
@ -2895,11 +2877,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$handled = false;
|
||||
|
||||
$isFlying = $packet->getFlag(AdventureSettingsPacket::FLYING);
|
||||
if($isFlying and !$this->allowFlight and !$this->server->getAllowFlight()){
|
||||
if($isFlying and !$this->allowFlight){
|
||||
$this->kick($this->server->getLanguage()->translateString("kick.reason.cheat", ["%ability.flight"]));
|
||||
return true;
|
||||
}elseif($isFlying !== $this->isFlying()){
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerToggleFlightEvent($this, $isFlying));
|
||||
$ev = new PlayerToggleFlightEvent($this, $isFlying);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSettings();
|
||||
}else{
|
||||
@ -2967,7 +2950,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$tile->spawnTo($this);
|
||||
return true;
|
||||
@ -3040,7 +3023,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->getServer()->getPluginManager()->callEvent($event = new PlayerEditBookEvent($this, $oldBook, $newBook, $packet->type, $modifiedPages));
|
||||
$event = new PlayerEditBookEvent($this, $oldBook, $newBook, $packet->type, $modifiedPages);
|
||||
$event->call();
|
||||
if($event->isCancelled()){
|
||||
return true;
|
||||
}
|
||||
@ -3075,7 +3059,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$timings = Timings::getSendDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
$this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
|
||||
$ev = new DataPacketSendEvent($this, $packet);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$timings->stopTiming();
|
||||
return false;
|
||||
@ -3106,7 +3091,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$timings = Timings::getSendDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
try{
|
||||
$this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
|
||||
$ev = new DataPacketSendEvent($this, $packet);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -3154,8 +3140,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @return bool if transfer was successful.
|
||||
*/
|
||||
public function transfer(string $address, int $port = 19132, string $message = "transfer") : bool{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerTransferEvent($this, $address, $port, $message));
|
||||
|
||||
$ev = new PlayerTransferEvent($this, $address, $port, $message);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$pk = new TransferPacket();
|
||||
$pk->address = $ev->getAddress();
|
||||
@ -3179,7 +3165,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @return bool
|
||||
*/
|
||||
public function kick(string $reason = "", bool $isAdmin = true, $quitMessage = null) : bool{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage()));
|
||||
$ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$reason = $ev->getReason();
|
||||
$message = $reason;
|
||||
@ -3427,7 +3414,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->stopSleep();
|
||||
|
||||
if($this->spawned){
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerQuitEvent($this, $message, $reason));
|
||||
$ev = new PlayerQuitEvent($this, $message, $reason);
|
||||
$ev->call();
|
||||
if($ev->getQuitMessage() != ""){
|
||||
$this->server->broadcastMessage($ev->getQuitMessage());
|
||||
}
|
||||
@ -3515,11 +3503,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/**
|
||||
* Handles player data saving
|
||||
*
|
||||
* @param bool $async
|
||||
*
|
||||
* @throws \InvalidStateException if the player is closed
|
||||
*/
|
||||
public function save(bool $async = false){
|
||||
public function save(){
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Tried to save closed player");
|
||||
}
|
||||
@ -3556,7 +3542,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->namedtag->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
|
||||
|
||||
if($this->username != "" and $this->namedtag instanceof CompoundTag){
|
||||
$this->server->saveOfflinePlayerData($this->username, $this->namedtag, $async);
|
||||
$this->server->saveOfflinePlayerData($this->username, $this->namedtag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3575,7 +3561,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
//main inventory and drops the rest on the ground.
|
||||
$this->doCloseInventory();
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops()));
|
||||
$ev = new PlayerDeathEvent($this, $this->getDrops());
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->getKeepInventory()){
|
||||
foreach($ev->getDrops() as $item){
|
||||
@ -3610,7 +3597,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn()));
|
||||
$ev = new PlayerRespawnEvent($this, $this->getSpawn());
|
||||
$ev->call();
|
||||
|
||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getLevel());
|
||||
$this->teleport($realSpawn);
|
||||
@ -3808,6 +3796,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @param bool $isPermanent Prevents the window being removed if true.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \InvalidArgumentException if a forceID which is already in use is specified
|
||||
* @throws \InvalidStateException if trying to add a window without forceID when no slots are free
|
||||
*/
|
||||
public function addWindow(Inventory $inventory, int $forceId = null, bool $isPermanent = false) : int{
|
||||
if(($id = $this->getWindowId($inventory)) !== ContainerIds::NONE){
|
||||
@ -3815,10 +3806,21 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
if($forceId === null){
|
||||
$this->windowCnt = $cnt = max(ContainerIds::FIRST, ++$this->windowCnt % ContainerIds::LAST);
|
||||
$cnt = $this->windowCnt;
|
||||
do{
|
||||
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % ContainerIds::LAST);
|
||||
if($cnt === $this->windowCnt){ //wraparound, no free slots
|
||||
throw new \InvalidStateException("No free window IDs found");
|
||||
}
|
||||
}while(isset($this->windowIndex[$cnt]));
|
||||
$this->windowCnt = $cnt;
|
||||
}else{
|
||||
$cnt = $forceId;
|
||||
if(isset($this->windowIndex[$cnt])){
|
||||
throw new \InvalidArgumentException("Requested force ID $forceId already in use");
|
||||
}
|
||||
}
|
||||
|
||||
$this->windowIndex[$cnt] = $inventory;
|
||||
$this->windows[spl_object_hash($inventory)] = $cnt;
|
||||
if($inventory->open($this)){
|
||||
|
@ -37,7 +37,7 @@ namespace pocketmine {
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.3.1";
|
||||
const BASE_VERSION = "3.5.2";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
||||
|
@ -91,7 +91,6 @@ use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\plugin\ScriptPluginLoader;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\scheduler\FileWriteTask;
|
||||
use pocketmine\scheduler\SendUsageTask;
|
||||
use pocketmine\snooze\SleeperHandler;
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
@ -198,9 +197,6 @@ class Server{
|
||||
/** @var ResourcePackManager */
|
||||
private $resourceManager;
|
||||
|
||||
/** @var ConsoleCommandSender */
|
||||
private $consoleSender;
|
||||
|
||||
/** @var int */
|
||||
private $maxPlayers;
|
||||
|
||||
@ -556,10 +552,11 @@ class Server{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return bool
|
||||
*/
|
||||
public function getAllowFlight() : bool{
|
||||
return $this->getConfigBool("allow-flight", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -734,6 +731,17 @@ class Server{
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the server has stored any saved data for this player.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOfflinePlayerData(string $name) : bool{
|
||||
return file_exists($this->getDataPath() . "players/$name.dat");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
@ -805,22 +813,17 @@ class Server{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param CompoundTag $nbtTag
|
||||
* @param bool $async
|
||||
*/
|
||||
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag, bool $async = false){
|
||||
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag){
|
||||
$ev = new PlayerDataSaveEvent($nbtTag, $name);
|
||||
$ev->setCancelled(!$this->shouldSavePlayerData());
|
||||
|
||||
$this->pluginManager->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
$nbt = new BigEndianNBTStream();
|
||||
try{
|
||||
if($async){
|
||||
$this->asyncPool->submitTask(new FileWriteTask($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData())));
|
||||
}else{
|
||||
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
|
||||
}
|
||||
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
|
||||
$this->logger->logException($e);
|
||||
@ -1034,7 +1037,7 @@ class Server{
|
||||
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
$this->getPluginManager()->callEvent(new LevelLoadEvent($level));
|
||||
(new LevelLoadEvent($level))->call();
|
||||
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
|
||||
@ -1083,9 +1086,9 @@ class Server{
|
||||
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
|
||||
$this->getPluginManager()->callEvent(new LevelInitEvent($level));
|
||||
(new LevelInitEvent($level))->call();
|
||||
|
||||
$this->getPluginManager()->callEvent(new LevelLoadEvent($level));
|
||||
(new LevelLoadEvent($level))->call();
|
||||
|
||||
$this->getLogger()->notice($this->getLanguage()->translateString("pocketmine.level.backgroundGeneration", [$name]));
|
||||
|
||||
@ -1438,31 +1441,6 @@ class Server{
|
||||
}
|
||||
$this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []);
|
||||
|
||||
define('pocketmine\DEBUG', (int) $this->getProperty("debug.level", 1));
|
||||
|
||||
$this->forceLanguage = (bool) $this->getProperty("settings.force-language", false);
|
||||
$this->baseLang = new BaseLang($this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE));
|
||||
$this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()]));
|
||||
|
||||
if(\pocketmine\IS_DEVELOPMENT_BUILD and !((bool) $this->getProperty("settings.enable-dev-builds", false))){
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error1", [\pocketmine\NAME]));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
|
||||
$this->forceShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
if(((int) ini_get('zend.assertions')) > 0 and ((bool) $this->getProperty("debug.assertions.warn-if-enabled", true)) !== false){
|
||||
$this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini.");
|
||||
}
|
||||
|
||||
ini_set('assert.exception', '1');
|
||||
|
||||
if($this->logger instanceof MainLogger){
|
||||
$this->logger->setLogDebug(\pocketmine\DEBUG > 1);
|
||||
}
|
||||
|
||||
$this->logger->info("Loading server properties...");
|
||||
$this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [
|
||||
"motd" => \pocketmine\NAME . " Server",
|
||||
@ -1471,7 +1449,6 @@ class Server{
|
||||
"announce-player-achievements" => true,
|
||||
"spawn-protection" => 16,
|
||||
"max-players" => 20,
|
||||
"allow-flight" => false,
|
||||
"spawn-animals" => true,
|
||||
"spawn-mobs" => true,
|
||||
"gamemode" => 0,
|
||||
@ -1488,9 +1465,36 @@ class Server{
|
||||
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
|
||||
"auto-save" => true,
|
||||
"view-distance" => 8,
|
||||
"xbox-auth" => true
|
||||
"xbox-auth" => true,
|
||||
"language" => "eng"
|
||||
]);
|
||||
|
||||
define('pocketmine\DEBUG', (int) $this->getProperty("debug.level", 1));
|
||||
|
||||
$this->forceLanguage = (bool) $this->getProperty("settings.force-language", false);
|
||||
$this->baseLang = new BaseLang($this->getConfigString("language", $this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE)));
|
||||
$this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()]));
|
||||
|
||||
if(\pocketmine\IS_DEVELOPMENT_BUILD and !((bool) $this->getProperty("settings.enable-dev-builds", false))){
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error1", [\pocketmine\NAME]));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
|
||||
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error5", ["https://github.com/pmmp/PocketMine-MP/releases"]));
|
||||
$this->forceShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
if(((int) ini_get('zend.assertions')) > 0 and ((bool) $this->getProperty("debug.assertions.warn-if-enabled", true)) !== false){
|
||||
$this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini.");
|
||||
}
|
||||
|
||||
ini_set('assert.exception', '1');
|
||||
|
||||
if($this->logger instanceof MainLogger){
|
||||
$this->logger->setLogDebug(\pocketmine\DEBUG > 1);
|
||||
}
|
||||
|
||||
$this->memoryManager = new MemoryManager($this);
|
||||
|
||||
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.start", [TextFormat::AQUA . $this->getVersion() . TextFormat::RESET]));
|
||||
@ -1528,10 +1532,22 @@ class Server{
|
||||
|
||||
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
|
||||
|
||||
|
||||
$consoleSender = new ConsoleCommandSender();
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender);
|
||||
|
||||
$consoleNotifier = new SleeperNotifier();
|
||||
$this->console = new CommandReader($consoleNotifier);
|
||||
$this->tickSleeper->addNotifier($consoleNotifier, function() : void{
|
||||
$this->checkConsole();
|
||||
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($consoleSender) : void{
|
||||
Timings::$serverCommandTimer->startTiming();
|
||||
while(($line = $this->console->getLine()) !== null){
|
||||
$ev = new ServerCommandEvent($consoleSender, $line);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->dispatchCommand($ev->getSender(), $ev->getCommand());
|
||||
}
|
||||
}
|
||||
Timings::$serverCommandTimer->stopTiming();
|
||||
});
|
||||
$this->console->start(PTHREADS_INHERIT_NONE);
|
||||
|
||||
@ -1607,7 +1623,6 @@ class Server{
|
||||
Timings::init();
|
||||
TimingsHandler::setEnabled((bool) $this->getProperty("settings.enable-profiling", false));
|
||||
|
||||
$this->consoleSender = new ConsoleCommandSender();
|
||||
$this->commandMap = new SimpleCommandMap($this);
|
||||
|
||||
Entity::init();
|
||||
@ -1624,7 +1639,6 @@ class Server{
|
||||
$this->resourceManager = new ResourcePackManager($this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR, $this->logger);
|
||||
|
||||
$this->pluginManager = new PluginManager($this, $this->commandMap, ((bool) $this->getProperty("plugins.legacy-data-dir", true)) ? null : $this->getDataPath() . "plugin_data" . DIRECTORY_SEPARATOR);
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
|
||||
$this->profilingTickRate = (float) $this->getProperty("settings.profile-report-trigger", 20);
|
||||
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
|
||||
$this->pluginManager->registerInterface(new ScriptPluginLoader());
|
||||
@ -1923,17 +1937,6 @@ class Server{
|
||||
$this->pluginManager->disablePlugins();
|
||||
}
|
||||
|
||||
public function checkConsole(){
|
||||
Timings::$serverCommandTimer->startTiming();
|
||||
while(($line = $this->console->getLine()) !== null){
|
||||
$this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line));
|
||||
if(!$ev->isCancelled()){
|
||||
$this->dispatchCommand($ev->getSender(), $ev->getCommand());
|
||||
}
|
||||
}
|
||||
Timings::$serverCommandTimer->stopTiming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command from a CommandSender
|
||||
*
|
||||
@ -1945,7 +1948,8 @@ class Server{
|
||||
*/
|
||||
public function dispatchCommand(CommandSender $sender, string $commandLine, bool $internal = false) : bool{
|
||||
if(!$internal){
|
||||
$this->pluginManager->callEvent($ev = new CommandEvent($sender, $commandLine));
|
||||
$ev = new CommandEvent($sender, $commandLine);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -2169,7 +2173,7 @@ class Server{
|
||||
"fullFile" => $e->getFile(),
|
||||
"file" => $errfile,
|
||||
"line" => $errline,
|
||||
"trace" => Utils::getTrace(0, $trace)
|
||||
"trace" => Utils::printableTrace($trace)
|
||||
];
|
||||
|
||||
global $lastExceptionError, $lastError;
|
||||
@ -2246,6 +2250,12 @@ class Server{
|
||||
|
||||
$this->forceShutdown();
|
||||
$this->isRunning = false;
|
||||
|
||||
//Force minimum uptime to be >= 120 seconds, to reduce the impact of spammy crash loops
|
||||
$spacing = ((int) \pocketmine\START_TIME) - time() + 120;
|
||||
if($spacing > 0){
|
||||
sleep($spacing);
|
||||
}
|
||||
@Utils::kill(getmypid());
|
||||
exit(1);
|
||||
}
|
||||
@ -2397,7 +2407,7 @@ class Server{
|
||||
Timings::$worldSaveTimer->startTiming();
|
||||
foreach($this->players as $index => $player){
|
||||
if($player->spawned){
|
||||
$player->save(true);
|
||||
$player->save();
|
||||
}elseif(!$player->isConnected()){
|
||||
$this->removePlayer($player);
|
||||
}
|
||||
@ -2534,7 +2544,7 @@ class Server{
|
||||
}
|
||||
|
||||
if(($this->tickCounter & 0b111111111) === 0){
|
||||
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
|
||||
($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5))->call();
|
||||
if($this->queryHandler !== null){
|
||||
$this->queryHandler->regenerateInfo();
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
class ActivatorRail extends Rail{
|
||||
class ActivatorRail extends RedstoneRail{
|
||||
|
||||
protected $id = self::ACTIVATOR_RAIL;
|
||||
|
||||
|
266
src/pocketmine/block/BaseRail.php
Normal file
266
src/pocketmine/block/BaseRail.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?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\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Player;
|
||||
|
||||
abstract class BaseRail extends Flowable{
|
||||
|
||||
public const STRAIGHT_NORTH_SOUTH = 0;
|
||||
public const STRAIGHT_EAST_WEST = 1;
|
||||
public const ASCENDING_EAST = 2;
|
||||
public const ASCENDING_WEST = 3;
|
||||
public const ASCENDING_NORTH = 4;
|
||||
public const ASCENDING_SOUTH = 5;
|
||||
|
||||
private const ASCENDING_SIDES = [
|
||||
self::ASCENDING_NORTH => Vector3::SIDE_NORTH,
|
||||
self::ASCENDING_EAST => Vector3::SIDE_EAST,
|
||||
self::ASCENDING_SOUTH => Vector3::SIDE_SOUTH,
|
||||
self::ASCENDING_WEST => Vector3::SIDE_WEST
|
||||
];
|
||||
|
||||
protected const FLAG_ASCEND = 1 << 24; //used to indicate direction-up
|
||||
|
||||
protected const CONNECTIONS = [
|
||||
//straights
|
||||
self::STRAIGHT_NORTH_SOUTH => [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_SOUTH
|
||||
],
|
||||
self::STRAIGHT_EAST_WEST => [
|
||||
Vector3::SIDE_EAST,
|
||||
Vector3::SIDE_WEST
|
||||
],
|
||||
|
||||
//ascending
|
||||
self::ASCENDING_EAST => [
|
||||
Vector3::SIDE_WEST,
|
||||
Vector3::SIDE_EAST | self::FLAG_ASCEND
|
||||
],
|
||||
self::ASCENDING_WEST => [
|
||||
Vector3::SIDE_EAST,
|
||||
Vector3::SIDE_WEST | self::FLAG_ASCEND
|
||||
],
|
||||
self::ASCENDING_NORTH => [
|
||||
Vector3::SIDE_SOUTH,
|
||||
Vector3::SIDE_NORTH | self::FLAG_ASCEND
|
||||
],
|
||||
self::ASCENDING_SOUTH => [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_SOUTH | self::FLAG_ASCEND
|
||||
]
|
||||
];
|
||||
|
||||
public function __construct(int $meta = 0){
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 0.7;
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent() and $this->getLevel()->setBlock($blockReplace, $this, true, true)){
|
||||
$this->tryReconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function searchState(array $connections, array $lookup) : int{
|
||||
$meta = array_search($connections, $lookup, true);
|
||||
if($meta === false){
|
||||
$meta = array_search(array_reverse($connections), $lookup, true);
|
||||
}
|
||||
if($meta === false){
|
||||
throw new \InvalidArgumentException("No meta value matches connections " . implode(", ", array_map('dechex', $connections)));
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a meta value for the rail with the given connections.
|
||||
*
|
||||
* @param array $connections
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \InvalidArgumentException if no state matches the given connections
|
||||
*/
|
||||
protected function getMetaForState(array $connections) : int{
|
||||
return self::searchState($connections, self::CONNECTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection directions of this rail (depending on the current block state)
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
abstract protected function getConnectionsForState() : array;
|
||||
|
||||
/**
|
||||
* Returns all the directions this rail is already connected in.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function getConnectedDirections() : array{
|
||||
/** @var int[] $connections */
|
||||
$connections = [];
|
||||
|
||||
/** @var int $connection */
|
||||
foreach($this->getConnectionsForState() as $connection){
|
||||
$other = $this->getSide($connection & ~self::FLAG_ASCEND);
|
||||
$otherConnection = Vector3::getOppositeSide($connection & ~self::FLAG_ASCEND);
|
||||
|
||||
if(($connection & self::FLAG_ASCEND) !== 0){
|
||||
$other = $other->getSide(Vector3::SIDE_UP);
|
||||
|
||||
}elseif(!($other instanceof BaseRail)){ //check for rail sloping up to meet this one
|
||||
$other = $other->getSide(Vector3::SIDE_DOWN);
|
||||
$otherConnection |= self::FLAG_ASCEND;
|
||||
}
|
||||
|
||||
if(
|
||||
$other instanceof BaseRail and
|
||||
in_array($otherConnection, $other->getConnectionsForState(), true)
|
||||
){
|
||||
$connections[] = $connection;
|
||||
}
|
||||
}
|
||||
|
||||
return $connections;
|
||||
}
|
||||
|
||||
private function getPossibleConnectionDirections(array $constraints) : array{
|
||||
switch(count($constraints)){
|
||||
case 0:
|
||||
//No constraints, can connect in any direction
|
||||
$possible = [
|
||||
Vector3::SIDE_NORTH => true,
|
||||
Vector3::SIDE_SOUTH => true,
|
||||
Vector3::SIDE_WEST => true,
|
||||
Vector3::SIDE_EAST => true
|
||||
];
|
||||
foreach($possible as $p => $_){
|
||||
$possible[$p | self::FLAG_ASCEND] = true;
|
||||
}
|
||||
|
||||
return $possible;
|
||||
case 1:
|
||||
return $this->getPossibleConnectionDirectionsOneConstraint(array_shift($constraints));
|
||||
case 2:
|
||||
return [];
|
||||
default:
|
||||
throw new \InvalidArgumentException("Expected at most 2 constraints, got " . count($constraints));
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
|
||||
$opposite = Vector3::getOppositeSide($constraint & ~self::FLAG_ASCEND);
|
||||
|
||||
$possible = [$opposite => true];
|
||||
|
||||
if(($constraint & self::FLAG_ASCEND) === 0){
|
||||
//We can slope the other way if this connection isn't already a slope
|
||||
$possible[$opposite | self::FLAG_ASCEND] = true;
|
||||
}
|
||||
|
||||
return $possible;
|
||||
}
|
||||
|
||||
private function tryReconnect() : void{
|
||||
$thisConnections = $this->getConnectedDirections();
|
||||
$changed = false;
|
||||
|
||||
do{
|
||||
$possible = $this->getPossibleConnectionDirections($thisConnections);
|
||||
$continue = false;
|
||||
|
||||
foreach($possible as $thisSide => $_){
|
||||
$otherSide = Vector3::getOppositeSide($thisSide & ~self::FLAG_ASCEND);
|
||||
|
||||
$other = $this->getSide($thisSide & ~self::FLAG_ASCEND);
|
||||
|
||||
if(($thisSide & self::FLAG_ASCEND) !== 0){
|
||||
$other = $other->getSide(Vector3::SIDE_UP);
|
||||
|
||||
}elseif(!($other instanceof BaseRail)){ //check if other rails can slope up to meet this one
|
||||
$other = $other->getSide(Vector3::SIDE_DOWN);
|
||||
$otherSide |= self::FLAG_ASCEND;
|
||||
}
|
||||
|
||||
if(!($other instanceof BaseRail) or count($otherConnections = $other->getConnectedDirections()) >= 2){
|
||||
//we can only connect to a rail that has less than 2 connections
|
||||
continue;
|
||||
}
|
||||
|
||||
$otherPossible = $other->getPossibleConnectionDirections($otherConnections);
|
||||
|
||||
if(isset($otherPossible[$otherSide])){
|
||||
$otherConnections[] = $otherSide;
|
||||
$other->updateState($otherConnections);
|
||||
|
||||
$changed = true;
|
||||
$thisConnections[] = $thisSide;
|
||||
$continue = count($thisConnections) < 2;
|
||||
|
||||
break; //force recomputing possible directions, since this connection could invalidate others
|
||||
}
|
||||
}
|
||||
}while($continue);
|
||||
|
||||
if($changed){
|
||||
$this->updateState($thisConnections);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateState(array $connections) : void{
|
||||
if(count($connections) === 1){
|
||||
$connections[] = Vector3::getOppositeSide($connections[0] & ~self::FLAG_ASCEND);
|
||||
}elseif(count($connections) !== 2){
|
||||
throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections));
|
||||
}
|
||||
|
||||
$this->meta = $this->getMetaForState($connections);
|
||||
$this->level->setBlock($this, $this, false, false); //avoid recursion
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent() or (
|
||||
isset(self::ASCENDING_SIDES[$this->meta & 0x07]) and
|
||||
$this->getSide(self::ASCENDING_SIDES[$this->meta & 0x07])->isTransparent()
|
||||
)){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
class Cactus extends Transparent{
|
||||
|
||||
@ -95,7 +94,8 @@ class Cactus extends Transparent{
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS)));
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
abstract class Crops extends Flowable{
|
||||
|
||||
@ -50,8 +49,8 @@ abstract class Crops extends Flowable{
|
||||
$block->meta = 7;
|
||||
}
|
||||
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
|
||||
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
|
||||
}
|
||||
@ -79,8 +78,8 @@ abstract class Crops extends Flowable{
|
||||
if($this->meta < 0x07){
|
||||
$block = clone $this;
|
||||
++$block->meta;
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
|
||||
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
class DetectorRail extends Rail{
|
||||
class DetectorRail extends RedstoneRail{
|
||||
|
||||
protected $id = self::DETECTOR_RAIL;
|
||||
|
||||
|
@ -31,7 +31,6 @@ use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Server;
|
||||
|
||||
class Fire extends Flowable{
|
||||
|
||||
@ -69,7 +68,7 @@ class Fire extends Flowable{
|
||||
if($entity instanceof Arrow){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$entity->setOnFire($ev->getDuration());
|
||||
}
|
||||
@ -153,7 +152,8 @@ class Fire extends Flowable{
|
||||
|
||||
private function burnBlock(Block $block, int $chanceBound) : void{
|
||||
if(mt_rand(0, $chanceBound) < $block->getFlammability()){
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockBurnEvent($block, $this));
|
||||
$ev = new BlockBurnEvent($block, $this);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$block->onIncinerate();
|
||||
|
||||
|
@ -67,7 +67,8 @@ class Grass extends Solid{
|
||||
$lightAbove = $this->level->getFullLightAt($this->x, $this->y + 1, $this->z);
|
||||
if($lightAbove < 4 and BlockFactory::$lightFilter[$this->level->getBlockIdAt($this->x, $this->y + 1, $this->z)] >= 3){ //2 plus 1 standard filter amount
|
||||
//grass dies
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT)));
|
||||
$ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->level->setBlock($this, $ev->getNewState(), false, false);
|
||||
}
|
||||
@ -86,7 +87,8 @@ class Grass extends Solid{
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($b = $this->level->getBlockAt($x, $y, $z), $this, BlockFactory::get(Block::GRASS)));
|
||||
$ev = new BlockSpreadEvent($b = $this->level->getBlockAt($x, $y, $z), $this, BlockFactory::get(Block::GRASS));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->level->setBlock($b, $ev->getNewState(), false, false);
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
class Lava extends Liquid{
|
||||
|
||||
@ -107,7 +106,7 @@ class Lava extends Liquid{
|
||||
$entity->attack($ev);
|
||||
|
||||
$ev = new EntityCombustByBlockEvent($this, $entity, 15);
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$entity->setOnFire($ev->getDuration());
|
||||
}
|
||||
|
@ -147,8 +147,8 @@ class Leaves extends Transparent{
|
||||
$this->meta &= 0x03;
|
||||
$visited = [];
|
||||
|
||||
$this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new LeavesDecayEvent($this));
|
||||
|
||||
$ev = new LeavesDecayEvent($this);
|
||||
$ev->call();
|
||||
if($ev->isCancelled() or $this->findLog($this, $visited, 0)){
|
||||
$this->getLevel()->setBlock($this, $this, false, false);
|
||||
}else{
|
||||
|
@ -24,11 +24,13 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
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{
|
||||
|
||||
@ -294,12 +296,16 @@ abstract class Liquid extends Transparent{
|
||||
|
||||
protected function flowIntoBlock(Block $block, int $newFlowDecay) : void{
|
||||
if($this->canFlowInto($block) and !($block instanceof Liquid)){
|
||||
if($block->getId() > 0){
|
||||
$this->level->useBreakOn($block);
|
||||
}
|
||||
$ev = new BlockSpreadEvent($block, $this, BlockFactory::get($this->getId(), $newFlowDecay));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
if($block->getId() > 0){
|
||||
$this->level->useBreakOn($block);
|
||||
}
|
||||
|
||||
$this->level->setBlock($block, BlockFactory::get($this->getId(), $newFlowDecay), true, true);
|
||||
$this->level->scheduleDelayedBlockUpdate($block, $this->tickRate());
|
||||
$this->level->setBlock($block, $ev->getNewState(), true, true);
|
||||
$this->level->scheduleDelayedBlockUpdate($block, $this->tickRate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,10 +431,12 @@ abstract class Liquid extends Transparent{
|
||||
}
|
||||
|
||||
protected function liquidCollide(Block $cause, Block $result) : bool{
|
||||
//TODO: add events
|
||||
|
||||
$this->level->setBlock($this, $result, 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));
|
||||
$ev = new BlockFormEvent($this, $result);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->level->setBlock($this, $ev->getNewState(), true, true);
|
||||
$this->level->addSound(new FizzSound($this->add(0.5, 0.5, 0.5), 2.6 + (lcg_value() - lcg_value()) * 0.8));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Server;
|
||||
|
||||
class MelonStem extends Crops{
|
||||
|
||||
@ -46,7 +45,8 @@ class MelonStem extends Crops{
|
||||
if($this->meta < 0x07){
|
||||
$block = clone $this;
|
||||
++$block->meta;
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
|
||||
}
|
||||
@ -60,7 +60,8 @@ class MelonStem extends Crops{
|
||||
$side = $this->getSide(mt_rand(2, 5));
|
||||
$d = $side->getSide(Vector3::SIDE_DOWN);
|
||||
if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, BlockFactory::get(Block::MELON_BLOCK)));
|
||||
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::MELON_BLOCK));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\event\block\BlockSpreadEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Server;
|
||||
|
||||
class Mycelium extends Solid{
|
||||
|
||||
@ -67,7 +66,8 @@ class Mycelium extends Solid{
|
||||
$block = $this->getLevel()->getBlockAt($x, $y, $z);
|
||||
if($block->getId() === Block::DIRT){
|
||||
if($block->getSide(Vector3::SIDE_UP) instanceof Transparent){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($block, $this, BlockFactory::get(Block::MYCELIUM)));
|
||||
$ev = new BlockSpreadEvent($block, $this, BlockFactory::get(Block::MYCELIUM));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($block, $ev->getNewState());
|
||||
}
|
||||
|
@ -68,8 +68,8 @@ class NetherWartPlant extends Flowable{
|
||||
if($this->meta < 3 and mt_rand(0, 10) === 0){ //Still growing
|
||||
$block = clone $this;
|
||||
$block->meta++;
|
||||
$this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
|
||||
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), false, true);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
class PoweredRail extends Rail{
|
||||
class PoweredRail extends RedstoneRail{
|
||||
protected $id = self::POWERED_RAIL;
|
||||
|
||||
public function getName() : string{
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Server;
|
||||
|
||||
class PumpkinStem extends Crops{
|
||||
|
||||
@ -46,7 +45,8 @@ class PumpkinStem extends Crops{
|
||||
if($this->meta < 0x07){
|
||||
$block = clone $this;
|
||||
++$block->meta;
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
|
||||
}
|
||||
@ -60,7 +60,8 @@ class PumpkinStem extends Crops{
|
||||
$side = $this->getSide(mt_rand(2, 5));
|
||||
$d = $side->getSide(Vector3::SIDE_DOWN);
|
||||
if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, BlockFactory::get(Block::PUMPKIN)));
|
||||
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::PUMPKIN));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
|
||||
}
|
||||
|
@ -23,54 +23,71 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Rail extends Flowable{
|
||||
class Rail extends BaseRail{
|
||||
|
||||
public const STRAIGHT_NORTH_SOUTH = 0;
|
||||
public const STRAIGHT_EAST_WEST = 1;
|
||||
public const ASCENDING_EAST = 2;
|
||||
public const ASCENDING_WEST = 3;
|
||||
public const ASCENDING_NORTH = 4;
|
||||
public const ASCENDING_SOUTH = 5;
|
||||
/* extended meta values for regular rails, to allow curving */
|
||||
public const CURVE_SOUTHEAST = 6;
|
||||
public const CURVE_SOUTHWEST = 7;
|
||||
public const CURVE_NORTHWEST = 8;
|
||||
public const CURVE_NORTHEAST = 9;
|
||||
|
||||
protected $id = self::RAIL;
|
||||
private const CURVE_CONNECTIONS = [
|
||||
self::CURVE_SOUTHEAST => [
|
||||
Vector3::SIDE_SOUTH,
|
||||
Vector3::SIDE_EAST
|
||||
],
|
||||
self::CURVE_SOUTHWEST => [
|
||||
Vector3::SIDE_SOUTH,
|
||||
Vector3::SIDE_WEST
|
||||
],
|
||||
self::CURVE_NORTHWEST => [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_WEST
|
||||
],
|
||||
self::CURVE_NORTHEAST => [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_EAST
|
||||
]
|
||||
];
|
||||
|
||||
public function __construct(int $meta = 0){
|
||||
$this->meta = $meta;
|
||||
}
|
||||
protected $id = self::RAIL;
|
||||
|
||||
public function getName() : string{
|
||||
return "Rail";
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 0.7;
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
}else{
|
||||
//TODO: Update rail connectivity
|
||||
protected function getMetaForState(array $connections) : int{
|
||||
try{
|
||||
return self::searchState($connections, self::CURVE_CONNECTIONS);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
return parent::getMetaForState($connections);
|
||||
}
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
return 0;
|
||||
protected function getConnectionsForState() : array{
|
||||
return self::CURVE_CONNECTIONS[$this->meta] ?? self::CONNECTIONS[$this->meta];
|
||||
}
|
||||
|
||||
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
|
||||
static $horizontal = [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_SOUTH,
|
||||
Vector3::SIDE_WEST,
|
||||
Vector3::SIDE_EAST
|
||||
];
|
||||
|
||||
$possible = parent::getPossibleConnectionDirectionsOneConstraint($constraint);
|
||||
|
||||
if(($constraint & self::FLAG_ASCEND) === 0){
|
||||
foreach($horizontal as $d){
|
||||
if($constraint !== $d){
|
||||
$possible[$d] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $possible;
|
||||
}
|
||||
}
|
||||
|
32
src/pocketmine/block/RedstoneRail.php
Normal file
32
src/pocketmine/block/RedstoneRail.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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\block;
|
||||
|
||||
class RedstoneRail extends BaseRail{
|
||||
protected const FLAG_POWERED = 0x08;
|
||||
|
||||
protected function getConnectionsForState() : array{
|
||||
return self::CONNECTIONS[$this->meta & ~self::FLAG_POWERED];
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
class Sugarcane extends Flowable{
|
||||
|
||||
@ -49,7 +48,8 @@ class Sugarcane extends Flowable{
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK)));
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\command;
|
||||
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
@ -49,11 +48,6 @@ class FormattedCommandAlias extends Command{
|
||||
$commands[] = $this->buildCommand($formatString, $args);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$sender->sendMessage(TextFormat::RED . $e->getMessage());
|
||||
return false;
|
||||
}catch(\Throwable $e){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception"));
|
||||
$sender->getServer()->getLogger()->logException($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -65,9 +65,7 @@ use pocketmine\command\defaults\VanillaCommand;
|
||||
use pocketmine\command\defaults\VersionCommand;
|
||||
use pocketmine\command\defaults\WhitelistCommand;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
class SimpleCommandMap implements CommandMap{
|
||||
|
||||
@ -258,14 +256,10 @@ class SimpleCommandMap implements CommandMap{
|
||||
$target->execute($sender, $sentCommandLabel, $args);
|
||||
}catch(InvalidCommandSyntaxException $e){
|
||||
$sender->sendMessage($this->server->getLanguage()->translateString("commands.generic.usage", [$target->getUsage()]));
|
||||
}catch(\Throwable $e){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception"));
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.command.exception", [$commandLine, (string) $target, $e->getMessage()]));
|
||||
$sender->getServer()->getLogger()->logException($e);
|
||||
}finally{
|
||||
$target->timings->stopTiming();
|
||||
}
|
||||
|
||||
$target->timings->stopTiming();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,8 @@ class PardonCommand extends VanillaCommand{
|
||||
parent::__construct(
|
||||
$name,
|
||||
"%pocketmine.command.unban.player.description",
|
||||
"%commands.unban.usage"
|
||||
"%commands.unban.usage",
|
||||
["unban"]
|
||||
);
|
||||
$this->setPermission("pocketmine.command.unban.player");
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ class PardonIpCommand extends VanillaCommand{
|
||||
parent::__construct(
|
||||
$name,
|
||||
"%pocketmine.command.unban.ip.description",
|
||||
"%commands.unbanip.usage"
|
||||
"%commands.unbanip.usage",
|
||||
["unban-ip"]
|
||||
);
|
||||
$this->setPermission("pocketmine.command.unban.ip");
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -54,7 +54,7 @@ class Attribute{
|
||||
public static function init() : void{
|
||||
self::addAttribute(self::ABSORPTION, "minecraft:absorption", 0.00, 340282346638528859811704183484516925440.00, 0.00);
|
||||
self::addAttribute(self::SATURATION, "minecraft:player.saturation", 0.00, 20.00, 20.00);
|
||||
self::addAttribute(self::EXHAUSTION, "minecraft:player.exhaustion", 0.00, 5.00, 0.0);
|
||||
self::addAttribute(self::EXHAUSTION, "minecraft:player.exhaustion", 0.00, 5.00, 0.0, false);
|
||||
self::addAttribute(self::KNOCKBACK_RESISTANCE, "minecraft:knockback_resistance", 0.00, 1.00, 0.00);
|
||||
self::addAttribute(self::HEALTH, "minecraft:health", 0.00, 20.00, 20.00);
|
||||
self::addAttribute(self::MOVEMENT_SPEED, "minecraft:movement", 0.00, 340282346638528859811704183484516925440.00, 0.10);
|
||||
@ -171,7 +171,7 @@ class Attribute{
|
||||
}
|
||||
|
||||
public function resetToDefault() : void{
|
||||
$this->setValue($this->getDefaultValue());
|
||||
$this->setValue($this->getDefaultValue(), true);
|
||||
}
|
||||
|
||||
public function getValue() : float{
|
||||
|
@ -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[] */
|
||||
@ -516,7 +534,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
|
||||
$this->recalculateBoundingBox();
|
||||
|
||||
$this->chunk = $this->level->getChunk($this->getFloorX() >> 4, $this->getFloorZ() >> 4, false);
|
||||
$this->chunk = $this->level->getChunkAtPosition($this, false);
|
||||
if($this->chunk === null){
|
||||
throw new \InvalidStateException("Cannot create entities in unloaded chunks");
|
||||
}
|
||||
@ -563,7 +581,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->level->addEntity($this);
|
||||
|
||||
$this->lastUpdate = $this->server->getTick();
|
||||
$this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this));
|
||||
(new EntitySpawnEvent($this))->call();
|
||||
|
||||
$this->scheduleUpdate();
|
||||
|
||||
@ -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;
|
||||
@ -905,7 +926,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
* @param EntityDamageEvent $source
|
||||
*/
|
||||
public function attack(EntityDamageEvent $source) : void{
|
||||
$this->server->getPluginManager()->callEvent($source);
|
||||
$source->call();
|
||||
if($source->isCancelled()){
|
||||
return;
|
||||
}
|
||||
@ -919,7 +940,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
* @param EntityRegainHealthEvent $source
|
||||
*/
|
||||
public function heal(EntityRegainHealthEvent $source) : void{
|
||||
$this->server->getPluginManager()->callEvent($source);
|
||||
$source->call();
|
||||
if($source->isCancelled()){
|
||||
return;
|
||||
}
|
||||
@ -1150,34 +1171,30 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
|
||||
protected function broadcastMovement(bool $teleport = false) : void{
|
||||
if($this->chunk !== null){
|
||||
$pk = new MoveEntityAbsolutePacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->position = $this->getOffsetPosition($this);
|
||||
$pk = new MoveEntityAbsolutePacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->position = $this->getOffsetPosition($this);
|
||||
|
||||
//this looks very odd but is correct as of 1.5.0.7
|
||||
//for arrows this is actually x/y/z rotation
|
||||
//for mobs x and z are used for pitch and yaw, and y is used for headyaw
|
||||
$pk->xRot = $this->pitch;
|
||||
$pk->yRot = $this->yaw; //TODO: head yaw
|
||||
$pk->zRot = $this->yaw;
|
||||
//this looks very odd but is correct as of 1.5.0.7
|
||||
//for arrows this is actually x/y/z rotation
|
||||
//for mobs x and z are used for pitch and yaw, and y is used for headyaw
|
||||
$pk->xRot = $this->pitch;
|
||||
$pk->yRot = $this->yaw; //TODO: head yaw
|
||||
$pk->zRot = $this->yaw;
|
||||
|
||||
if($teleport){
|
||||
$pk->flags |= MoveEntityAbsolutePacket::FLAG_TELEPORT;
|
||||
}
|
||||
|
||||
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
|
||||
if($teleport){
|
||||
$pk->flags |= MoveEntityAbsolutePacket::FLAG_TELEPORT;
|
||||
}
|
||||
|
||||
$this->level->broadcastPacketToViewers($this, $pk);
|
||||
}
|
||||
|
||||
protected function broadcastMotion() : void{
|
||||
if($this->chunk !== null){
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->motion = $this->getMotion();
|
||||
|
||||
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
|
||||
}
|
||||
$this->level->broadcastPacketToViewers($this, $pk);
|
||||
}
|
||||
|
||||
protected function applyDragBeforeGravity() : bool{
|
||||
@ -1808,7 +1825,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->chunk = $this->level->getChunk($chunkX, $chunkZ, true);
|
||||
|
||||
if(!$this->justCreated){
|
||||
$newChunk = $this->level->getChunkPlayers($chunkX, $chunkZ);
|
||||
$newChunk = $this->level->getViewersForPosition($this);
|
||||
foreach($this->hasSpawned as $player){
|
||||
if(!isset($newChunk[$player->getLoaderId()])){
|
||||
$this->despawnFrom($player);
|
||||
@ -1841,7 +1858,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
public function setMotion(Vector3 $motion) : bool{
|
||||
if(!$this->justCreated){
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityMotionEvent($this, $motion));
|
||||
$ev = new EntityMotionEvent($this, $motion);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1874,7 +1892,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
$from = Position::fromObject($this, $this->level);
|
||||
$to = Position::fromObject($pos, $pos instanceof Position ? $pos->getLevel() : $this->level);
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityTeleportEvent($this, $from, $to));
|
||||
$ev = new EntityTeleportEvent($this, $from, $to);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1900,7 +1919,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
|
||||
if($this->isValid()){
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel));
|
||||
$ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1953,7 +1973,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
* @param Player $player
|
||||
*/
|
||||
public function spawnTo(Player $player) : void{
|
||||
if(!isset($this->hasSpawned[$player->getLoaderId()]) and $this->chunk !== null and isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])){
|
||||
if(!isset($this->hasSpawned[$player->getLoaderId()])){
|
||||
$this->hasSpawned[$player->getLoaderId()] = $player;
|
||||
|
||||
$this->sendSpawnPacket($player);
|
||||
@ -1964,7 +1984,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
if($this->chunk === null or $this->closed){
|
||||
return;
|
||||
}
|
||||
foreach($this->level->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){
|
||||
foreach($this->level->getViewersForPosition($this) as $player){
|
||||
if($player->isOnline()){
|
||||
$this->spawnTo($player);
|
||||
}
|
||||
@ -2026,7 +2046,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
*/
|
||||
public function close() : void{
|
||||
if(!$this->closed){
|
||||
$this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this));
|
||||
(new EntityDespawnEvent($this))->call();
|
||||
$this->closed = true;
|
||||
|
||||
$this->despawnFromAll();
|
||||
@ -2079,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2089,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -269,7 +269,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
* @return float the amount of exhaustion level increased
|
||||
*/
|
||||
public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerExhaustEvent($this, $amount, $cause));
|
||||
$ev = new PlayerExhaustEvent($this, $amount, $cause);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return 0.0;
|
||||
}
|
||||
@ -449,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -466,7 +467,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
protected function setXpAndProgress(?int $level, ?float $progress) : bool{
|
||||
if(!$this->justCreated){
|
||||
$ev = new PlayerExperienceChangeEvent($this, $this->getXpLevel(), $this->getXpProgress(), $level, $progress);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
|
@ -205,7 +205,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
if(isset($this->effects[$effectId])){
|
||||
$effect = $this->effects[$effectId];
|
||||
$hasExpired = $effect->hasExpired();
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityEffectRemoveEvent($this, $effect));
|
||||
$ev = new EntityEffectRemoveEvent($this, $effect);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
if($hasExpired and !$ev->getEffect()->hasExpired()){ //altered duration of an expired effect to make it not get removed
|
||||
$this->sendEffectAdd($ev->getEffect(), true);
|
||||
@ -278,7 +279,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
$ev = new EntityEffectAddEvent($this, $effect, $oldEffect);
|
||||
$ev->setCancelled($cancelled);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -570,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());
|
||||
}
|
||||
|
||||
@ -622,7 +626,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
}
|
||||
|
||||
protected function onDeath() : void{
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops()));
|
||||
$ev = new EntityDeathEvent($this, $this->getDrops());
|
||||
$ev->call();
|
||||
foreach($ev->getDrops() as $item){
|
||||
$this->getLevel()->dropItem($this, $item);
|
||||
}
|
||||
@ -655,11 +660,11 @@ abstract class Living extends Entity implements Damageable{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if($this->doEffectsTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isAlive()){
|
||||
if($this->doEffectsTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isInsideOfSolid()){
|
||||
$hasUpdate = true;
|
||||
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1);
|
||||
|
@ -113,7 +113,8 @@ class FallingBlock extends Entity{
|
||||
//FIXME: anvils are supposed to destroy torches
|
||||
$this->getLevel()->dropItem($this, ItemFactory::get($this->getBlock(), $this->getDamage()));
|
||||
}else{
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block));
|
||||
$ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($pos, $ev->getTo(), true);
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ class ItemEntity extends Entity{
|
||||
}
|
||||
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new ItemSpawnEvent($this));
|
||||
(new ItemSpawnEvent($this))->call();
|
||||
}
|
||||
|
||||
public function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
@ -96,7 +96,8 @@ class ItemEntity extends Entity{
|
||||
|
||||
$this->age += $tickDiff;
|
||||
if($this->age > 6000){
|
||||
$this->server->getPluginManager()->callEvent($ev = new ItemDespawnEvent($this));
|
||||
$ev = new ItemDespawnEvent($this);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->age = 0;
|
||||
}else{
|
||||
@ -208,11 +209,12 @@ class ItemEntity extends Entity{
|
||||
$item = $this->getItem();
|
||||
$playerInventory = $player->getInventory();
|
||||
|
||||
if(!($item instanceof Item) or ($player->isSurvival() and !$playerInventory->canAddItem($item))){
|
||||
if($player->isSurvival() and !$playerInventory->canAddItem($item)){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($playerInventory, $this));
|
||||
$ev = new InventoryPickupItemEvent($playerInventory, $this);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
@ -102,8 +102,8 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
}
|
||||
|
||||
public function explode() : void{
|
||||
$this->server->getPluginManager()->callEvent($ev = new ExplosionPrimeEvent($this, 4));
|
||||
|
||||
$ev = new ExplosionPrimeEvent($this, 4);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$explosion = new Explosion($this, $ev->getForce(), $this);
|
||||
if($ev->isBlockBreaking()){
|
||||
|
@ -186,7 +186,7 @@ class Arrow extends Projectile{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ abstract class Projectile extends Entity{
|
||||
}
|
||||
|
||||
if($ev !== null){
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
$this->onHit($ev);
|
||||
|
||||
if($ev instanceof ProjectileHitEntityEvent){
|
||||
@ -315,7 +315,7 @@ abstract class Projectile extends Entity{
|
||||
|
||||
if($this->fireTicks > 0){
|
||||
$ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$entityHit->setOnFire($ev->getDuration());
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event;
|
||||
|
||||
abstract class Event{
|
||||
private const MAX_EVENT_CALL_DEPTH = 50;
|
||||
/** @var int */
|
||||
private static $eventCallDepth = 1;
|
||||
|
||||
/** @var string|null */
|
||||
protected $eventName = null;
|
||||
@ -67,4 +70,37 @@ abstract class Event{
|
||||
/** @var Event $this */
|
||||
$this->isCancelled = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls event handlers registered for this event.
|
||||
*
|
||||
* @throws \RuntimeException if event call recursion reaches the max depth limit
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function call() : void{
|
||||
if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
|
||||
//this exception will be caught by the parent event call if all else fails
|
||||
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
|
||||
}
|
||||
|
||||
$handlerList = HandlerList::getHandlerListFor(get_class($this));
|
||||
assert($handlerList !== null, "Called event should have a valid HandlerList");
|
||||
|
||||
++self::$eventCallDepth;
|
||||
try{
|
||||
foreach(EventPriority::ALL as $priority){
|
||||
$currentList = $handlerList;
|
||||
while($currentList !== null){
|
||||
foreach($currentList->getListenersByPriority($priority) as $registration){
|
||||
$registration->callEvent($this);
|
||||
}
|
||||
|
||||
$currentList = $currentList->getParent();
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
--self::$eventCallDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\inventory;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityArmorChangeEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
|
||||
class ArmorInventoryEventProcessor implements InventoryEventProcessor{
|
||||
/** @var Entity */
|
||||
@ -37,7 +36,8 @@ class ArmorInventoryEventProcessor implements InventoryEventProcessor{
|
||||
}
|
||||
|
||||
public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem, Item $newItem) : ?Item{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->entity, $oldItem, $newItem, $slot));
|
||||
$ev = new EntityArmorChangeEvent($this->entity, $oldItem, $newItem, $slot);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return null;
|
||||
}
|
||||
|
@ -399,7 +399,8 @@ abstract class BaseInventory implements Inventory{
|
||||
}
|
||||
|
||||
public function open(Player $who) : bool{
|
||||
$who->getServer()->getPluginManager()->callEvent($ev = new InventoryOpenEvent($this, $who));
|
||||
$ev = new InventoryOpenEvent($this, $who);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
@ -87,6 +87,6 @@ class ChestInventory extends ContainerInventory{
|
||||
$pk->z = (int) $holder->z;
|
||||
$pk->eventType = 1; //it's always 1 for a chest
|
||||
$pk->eventData = $isOpen ? 1 : 0;
|
||||
$holder->getLevel()->addChunkPacket($holder->getFloorX() >> 4, $holder->getFloorZ() >> 4, $pk);
|
||||
$holder->getLevel()->broadcastPacketToViewers($holder, $pk);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\inventory;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
|
||||
class EntityInventoryEventProcessor implements InventoryEventProcessor{
|
||||
/** @var Entity */
|
||||
@ -37,7 +36,8 @@ class EntityInventoryEventProcessor implements InventoryEventProcessor{
|
||||
}
|
||||
|
||||
public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem, Item $newItem) : ?Item{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->entity, $oldItem, $newItem, $slot));
|
||||
$ev = new EntityInventoryChangeEvent($this->entity, $oldItem, $newItem, $slot);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return null;
|
||||
}
|
||||
|
@ -69,7 +69,8 @@ class PlayerInventory extends BaseInventory{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot));
|
||||
$ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot);
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
$this->sendHeldItem($this->getHolder());
|
||||
|
@ -134,7 +134,8 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
}
|
||||
|
||||
protected function callExecuteEvent() : bool{
|
||||
$this->source->getServer()->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs));
|
||||
$ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs);
|
||||
$ev->call();
|
||||
return !$ev->isCancelled();
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ use pocketmine\inventory\transaction\action\InventoryAction;
|
||||
use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
/**
|
||||
* This InventoryTransaction only allows doing Transaction between one / two inventories
|
||||
@ -250,7 +249,8 @@ class InventoryTransaction{
|
||||
}
|
||||
|
||||
protected function callExecuteEvent() : bool{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
|
||||
$ev = new InventoryTransactionEvent($this);
|
||||
$ev->call();
|
||||
return !$ev->isCancelled();
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,8 @@ class DropItemAction extends InventoryAction{
|
||||
}
|
||||
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
$source->getServer()->getPluginManager()->callEvent($ev = new PlayerDropItemEvent($source, $this->targetItem));
|
||||
$ev = new PlayerDropItemEvent($source, $this->targetItem);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class Bow extends Tool{
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$player->getServer()->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
$entity = $ev->getProjectile(); //This might have been changed by plugins
|
||||
|
||||
@ -104,7 +104,8 @@ class Bow extends Tool{
|
||||
}
|
||||
|
||||
if($entity instanceof Projectile){
|
||||
$player->getServer()->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($entity));
|
||||
$projectileEv = new ProjectileLaunchEvent($entity);
|
||||
$projectileEv->call();
|
||||
if($projectileEv->isCancelled()){
|
||||
$ev->getProjectile()->flagForDespawn();
|
||||
}else{
|
||||
|
@ -59,7 +59,8 @@ class Bucket extends Item implements Consumable{
|
||||
|
||||
$stack->pop();
|
||||
$resultItem = ItemFactory::get(Item::BUCKET, $blockClicked->getFlowingForm()->getId());
|
||||
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem));
|
||||
$ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$player->getLevel()->setBlock($blockClicked, BlockFactory::get(Block::AIR), true, true);
|
||||
$player->getLevel()->broadcastLevelSoundEvent($blockClicked->add(0.5, 0.5, 0.5), $blockClicked->getBucketFillSound());
|
||||
@ -80,7 +81,8 @@ class Bucket extends Item implements Consumable{
|
||||
}
|
||||
}
|
||||
}elseif($resultBlock instanceof Liquid and $blockReplace->canBeReplaced()){
|
||||
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, ItemFactory::get(Item::BUCKET)));
|
||||
$ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, ItemFactory::get(Item::BUCKET));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$player->getLevel()->setBlock($blockReplace, $resultBlock->getFlowingForm(), true, true);
|
||||
$player->getLevel()->broadcastLevelSoundEvent($blockClicked->add(0.5, 0.5, 0.5), $resultBlock->getBucketEmptySound());
|
||||
|
@ -37,67 +37,68 @@ class PaintingItem extends Item{
|
||||
}
|
||||
|
||||
public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{
|
||||
if(!$blockClicked->isTransparent() and $face > 1 and !$blockReplace->isSolid()){
|
||||
/** @var PaintingMotive[] $motives */
|
||||
$motives = [];
|
||||
if($face === Vector3::SIDE_DOWN or $face === Vector3::SIDE_UP){
|
||||
return false;
|
||||
}
|
||||
|
||||
$totalDimension = 0;
|
||||
foreach(PaintingMotive::getAll() as $motive){
|
||||
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
|
||||
if($currentTotalDimension < $totalDimension){
|
||||
continue;
|
||||
$motives = [];
|
||||
|
||||
$totalDimension = 0;
|
||||
foreach(PaintingMotive::getAll() as $motive){
|
||||
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
|
||||
if($currentTotalDimension < $totalDimension){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
|
||||
if($currentTotalDimension > $totalDimension){
|
||||
$totalDimension = $currentTotalDimension;
|
||||
/*
|
||||
* This drops all motive possibilities smaller than this
|
||||
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
|
||||
* when there is an L-shape of space available.
|
||||
*/
|
||||
$motives = [];
|
||||
}
|
||||
|
||||
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
|
||||
if($currentTotalDimension > $totalDimension){
|
||||
$totalDimension = $currentTotalDimension;
|
||||
/*
|
||||
* This drops all motive possibilities smaller than this
|
||||
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
|
||||
* when there is an L-shape of space available.
|
||||
*/
|
||||
$motives = [];
|
||||
}
|
||||
|
||||
$motives[] = $motive;
|
||||
}
|
||||
$motives[] = $motive;
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($motives)){ //No space available
|
||||
return false;
|
||||
}
|
||||
if(empty($motives)){ //No space available
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var PaintingMotive $motive */
|
||||
$motive = $motives[array_rand($motives)];
|
||||
/** @var PaintingMotive $motive */
|
||||
$motive = $motives[array_rand($motives)];
|
||||
|
||||
static $directions = [
|
||||
Vector3::SIDE_SOUTH => 0,
|
||||
Vector3::SIDE_WEST => 1,
|
||||
Vector3::SIDE_NORTH => 2,
|
||||
Vector3::SIDE_EAST => 3
|
||||
];
|
||||
static $directions = [
|
||||
Vector3::SIDE_SOUTH => 0,
|
||||
Vector3::SIDE_WEST => 1,
|
||||
Vector3::SIDE_NORTH => 2,
|
||||
Vector3::SIDE_EAST => 3
|
||||
];
|
||||
|
||||
$direction = $directions[$face] ?? -1;
|
||||
if($direction === -1){
|
||||
return false;
|
||||
}
|
||||
$direction = $directions[$face] ?? -1;
|
||||
if($direction === -1){
|
||||
return false;
|
||||
}
|
||||
|
||||
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
|
||||
$nbt->setByte("Direction", $direction);
|
||||
$nbt->setString("Motive", $motive->getName());
|
||||
$nbt->setInt("TileX", $blockClicked->getFloorX());
|
||||
$nbt->setInt("TileY", $blockClicked->getFloorY());
|
||||
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
|
||||
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
|
||||
$nbt->setByte("Direction", $direction);
|
||||
$nbt->setString("Motive", $motive->getName());
|
||||
$nbt->setInt("TileX", $blockClicked->getFloorX());
|
||||
$nbt->setInt("TileY", $blockClicked->getFloorY());
|
||||
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
|
||||
|
||||
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
|
||||
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
|
||||
|
||||
if($entity instanceof Entity){
|
||||
--$this->count;
|
||||
$entity->spawnToAll();
|
||||
if($entity instanceof Entity){
|
||||
--$this->count;
|
||||
$entity->spawnToAll();
|
||||
|
||||
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
|
||||
return true;
|
||||
}
|
||||
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -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;
|
||||
@ -58,15 +59,14 @@ abstract class ProjectileItem extends Item{
|
||||
$this->count--;
|
||||
|
||||
if($projectile instanceof Projectile){
|
||||
$player->getServer()->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($projectile));
|
||||
$projectileEv = new ProjectileLaunchEvent($projectile);
|
||||
$projectileEv->call();
|
||||
if($projectileEv->isCancelled()){
|
||||
$projectile->flagForDespawn();
|
||||
}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();
|
||||
|
Submodule src/pocketmine/lang/locale updated: 0cd3f8ef30...1d525a7c1c
@ -153,7 +153,8 @@ class Explosion{
|
||||
$yield = (1 / $this->size) * 100;
|
||||
|
||||
if($this->what instanceof Entity){
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield));
|
||||
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}else{
|
||||
@ -234,7 +235,8 @@ class Explosion{
|
||||
continue;
|
||||
}
|
||||
if(!isset($this->affectedBlocks[$index = Level::blockHash($sideBlock->x, $sideBlock->y, $sideBlock->z)]) and !isset($updateBlocks[$index])){
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->level->getBlockAt($sideBlock->x, $sideBlock->y, $sideBlock->z)));
|
||||
$ev = new BlockUpdateEvent($this->level->getBlockAt($sideBlock->x, $sideBlock->y, $sideBlock->z));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
foreach($this->level->getNearbyEntities(new AxisAlignedBB($sideBlock->x - 1, $sideBlock->y - 1, $sideBlock->z - 1, $sideBlock->x + 2, $sideBlock->y + 2, $sideBlock->z + 2)) as $entity){
|
||||
$entity->onNearbyBlockChange();
|
||||
@ -251,7 +253,7 @@ class Explosion{
|
||||
$pk->position = $this->source->asVector3();
|
||||
$pk->radius = $this->size;
|
||||
$pk->records = $send;
|
||||
$this->level->addChunkPacket($source->getFloorX() >> 4, $source->getFloorZ() >> 4, $pk);
|
||||
$this->level->broadcastPacketToViewers($source, $pk);
|
||||
|
||||
$this->level->addParticle(new HugeExplodeSeedParticle($source));
|
||||
$this->level->broadcastLevelSoundEvent($source, LevelSoundEventPacket::SOUND_EXPLODE);
|
||||
|
@ -69,6 +69,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;
|
||||
@ -241,12 +242,20 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var int */
|
||||
public $tickRateCounter = 0;
|
||||
|
||||
/** @var bool */
|
||||
private $doingTick = false;
|
||||
|
||||
/** @var string|Generator */
|
||||
private $generator;
|
||||
|
||||
/** @var bool */
|
||||
private $closed = false;
|
||||
|
||||
/** @var BlockLightUpdate|null */
|
||||
private $blockLightUpdate = null;
|
||||
/** @var SkyLightUpdate|null */
|
||||
private $skyLightUpdate = null;
|
||||
|
||||
public static function chunkHash(int $x, int $z) : int{
|
||||
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF);
|
||||
}
|
||||
@ -442,13 +451,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
if(!is_array($pk)){
|
||||
$pk = [$pk];
|
||||
}
|
||||
|
||||
if($players === null){
|
||||
foreach($pk as $e){
|
||||
$this->addChunkPacket($sound->getFloorX() >> 4, $sound->getFloorZ() >> 4, $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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,13 +467,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
if(!is_array($pk)){
|
||||
$pk = [$pk];
|
||||
}
|
||||
|
||||
if($players === null){
|
||||
foreach($pk as $e){
|
||||
$this->addChunkPacket($particle->getFloorX() >> 4, $particle->getFloorZ() >> 4, $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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,10 +491,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->data = $data;
|
||||
if($pos !== null){
|
||||
$pk->position = $pos->asVector3();
|
||||
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $pk);
|
||||
$this->broadcastPacketToViewers($pos, $pk);
|
||||
}else{
|
||||
$pk->position = null;
|
||||
$this->addGlobalPacket($pk);
|
||||
$this->broadcastGlobalPacket($pk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,20 +503,20 @@ 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();
|
||||
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $pk);
|
||||
$this->broadcastPacketToViewers($pos, $pk);
|
||||
}
|
||||
|
||||
public function getAutoSave() : bool{
|
||||
@ -524,8 +535,12 @@ class Level implements ChunkManager, Metadatable{
|
||||
* @param bool $force default false, force unload of default level
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidStateException if trying to unload a level during level tick
|
||||
*/
|
||||
public function unload(bool $force = false) : bool{
|
||||
if($this->doingTick and !$force){
|
||||
throw new \InvalidStateException("Cannot unload a level during level tick");
|
||||
}
|
||||
|
||||
$ev = new LevelUnloadEvent($this);
|
||||
|
||||
@ -533,7 +548,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$ev->setCancelled(true);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
|
||||
if(!$force and $ev->isCancelled()){
|
||||
return false;
|
||||
@ -584,6 +599,16 @@ class Level implements ChunkManager, Metadatable{
|
||||
return $this->chunkLoaders[Level::chunkHash($chunkX, $chunkZ)] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of players who have the target position within their view distance.
|
||||
* @param Vector3 $pos
|
||||
*
|
||||
* @return Player[]
|
||||
*/
|
||||
public function getViewersForPosition(Vector3 $pos) : array{
|
||||
return $this->getChunkPlayers($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a DataPacket to be sent to all players using the chunk at the specified X/Z coordinates at the end of the
|
||||
* current tick.
|
||||
@ -601,7 +626,27 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a DataPacket to be sent to everyone in the Level at the end of the current tick.
|
||||
* Broadcasts a packet to every player who has the target position within their view distance.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
* @param DataPacket $packet
|
||||
*/
|
||||
public function broadcastPacketToViewers(Vector3 $pos, DataPacket $packet) : void{
|
||||
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to every player in the level.
|
||||
*
|
||||
* @param DataPacket $packet
|
||||
*/
|
||||
public function broadcastGlobalPacket(DataPacket $packet) : void{
|
||||
$this->globalPackets[] = $packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see Level::broadcastGlobalPacket()
|
||||
*
|
||||
* @param DataPacket $packet
|
||||
*/
|
||||
@ -693,7 +738,16 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
$this->timings->doTick->startTiming();
|
||||
$this->doingTick = true;
|
||||
try{
|
||||
$this->actuallyDoTick($currentTick);
|
||||
}finally{
|
||||
$this->doingTick = false;
|
||||
$this->timings->doTick->stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
protected function actuallyDoTick(int $currentTick) : void{
|
||||
$this->checkTime();
|
||||
|
||||
$this->sunAnglePercentage = $this->computeSunAnglePercentage(); //Sun angle depends on the current time
|
||||
@ -728,7 +782,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$block = $this->getBlockAt($x, $y, $z);
|
||||
$block->clearCaches(); //for blocks like fences, force recalculation of connected AABBs
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block));
|
||||
$ev = new BlockUpdateEvent($block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$block->onNearbyBlockChange();
|
||||
}
|
||||
@ -765,6 +820,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->tickChunks();
|
||||
$this->timings->doTickTiles->stopTiming();
|
||||
|
||||
$this->executeQueuedLightUpdates();
|
||||
|
||||
if(count($this->changedBlocks) > 0){
|
||||
if(count($this->players) > 0){
|
||||
foreach($this->changedBlocks as $index => $blocks){
|
||||
@ -812,8 +869,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
$this->chunkPackets = [];
|
||||
|
||||
$this->timings->doTick->stopTiming();
|
||||
}
|
||||
|
||||
public function checkSleep(){
|
||||
@ -1038,7 +1093,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new LevelSaveEvent($this));
|
||||
(new LevelSaveEvent($this))->call();
|
||||
|
||||
$this->provider->setTime($this->time);
|
||||
$this->saveChunks();
|
||||
@ -1419,22 +1474,21 @@ class Level implements ChunkManager, Metadatable{
|
||||
$newHeightMap = $oldHeightMap;
|
||||
}
|
||||
|
||||
$update = new SkyLightUpdate($this);
|
||||
|
||||
if($this->skyLightUpdate === null){
|
||||
$this->skyLightUpdate = new SkyLightUpdate($this);
|
||||
}
|
||||
if($newHeightMap > $oldHeightMap){ //Heightmap increase, block placed, remove sky light
|
||||
for($i = $y; $i >= $oldHeightMap; --$i){
|
||||
$update->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest.
|
||||
$this->skyLightUpdate->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest.
|
||||
}
|
||||
}elseif($newHeightMap < $oldHeightMap){ //Heightmap decrease, block changed or removed, add sky light
|
||||
for($i = $y; $i >= $newHeightMap; --$i){
|
||||
$update->setAndUpdateLight($x, $i, $z, 15);
|
||||
$this->skyLightUpdate->setAndUpdateLight($x, $i, $z, 15);
|
||||
}
|
||||
}else{ //No heightmap change, block changed "underground"
|
||||
$update->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockSkyLight($x, $y, $z) - BlockFactory::$lightFilter[$sourceId]));
|
||||
$this->skyLightUpdate->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockSkyLight($x, $y, $z) - BlockFactory::$lightFilter[$sourceId]));
|
||||
}
|
||||
|
||||
$update->execute();
|
||||
|
||||
$this->timings->doBlockSkyLightUpdates->stopTiming();
|
||||
}
|
||||
|
||||
@ -1464,13 +1518,30 @@ class Level implements ChunkManager, Metadatable{
|
||||
$id = $this->getBlockIdAt($x, $y, $z);
|
||||
$newLevel = max(BlockFactory::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - BlockFactory::$lightFilter[$id]);
|
||||
|
||||
$update = new BlockLightUpdate($this);
|
||||
$update->setAndUpdateLight($x, $y, $z, $newLevel);
|
||||
$update->execute();
|
||||
if($this->blockLightUpdate === null){
|
||||
$this->blockLightUpdate = new BlockLightUpdate($this);
|
||||
}
|
||||
$this->blockLightUpdate->setAndUpdateLight($x, $y, $z, $newLevel);
|
||||
|
||||
$this->timings->doBlockLightUpdates->stopTiming();
|
||||
}
|
||||
|
||||
public function executeQueuedLightUpdates() : void{
|
||||
if($this->blockLightUpdate !== null){
|
||||
$this->timings->doBlockLightUpdates->startTiming();
|
||||
$this->blockLightUpdate->execute();
|
||||
$this->blockLightUpdate = null;
|
||||
$this->timings->doBlockLightUpdates->stopTiming();
|
||||
}
|
||||
|
||||
if($this->skyLightUpdate !== null){
|
||||
$this->timings->doBlockSkyLightUpdates->startTiming();
|
||||
$this->skyLightUpdate->execute();
|
||||
$this->skyLightUpdate = null;
|
||||
$this->timings->doBlockSkyLightUpdates->stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets on Vector3 the data from a Block object,
|
||||
* does block updates and puts the changes to the send queue.
|
||||
@ -1497,7 +1568,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$this->timings->setBlock->startTiming();
|
||||
|
||||
if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
|
||||
if($this->getChunkAtPosition($pos, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
|
||||
if(!($pos instanceof Position)){
|
||||
$pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
|
||||
}
|
||||
@ -1530,7 +1601,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
if($update){
|
||||
$this->updateAllLight($block);
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block));
|
||||
$ev = new BlockUpdateEvent($block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
foreach($this->getNearbyEntities(new AxisAlignedBB($block->x - 1, $block->y - 1, $block->z - 1, $block->x + 2, $block->y + 2, $block->z + 2)) as $entity){
|
||||
$entity->onNearbyBlockChange();
|
||||
@ -1693,7 +1765,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$ev->setCancelled(!$canBreak);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1783,7 +1855,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$ev->setCancelled(); //set it to cancelled so plugins can bypass this
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
if(!$player->isSneaking() and $blockClicked->onActivate($item, $player)){
|
||||
return true;
|
||||
@ -1855,7 +1927,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$ev->setCancelled(!$canPlace);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
@ -1866,7 +1938,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();
|
||||
@ -2108,6 +2180,9 @@ class Level implements ChunkManager, Metadatable{
|
||||
* @param int $id 0-255
|
||||
*/
|
||||
public function setBlockIdAt(int $x, int $y, int $z, int $id){
|
||||
if(!$this->isInWorld($x, $y, $z)){ //TODO: bad hack but fixing this requires BC breaks to do properly :(
|
||||
return;
|
||||
}
|
||||
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
|
||||
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
|
||||
|
||||
@ -2142,6 +2217,9 @@ class Level implements ChunkManager, Metadatable{
|
||||
* @param int $data 0-15
|
||||
*/
|
||||
public function setBlockDataAt(int $x, int $y, int $z, int $data){
|
||||
if(!$this->isInWorld($x, $y, $z)){ //TODO: bad hack but fixing this requires BC breaks to do properly :(
|
||||
return;
|
||||
}
|
||||
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
|
||||
|
||||
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
|
||||
@ -2280,6 +2358,18 @@ class Level implements ChunkManager, Metadatable{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chunk containing the given Vector3 position.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
* @param bool $create
|
||||
*
|
||||
* @return null|Chunk
|
||||
*/
|
||||
public function getChunkAtPosition(Vector3 $pos, bool $create = false) : ?Chunk{
|
||||
return $this->getChunk($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $create);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chunks adjacent to the specified chunk.
|
||||
*
|
||||
@ -2317,7 +2407,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$oldChunk = $this->getChunk($x, $z, false);
|
||||
$this->setChunk($x, $z, $chunk, false);
|
||||
if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){
|
||||
$this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($this, $chunk));
|
||||
(new ChunkPopulateEvent($this, $chunk))->call();
|
||||
|
||||
foreach($this->getChunkLoaders($x, $z) as $loader){
|
||||
$loader->onChunkPopulated($chunk);
|
||||
@ -2351,7 +2441,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{
|
||||
@ -2373,6 +2463,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)){
|
||||
@ -2396,6 +2490,17 @@ class Level implements ChunkManager, Metadatable{
|
||||
return $this->getChunk($x >> 4, $z >> 4, true)->getHighestBlockAt($x & 0x0f, $z & 0x0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given position is in a loaded area of terrain.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInLoadedTerrain(Vector3 $pos) : bool{
|
||||
return $this->isChunkLoaded($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $z
|
||||
@ -2445,7 +2550,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
public function setSpawnLocation(Vector3 $pos){
|
||||
$previousSpawn = $this->getSpawnLocation();
|
||||
$this->provider->setSpawn($pos);
|
||||
$this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn));
|
||||
(new SpawnChangeEvent($this, $previousSpawn))->call();
|
||||
}
|
||||
|
||||
public function requestChunk(int $x, int $z, Player $player){
|
||||
@ -2667,7 +2772,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$chunk->initChunk($this);
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated()));
|
||||
(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated()))->call();
|
||||
|
||||
if(!$chunk->isLightPopulated() and $chunk->isPopulated() and $this->getServer()->getProperty("chunk-ticking.light-updates", false)){
|
||||
$this->getServer()->getAsyncPool()->submitTask(new LightPopulationTask($this, $chunk));
|
||||
@ -2721,7 +2826,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$chunk = $this->chunks[$chunkHash] ?? null;
|
||||
|
||||
if($chunk !== null){
|
||||
$this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($this, $chunk));
|
||||
$ev = new ChunkUnloadEvent($this, $chunk);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->timings->doChunkUnload->stopTiming();
|
||||
|
||||
@ -2788,7 +2894,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$max = $this->worldHeight;
|
||||
$v = $spawn->floor();
|
||||
$chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false);
|
||||
$chunk = $this->getChunkAtPosition($v, false);
|
||||
$x = (int) $v->x;
|
||||
$z = (int) $v->z;
|
||||
if($chunk !== null and $chunk->isGenerated()){
|
||||
@ -2979,6 +3085,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->unloadChunkRequest($X, $Z, true);
|
||||
}
|
||||
}
|
||||
$chunk->collectGarbage();
|
||||
}
|
||||
|
||||
$this->provider->doGarbageCollection();
|
||||
|
@ -836,18 +836,17 @@ class Chunk{
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of empty subchunks
|
||||
* Disposes of empty subchunks and frees data where possible
|
||||
*/
|
||||
public function pruneEmptySubChunks(){
|
||||
public function collectGarbage() : void{
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
}elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
|
||||
$this->subChunks[$y] = $this->emptySubChunk;
|
||||
}else{
|
||||
continue; //do not set changed
|
||||
if($subChunk instanceof SubChunk){
|
||||
if($subChunk->isEmpty()){
|
||||
$this->subChunks[$y] = $this->emptySubChunk;
|
||||
}else{
|
||||
$subChunk->collectGarbage();
|
||||
}
|
||||
}
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -887,20 +886,31 @@ class Chunk{
|
||||
$stream = new BinaryStream();
|
||||
$stream->putInt($this->x);
|
||||
$stream->putInt($this->z);
|
||||
$count = 0;
|
||||
$subChunks = "";
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
$stream->putByte(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0));
|
||||
if($this->terrainGenerated){
|
||||
//subchunks
|
||||
$count = 0;
|
||||
$subChunks = "";
|
||||
foreach($this->subChunks as $y => $subChunk){
|
||||
if($subChunk instanceof EmptySubChunk){
|
||||
continue;
|
||||
}
|
||||
++$count;
|
||||
$subChunks .= chr($y) . $subChunk->getBlockIdArray() . $subChunk->getBlockDataArray();
|
||||
if($this->lightPopulated){
|
||||
$subChunks .= $subChunk->getBlockSkyLightArray() . $subChunk->getBlockLightArray();
|
||||
}
|
||||
}
|
||||
$stream->putByte($count);
|
||||
$stream->put($subChunks);
|
||||
|
||||
//biomes
|
||||
$stream->put($this->biomeIds);
|
||||
if($this->lightPopulated){
|
||||
$stream->put(pack("v*", ...$this->heightMap));
|
||||
}
|
||||
++$count;
|
||||
$subChunks .= chr($y) . $subChunk->fastSerialize();
|
||||
}
|
||||
$stream->putByte($count);
|
||||
$stream->put($subChunks);
|
||||
$stream->put(pack("v*", ...$this->heightMap) .
|
||||
$this->biomeIds .
|
||||
chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0)));
|
||||
|
||||
return $stream->getBuffer();
|
||||
}
|
||||
|
||||
@ -916,19 +926,36 @@ class Chunk{
|
||||
|
||||
$x = $stream->getInt();
|
||||
$z = $stream->getInt();
|
||||
$flags = $stream->getByte();
|
||||
$lightPopulated = (bool) ($flags & 4);
|
||||
$terrainPopulated = (bool) ($flags & 2);
|
||||
$terrainGenerated = (bool) ($flags & 1);
|
||||
|
||||
$subChunks = [];
|
||||
$count = $stream->getByte();
|
||||
for($y = 0; $y < $count; ++$y){
|
||||
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
|
||||
$biomeIds = "";
|
||||
$heightMap = [];
|
||||
if($terrainGenerated){
|
||||
$count = $stream->getByte();
|
||||
for($y = 0; $y < $count; ++$y){
|
||||
$subChunks[$stream->getByte()] = new SubChunk(
|
||||
$stream->get(4096), //blockids
|
||||
$stream->get(2048), //blockdata
|
||||
$lightPopulated ? $stream->get(2048) : "", //skylight
|
||||
$lightPopulated ? $stream->get(2048) : "" //blocklight
|
||||
);
|
||||
}
|
||||
|
||||
$biomeIds = $stream->get(256);
|
||||
if($lightPopulated){
|
||||
$heightMap = array_values(unpack("v*", $stream->get(512)));
|
||||
}
|
||||
}
|
||||
$heightMap = array_values(unpack("v*", $stream->get(512)));
|
||||
$biomeIds = $stream->get(256);
|
||||
|
||||
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
|
||||
$flags = $stream->getByte();
|
||||
$chunk->lightPopulated = (bool) ($flags & 4);
|
||||
$chunk->terrainPopulated = (bool) ($flags & 2);
|
||||
$chunk->terrainGenerated = (bool) ($flags & 1);
|
||||
$chunk->setGenerated($terrainGenerated);
|
||||
$chunk->setPopulated($terrainPopulated);
|
||||
$chunk->setLightPopulated($lightPopulated);
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
}
|
||||
|
@ -126,8 +126,4 @@ class EmptySubChunk implements SubChunkInterface{
|
||||
public function networkSerialize() : string{
|
||||
return "\x00" . str_repeat("\x00", 6144);
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
throw new \BadMethodCallException("Should not try to serialize empty subchunks");
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\level\format;
|
||||
|
||||
class SubChunk implements SubChunkInterface{
|
||||
if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
|
||||
define(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY', str_repeat("\x00", 2048));
|
||||
}
|
||||
|
||||
class SubChunk implements SubChunkInterface{
|
||||
protected $ids;
|
||||
protected $data;
|
||||
protected $blockLight;
|
||||
@ -44,6 +47,7 @@ class SubChunk implements SubChunkInterface{
|
||||
self::assignData($this->data, $data, 2048);
|
||||
self::assignData($this->skyLight, $skyLight, 2048, "\xff");
|
||||
self::assignData($this->blockLight, $blockLight, 2048);
|
||||
$this->collectGarbage();
|
||||
}
|
||||
|
||||
public function isEmpty(bool $checkLight = true) : bool{
|
||||
@ -51,7 +55,7 @@ class SubChunk implements SubChunkInterface{
|
||||
substr_count($this->ids, "\x00") === 4096 and
|
||||
(!$checkLight or (
|
||||
substr_count($this->skyLight, "\xff") === 2048 and
|
||||
substr_count($this->blockLight, "\x00") === 2048
|
||||
$this->blockLight === ZERO_NIBBLE_ARRAY
|
||||
))
|
||||
);
|
||||
}
|
||||
@ -66,31 +70,22 @@ class SubChunk implements SubChunkInterface{
|
||||
}
|
||||
|
||||
public function getBlockData(int $x, int $y, int $z) : int{
|
||||
$m = ord($this->data{($x << 7) + ($z << 3) + ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $m & 0x0f;
|
||||
}else{
|
||||
return $m >> 4;
|
||||
}
|
||||
return (ord($this->data{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
|
||||
}
|
||||
|
||||
public function setBlockData(int $x, int $y, int $z, int $data) : bool{
|
||||
$i = ($x << 7) | ($z << 3) | ($y >> 1);
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr((ord($this->data{$i}) & 0xf0) | ($data & 0x0f));
|
||||
}else{
|
||||
$this->data{$i} = chr((($data & 0x0f) << 4) | (ord($this->data{$i}) & 0x0f));
|
||||
}
|
||||
|
||||
$shift = ($y & 1) << 2;
|
||||
$byte = ord($this->data{$i});
|
||||
$this->data{$i} = chr(($byte & ~(0xf << $shift)) | (($data & 0xf) << $shift));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFullBlock(int $x, int $y, int $z) : int{
|
||||
$i = ($x << 8) | ($z << 4) | $y;
|
||||
if(($y & 1) === 0){
|
||||
return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0f);
|
||||
}else{
|
||||
return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4);
|
||||
}
|
||||
return (ord($this->ids{$i}) << 4) | ((ord($this->data{$i >> 1}) >> (($y & 1) << 2)) & 0xf);
|
||||
}
|
||||
|
||||
public function setBlock(int $x, int $y, int $z, ?int $id = null, ?int $data = null) : bool{
|
||||
@ -106,13 +101,12 @@ class SubChunk implements SubChunkInterface{
|
||||
|
||||
if($data !== null){
|
||||
$i >>= 1;
|
||||
$byte = ord($this->data{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f));
|
||||
}else{
|
||||
$this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f));
|
||||
}
|
||||
if($this->data{$i} !== $byte){
|
||||
|
||||
$shift = ($y & 1) << 2;
|
||||
$oldPair = ord($this->data{$i});
|
||||
$newPair = ($oldPair & ~(0xf << $shift)) | (($data & 0xf) << $shift);
|
||||
if($newPair !== $oldPair){
|
||||
$this->data{$i} = chr($newPair);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
@ -121,42 +115,30 @@ class SubChunk implements SubChunkInterface{
|
||||
}
|
||||
|
||||
public function getBlockLight(int $x, int $y, int $z) : int{
|
||||
$byte = ord($this->blockLight{($x << 7) + ($z << 3) + ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $byte & 0x0f;
|
||||
}else{
|
||||
return $byte >> 4;
|
||||
}
|
||||
return (ord($this->blockLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
|
||||
}
|
||||
|
||||
public function setBlockLight(int $x, int $y, int $z, int $level) : bool{
|
||||
$i = ($x << 7) + ($z << 3) + ($y >> 1);
|
||||
$i = ($x << 7) | ($z << 3) | ($y >> 1);
|
||||
|
||||
$shift = ($y & 1) << 2;
|
||||
$byte = ord($this->blockLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->blockLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f));
|
||||
}else{
|
||||
$this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f));
|
||||
}
|
||||
$this->blockLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getBlockSkyLight(int $x, int $y, int $z) : int{
|
||||
$byte = ord($this->skyLight{($x << 7) + ($z << 3) + ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $byte & 0x0f;
|
||||
}else{
|
||||
return $byte >> 4;
|
||||
}
|
||||
return (ord($this->skyLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
|
||||
}
|
||||
|
||||
public function setBlockSkyLight(int $x, int $y, int $z, int $level) : bool{
|
||||
$i = ($x << 7) + ($z << 3) + ($y >> 1);
|
||||
$i = ($x << 7) | ($z << 3) | ($y >> 1);
|
||||
|
||||
$shift = ($y & 1) << 2;
|
||||
$byte = ord($this->skyLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->skyLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f));
|
||||
}else{
|
||||
$this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f));
|
||||
}
|
||||
$this->skyLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -222,24 +204,24 @@ class SubChunk implements SubChunkInterface{
|
||||
return "\x00" . $this->ids . $this->data;
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
return
|
||||
$this->ids .
|
||||
$this->data .
|
||||
$this->skyLight .
|
||||
$this->blockLight;
|
||||
}
|
||||
|
||||
public static function fastDeserialize(string $data) : SubChunk{
|
||||
return new SubChunk(
|
||||
substr($data, 0, 4096), //ids
|
||||
substr($data, 4096, 2048), //data
|
||||
substr($data, 6144, 2048), //sky light
|
||||
substr($data, 8192, 2048) //block light
|
||||
);
|
||||
}
|
||||
|
||||
public function __debugInfo(){
|
||||
return [];
|
||||
}
|
||||
|
||||
public function collectGarbage() : void{
|
||||
/*
|
||||
* This strange looking code is designed to exploit PHP's copy-on-write behaviour. Assigning will copy a
|
||||
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
|
||||
* modified, which is perfect for this purpose.
|
||||
*/
|
||||
if($this->data === ZERO_NIBBLE_ARRAY){
|
||||
$this->data = ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
if($this->skyLight === ZERO_NIBBLE_ARRAY){
|
||||
$this->skyLight = ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
if($this->blockLight === ZERO_NIBBLE_ARRAY){
|
||||
$this->blockLight = ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,9 +202,4 @@ interface SubChunkInterface{
|
||||
* @return string
|
||||
*/
|
||||
public function networkSerialize() : string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function fastSerialize() : string;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\tile\Spawnable;
|
||||
|
||||
class ChunkRequestTask extends AsyncTask{
|
||||
|
||||
@ -39,43 +38,29 @@ class ChunkRequestTask extends AsyncTask{
|
||||
protected $chunkX;
|
||||
protected $chunkZ;
|
||||
|
||||
protected $tiles;
|
||||
|
||||
protected $compressionLevel;
|
||||
|
||||
public function __construct(Level $level, int $chunkX, int $chunkZ, Chunk $chunk){
|
||||
$this->levelId = $level->getId();
|
||||
$this->compressionLevel = $level->getServer()->networkCompressionLevel;
|
||||
|
||||
$this->chunk = $chunk->fastSerialize();
|
||||
$this->chunk = $chunk->networkSerialize();
|
||||
$this->chunkX = $chunkX;
|
||||
$this->chunkZ = $chunkZ;
|
||||
|
||||
//TODO: serialize tiles with chunks
|
||||
$tiles = "";
|
||||
foreach($chunk->getTiles() as $tile){
|
||||
if($tile instanceof Spawnable){
|
||||
$tiles .= $tile->getSerializedSpawnCompound();
|
||||
}
|
||||
}
|
||||
|
||||
$this->tiles = $tiles;
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
$chunk = Chunk::fastDeserialize($this->chunk);
|
||||
|
||||
$pk = new FullChunkDataPacket();
|
||||
$pk->chunkX = $this->chunkX;
|
||||
$pk->chunkZ = $this->chunkZ;
|
||||
$pk->data = $chunk->networkSerialize() . $this->tiles;
|
||||
$pk->data = $this->chunk;
|
||||
|
||||
$batch = new BatchPacket();
|
||||
$batch->addPacket($pk);
|
||||
$batch->setCompressionLevel($this->compressionLevel);
|
||||
$batch->encode();
|
||||
|
||||
$this->setResult($batch->buffer, false);
|
||||
$this->setResult($batch->buffer);
|
||||
}
|
||||
|
||||
public function onCompletion(Server $server){
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
];
|
||||
|
||||
|
@ -34,6 +34,9 @@ abstract class LightUpdate{
|
||||
/** @var ChunkManager */
|
||||
protected $level;
|
||||
|
||||
/** @var int[] blockhash => new light level */
|
||||
protected $updateNodes = [];
|
||||
|
||||
/** @var \SplQueue */
|
||||
protected $spreadQueue;
|
||||
/** @var bool[] */
|
||||
@ -59,31 +62,31 @@ abstract class LightUpdate{
|
||||
abstract protected function setLight(int $x, int $y, int $z, int $level);
|
||||
|
||||
public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){
|
||||
if(!$this->level->isInWorld($x, $y, $z)){
|
||||
throw new \InvalidArgumentException("Coordinates x=$x, y=$y, z=$z are out of range");
|
||||
}
|
||||
$this->updateNodes[Level::blockHash($x, $y, $z)] = [$x, $y, $z, $newLevel];
|
||||
}
|
||||
|
||||
if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){
|
||||
throw new \InvalidArgumentException("Already have a visit ready for this block");
|
||||
}
|
||||
private function prepareNodes() : void{
|
||||
foreach($this->updateNodes as $blockHash => [$x, $y, $z, $newLevel]){
|
||||
if($this->subChunkHandler->moveTo($x, $y, $z)){
|
||||
$oldLevel = $this->getLight($x, $y, $z);
|
||||
|
||||
if($this->subChunkHandler->moveTo($x, $y, $z)){
|
||||
$oldLevel = $this->getLight($x, $y, $z);
|
||||
|
||||
if($oldLevel !== $newLevel){
|
||||
$this->setLight($x, $y, $z, $newLevel);
|
||||
if($oldLevel < $newLevel){ //light increased
|
||||
$this->spreadVisited[$index] = true;
|
||||
$this->spreadQueue->enqueue([$x, $y, $z]);
|
||||
}else{ //light removed
|
||||
$this->removalVisited[$index] = true;
|
||||
$this->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
|
||||
if($oldLevel !== $newLevel){
|
||||
$this->setLight($x, $y, $z, $newLevel);
|
||||
if($oldLevel < $newLevel){ //light increased
|
||||
$this->spreadVisited[$blockHash] = true;
|
||||
$this->spreadQueue->enqueue([$x, $y, $z]);
|
||||
}else{ //light removed
|
||||
$this->removalVisited[$blockHash] = true;
|
||||
$this->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(){
|
||||
$this->prepareNodes();
|
||||
|
||||
while(!$this->removalQueue->isEmpty()){
|
||||
list($x, $y, $z, $oldAdjacentLight) = $this->removalQueue->dequeue();
|
||||
|
||||
|
@ -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[]
|
||||
|
@ -35,55 +35,26 @@ class BlockMetadataStore extends MetadataStore{
|
||||
$this->owningLevel = $owningLevel;
|
||||
}
|
||||
|
||||
public function disambiguate(Metadatable $block, string $metadataKey) : string{
|
||||
if(!($block instanceof Block)){
|
||||
throw new \InvalidArgumentException("Argument must be a Block instance");
|
||||
private function disambiguate(Block $block, string $metadataKey) : string{
|
||||
if($block->getLevel() !== $this->owningLevel){
|
||||
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
|
||||
}
|
||||
|
||||
return $block->x . ":" . $block->y . ":" . $block->z . ":" . $metadataKey;
|
||||
}
|
||||
|
||||
public function getMetadata(Metadatable $subject, string $metadataKey){
|
||||
if(!($subject instanceof Block)){
|
||||
throw new \InvalidArgumentException("Object must be a Block");
|
||||
}
|
||||
if($subject->getLevel() === $this->owningLevel){
|
||||
return parent::getMetadata($subject, $metadataKey);
|
||||
}else{
|
||||
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
|
||||
}
|
||||
public function getMetadata(Block $subject, string $metadataKey){
|
||||
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function hasMetadata(Metadatable $subject, string $metadataKey) : bool{
|
||||
if(!($subject instanceof Block)){
|
||||
throw new \InvalidArgumentException("Object must be a Block");
|
||||
}
|
||||
if($subject->getLevel() === $this->owningLevel){
|
||||
return parent::hasMetadata($subject, $metadataKey);
|
||||
}else{
|
||||
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
|
||||
}
|
||||
public function hasMetadata(Block $subject, string $metadataKey) : bool{
|
||||
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function removeMetadata(Metadatable $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
if(!($subject instanceof Block)){
|
||||
throw new \InvalidArgumentException("Object must be a Block");
|
||||
}
|
||||
if($subject->getLevel() === $this->owningLevel){
|
||||
parent::removeMetadata($subject, $metadataKey, $owningPlugin);
|
||||
}else{
|
||||
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
|
||||
}
|
||||
public function removeMetadata(Block $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
|
||||
}
|
||||
|
||||
public function setMetadata(Metadatable $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
if(!($subject instanceof Block)){
|
||||
throw new \InvalidArgumentException("Object must be a Block");
|
||||
}
|
||||
if($subject->getLevel() === $this->owningLevel){
|
||||
parent::setMetadata($subject, $metadataKey, $newMetadataValue);
|
||||
}else{
|
||||
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
|
||||
}
|
||||
public function setMetadata(Block $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,27 @@ declare(strict_types=1);
|
||||
namespace pocketmine\metadata;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\plugin\Plugin;
|
||||
|
||||
class EntityMetadataStore extends MetadataStore{
|
||||
|
||||
public function disambiguate(Metadatable $entity, string $metadataKey) : string{
|
||||
if(!($entity instanceof Entity)){
|
||||
throw new \InvalidArgumentException("Argument must be an Entity instance");
|
||||
}
|
||||
|
||||
private function disambiguate(Entity $entity, string $metadataKey) : string{
|
||||
return $entity->getId() . ":" . $metadataKey;
|
||||
}
|
||||
|
||||
public function getMetadata(Entity $subject, string $metadataKey){
|
||||
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function hasMetadata(Entity $subject, string $metadataKey) : bool{
|
||||
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function removeMetadata(Entity $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
|
||||
}
|
||||
|
||||
public function setMetadata(Entity $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,27 @@ declare(strict_types=1);
|
||||
namespace pocketmine\metadata;
|
||||
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\plugin\Plugin;
|
||||
|
||||
class LevelMetadataStore extends MetadataStore{
|
||||
|
||||
public function disambiguate(Metadatable $level, string $metadataKey) : string{
|
||||
if(!($level instanceof Level)){
|
||||
throw new \InvalidArgumentException("Argument must be a Level instance");
|
||||
}
|
||||
|
||||
private function disambiguate(Level $level, string $metadataKey) : string{
|
||||
return strtolower($level->getName()) . ":" . $metadataKey;
|
||||
}
|
||||
|
||||
public function getMetadata(Level $subject, string $metadataKey){
|
||||
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function hasMetadata(Level $subject, string $metadataKey) : bool{
|
||||
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function removeMetadata(Level $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
|
||||
}
|
||||
|
||||
public function setMetadata(Level $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\metadata;
|
||||
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginException;
|
||||
|
||||
abstract class MetadataStore{
|
||||
/** @var \SplObjectStorage[] */
|
||||
@ -36,17 +35,12 @@ abstract class MetadataStore{
|
||||
/**
|
||||
* Adds a metadata value to an object.
|
||||
*
|
||||
* @param Metadatable $subject
|
||||
* @param string $metadataKey
|
||||
* @param string $key
|
||||
* @param MetadataValue $newMetadataValue
|
||||
*/
|
||||
public function setMetadata(Metadatable $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
protected function setMetadataInternal(string $key, MetadataValue $newMetadataValue){
|
||||
$owningPlugin = $newMetadataValue->getOwningPlugin();
|
||||
if($owningPlugin === null){
|
||||
throw new PluginException("Plugin cannot be null");
|
||||
}
|
||||
|
||||
$key = $this->disambiguate($subject, $metadataKey);
|
||||
if(!isset($this->metadataMap[$key])){
|
||||
$entry = new \SplObjectStorage();
|
||||
$this->metadataMap[$key] = $entry;
|
||||
@ -60,13 +54,11 @@ abstract class MetadataStore{
|
||||
* Returns all metadata values attached to an object. If multiple
|
||||
* have attached metadata, each will value will be included.
|
||||
*
|
||||
* @param Metadatable $subject
|
||||
* @param string $metadataKey
|
||||
* @param string $key
|
||||
*
|
||||
* @return MetadataValue[]
|
||||
*/
|
||||
public function getMetadata(Metadatable $subject, string $metadataKey){
|
||||
$key = $this->disambiguate($subject, $metadataKey);
|
||||
protected function getMetadataInternal(string $key){
|
||||
if(isset($this->metadataMap[$key])){
|
||||
return $this->metadataMap[$key];
|
||||
}else{
|
||||
@ -77,24 +69,21 @@ abstract class MetadataStore{
|
||||
/**
|
||||
* Tests to see if a metadata attribute has been set on an object.
|
||||
*
|
||||
* @param Metadatable $subject
|
||||
* @param string $metadataKey
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMetadata(Metadatable $subject, string $metadataKey) : bool{
|
||||
return isset($this->metadataMap[$this->disambiguate($subject, $metadataKey)]);
|
||||
protected function hasMetadataInternal(string $key) : bool{
|
||||
return isset($this->metadataMap[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a metadata item owned by a plugin from a subject.
|
||||
*
|
||||
* @param Metadatable $subject
|
||||
* @param string $metadataKey
|
||||
* @param Plugin $owningPlugin
|
||||
* @param string $key
|
||||
* @param Plugin $owningPlugin
|
||||
*/
|
||||
public function removeMetadata(Metadatable $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
$key = $this->disambiguate($subject, $metadataKey);
|
||||
protected function removeMetadataInternal(string $key, Plugin $owningPlugin){
|
||||
if(isset($this->metadataMap[$key])){
|
||||
unset($this->metadataMap[$key][$owningPlugin]);
|
||||
if($this->metadataMap[$key]->count() === 0){
|
||||
@ -118,17 +107,4 @@ abstract class MetadataStore{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique name for the object receiving metadata by combining
|
||||
* unique data from the subject with a metadataKey.
|
||||
*
|
||||
* @param Metadatable $subject
|
||||
* @param string $metadataKey
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
abstract public function disambiguate(Metadatable $subject, string $metadataKey) : string;
|
||||
}
|
||||
|
@ -24,14 +24,27 @@ declare(strict_types=1);
|
||||
namespace pocketmine\metadata;
|
||||
|
||||
use pocketmine\IPlayer;
|
||||
use pocketmine\plugin\Plugin;
|
||||
|
||||
class PlayerMetadataStore extends MetadataStore{
|
||||
|
||||
public function disambiguate(Metadatable $player, string $metadataKey) : string{
|
||||
if(!($player instanceof IPlayer)){
|
||||
throw new \InvalidArgumentException("Argument must be an IPlayer instance");
|
||||
}
|
||||
|
||||
private function disambiguate(IPlayer $player, string $metadataKey) : string{
|
||||
return strtolower($player->getName()) . ":" . $metadataKey;
|
||||
}
|
||||
|
||||
public function getMetadata(IPlayer $subject, string $metadataKey){
|
||||
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function hasMetadata(IPlayer $subject, string $metadataKey) : bool{
|
||||
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
|
||||
}
|
||||
|
||||
public function removeMetadata(IPlayer $subject, string $metadataKey, Plugin $owningPlugin){
|
||||
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
|
||||
}
|
||||
|
||||
public function setMetadata(IPlayer $subject, string $metadataKey, MetadataValue $newMetadataValue){
|
||||
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class CompressBatchedTask extends AsyncTask{
|
||||
$batch->setCompressionLevel($this->level);
|
||||
$batch->encode();
|
||||
|
||||
$this->setResult($batch->buffer, false);
|
||||
$this->setResult($batch->buffer);
|
||||
}
|
||||
|
||||
public function onCompletion(Server $server){
|
||||
|
@ -98,7 +98,7 @@ class Network{
|
||||
$logger->logException($e);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new NetworkInterfaceCrashEvent($interface, $e));
|
||||
(new NetworkInterfaceCrashEvent($interface, $e))->call();
|
||||
|
||||
$interface->emergencyShutdown();
|
||||
$this->unregisterInterface($interface);
|
||||
@ -110,7 +110,8 @@ class Network{
|
||||
* @param SourceInterface $interface
|
||||
*/
|
||||
public function registerInterface(SourceInterface $interface){
|
||||
$this->server->getPluginManager()->callEvent($ev = new NetworkInterfaceRegisterEvent($interface));
|
||||
$ev = new NetworkInterfaceRegisterEvent($interface);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$interface->start();
|
||||
$this->interfaces[$hash = spl_object_hash($interface)] = $interface;
|
||||
@ -126,7 +127,7 @@ class Network{
|
||||
* @param SourceInterface $interface
|
||||
*/
|
||||
public function unregisterInterface(SourceInterface $interface){
|
||||
$this->server->getPluginManager()->callEvent(new NetworkInterfaceUnregisterEvent($interface));
|
||||
(new NetworkInterfaceUnregisterEvent($interface))->call();
|
||||
unset($this->interfaces[$hash = spl_object_hash($interface)], $this->advancedInterfaces[$hash]);
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,12 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->putVarInt($auxValue);
|
||||
|
||||
$nbt = $item->getCompoundTag();
|
||||
$this->putLShort(strlen($nbt));
|
||||
$nbtLen = strlen($nbt);
|
||||
if($nbtLen > 32767){
|
||||
throw new \InvalidArgumentException("NBT encoded length must be < 32768, got $nbtLen bytes");
|
||||
}
|
||||
|
||||
$this->putLShort($nbtLen);
|
||||
$this->put($nbt);
|
||||
|
||||
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
@ -92,7 +94,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
$this->server->getLogger()->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": 0x" . bin2hex($remains));
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this->player, $packet));
|
||||
$ev = new DataPacketReceiveEvent($this->player, $packet);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled() and !$packet->handle($this)){
|
||||
$this->server->getLogger()->debug("Unhandled " . $packet->getName() . " received from " . $this->player->getName() . ": 0x" . bin2hex($packet->buffer));
|
||||
}
|
||||
@ -124,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{
|
||||
@ -279,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);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
$this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
|
||||
$this->server->getNetwork()->processInterface($this);
|
||||
});
|
||||
$this->rakLib->start(PTHREADS_INHERIT_CONSTANTS | PTHREADS_INHERIT_INI); //HACK: MainLogger needs INI and constants
|
||||
$this->rakLib->start(PTHREADS_INHERIT_CONSTANTS); //HACK: MainLogger needs constants for exception logging
|
||||
}
|
||||
|
||||
public function setNetwork(Network $network){
|
||||
@ -137,7 +137,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
|
||||
public function openSession(string $identifier, string $address, int $port, int $clientID) : void{
|
||||
$ev = new PlayerCreationEvent($this, Player::class, Player::class, $address, $port);
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$ev->call();
|
||||
$class = $ev->getPlayerClass();
|
||||
|
||||
/**
|
||||
@ -154,17 +154,19 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
public function handleEncapsulated(string $identifier, EncapsulatedPacket $packet, int $flags) : void{
|
||||
if(isset($this->players[$identifier])){
|
||||
//get this now for blocking in case the player was closed before the exception was raised
|
||||
$address = $this->players[$identifier]->getAddress();
|
||||
$player = $this->players[$identifier];
|
||||
$address = $player->getAddress();
|
||||
try{
|
||||
if($packet->buffer !== ""){
|
||||
$pk = PacketPool::getPacket($packet->buffer);
|
||||
$this->players[$identifier]->handleDataPacket($pk);
|
||||
$player->handleDataPacket($pk);
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$logger = $this->server->getLogger();
|
||||
$logger->debug("Packet " . (isset($pk) ? get_class($pk) : "unknown") . " 0x" . bin2hex($packet->buffer));
|
||||
$logger->logException($e);
|
||||
|
||||
$player->close($player->getLeaveMessage(), "Internal server error");
|
||||
$this->interface->blockAddress($address, 5);
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\CachedEncapsulatedPacket;
|
||||
use pocketmine\network\mcpe\NetworkBinaryStream;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\utils\Utils;
|
||||
@ -35,6 +36,8 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
|
||||
/** @var bool */
|
||||
public $isEncoded = false;
|
||||
/** @var CachedEncapsulatedPacket */
|
||||
public $__encapsulatedPacket = null;
|
||||
|
||||
/** @var int */
|
||||
public $senderSubId = 0;
|
||||
@ -134,4 +137,12 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function __get($name){
|
||||
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
|
||||
}
|
||||
|
||||
public function __set($name, $value){
|
||||
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -96,7 +96,8 @@ class RCON{
|
||||
$response = new RemoteConsoleCommandSender();
|
||||
$command = $this->instance->cmd;
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new RemoteServerCommandEvent($response, $command));
|
||||
$ev = new RemoteServerCommandEvent($response, $command);
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
$this->server->dispatchCommand($ev->getSender(), $ev->getCommand());
|
||||
|
@ -68,7 +68,7 @@ class RCONInstance extends Thread{
|
||||
$this->ipcSocket = $ipcSocket;
|
||||
$this->notifier = $notifier;
|
||||
|
||||
$this->start(PTHREADS_INHERIT_INI); //HACK: need INI for timezone (logger)
|
||||
$this->start(PTHREADS_INHERIT_NONE);
|
||||
}
|
||||
|
||||
private function writePacket($client, int $requestID, int $packetType, string $payload){
|
||||
|
@ -42,6 +42,8 @@ class Permission{
|
||||
* @param bool|string $value
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function getByName($value) : string{
|
||||
if(is_bool($value)){
|
||||
@ -70,10 +72,11 @@ class Permission{
|
||||
|
||||
case "true":
|
||||
return self::DEFAULT_TRUE;
|
||||
|
||||
default:
|
||||
case "false":
|
||||
return self::DEFAULT_FALSE;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unknown permission default name \"$value\"");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,6 @@ use pocketmine\utils\Utils;
|
||||
* Manages all the plugins
|
||||
*/
|
||||
class PluginManager{
|
||||
private const MAX_EVENT_CALL_DEPTH = 50;
|
||||
|
||||
/** @var Server */
|
||||
private $server;
|
||||
@ -66,9 +65,6 @@ class PluginManager{
|
||||
*/
|
||||
protected $fileAssociations = [];
|
||||
|
||||
/** @var int */
|
||||
private $eventCallDepth = 0;
|
||||
|
||||
/** @var string|null */
|
||||
private $pluginDataDirectory;
|
||||
|
||||
@ -199,122 +195,140 @@ class PluginManager{
|
||||
* @return Plugin[]
|
||||
*/
|
||||
public function loadPlugins(string $directory, array $newLoaders = null){
|
||||
if(!is_dir($directory)){
|
||||
return [];
|
||||
}
|
||||
|
||||
if(is_dir($directory)){
|
||||
$plugins = [];
|
||||
$loadedPlugins = [];
|
||||
$dependencies = [];
|
||||
$softDependencies = [];
|
||||
if(is_array($newLoaders)){
|
||||
$loaders = [];
|
||||
foreach($newLoaders as $key){
|
||||
if(isset($this->fileAssociations[$key])){
|
||||
$loaders[$key] = $this->fileAssociations[$key];
|
||||
}
|
||||
$plugins = [];
|
||||
$loadedPlugins = [];
|
||||
$dependencies = [];
|
||||
$softDependencies = [];
|
||||
if(is_array($newLoaders)){
|
||||
$loaders = [];
|
||||
foreach($newLoaders as $key){
|
||||
if(isset($this->fileAssociations[$key])){
|
||||
$loaders[$key] = $this->fileAssociations[$key];
|
||||
}
|
||||
}else{
|
||||
$loaders = $this->fileAssociations;
|
||||
}
|
||||
foreach($loaders as $loader){
|
||||
foreach(new \DirectoryIterator($directory) as $file){
|
||||
if($file === "." or $file === ".."){
|
||||
}else{
|
||||
$loaders = $this->fileAssociations;
|
||||
}
|
||||
foreach($loaders as $loader){
|
||||
foreach(new \DirectoryIterator($directory) as $file){
|
||||
if($file === "." or $file === ".."){
|
||||
continue;
|
||||
}
|
||||
$file = $directory . $file;
|
||||
if(!$loader->canLoadPlugin($file)){
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$description = $loader->getPluginDescription($file);
|
||||
if($description === null){
|
||||
continue;
|
||||
}
|
||||
$file = $directory . $file;
|
||||
if(!$loader->canLoadPlugin($file)){
|
||||
|
||||
$name = $description->getName();
|
||||
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
|
||||
continue;
|
||||
}elseif(strpos($name, " ") !== false){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
|
||||
}
|
||||
|
||||
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$description = $loader->getPluginDescription($file);
|
||||
if($description instanceof PluginDescription){
|
||||
$name = $description->getName();
|
||||
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"]));
|
||||
continue;
|
||||
}elseif(strpos($name, " ") !== false){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name]));
|
||||
}
|
||||
|
||||
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name]));
|
||||
continue;
|
||||
}
|
||||
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$this->isCompatibleApi(...$description->getCompatibleApis())){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleAPI", [implode(", ", $description->getCompatibleApis())])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
|
||||
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
|
||||
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$plugins[$name] = $file;
|
||||
|
||||
$softDependencies[$name] = $description->getSoftDepend();
|
||||
$dependencies[$name] = $description->getDepend();
|
||||
|
||||
foreach($description->getLoadBefore() as $before){
|
||||
if(isset($softDependencies[$before])){
|
||||
$softDependencies[$before][] = $name;
|
||||
}else{
|
||||
$softDependencies[$before] = [$name];
|
||||
}
|
||||
}
|
||||
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
|
||||
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
|
||||
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.incompatibleProtocol", [implode(", ", $pluginMcpeProtocols)])
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
|
||||
$this->server->getLogger()->logException($e);
|
||||
}
|
||||
|
||||
$plugins[$name] = $file;
|
||||
|
||||
$softDependencies[$name] = $description->getSoftDepend();
|
||||
$dependencies[$name] = $description->getDepend();
|
||||
|
||||
foreach($description->getLoadBefore() as $before){
|
||||
if(isset($softDependencies[$before])){
|
||||
$softDependencies[$before][] = $name;
|
||||
}else{
|
||||
$softDependencies[$before] = [$name];
|
||||
}
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()]));
|
||||
$this->server->getLogger()->logException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while(count($plugins) > 0){
|
||||
$missingDependency = true;
|
||||
foreach($plugins as $name => $file){
|
||||
if(isset($dependencies[$name])){
|
||||
foreach($dependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($dependencies[$name][$key]);
|
||||
}elseif(!isset($plugins[$dependency])){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
|
||||
]));
|
||||
unset($plugins[$name]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(count($dependencies[$name]) === 0){
|
||||
unset($dependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($softDependencies[$name])){
|
||||
foreach($softDependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($softDependencies[$name][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($softDependencies[$name]) === 0){
|
||||
unset($softDependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
$loadedPlugins[$name] = $plugin;
|
||||
}else{
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while(count($plugins) > 0){
|
||||
$missingDependency = true;
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
if(isset($dependencies[$name])){
|
||||
foreach($dependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($dependencies[$name][$key]);
|
||||
}elseif(!isset($plugins[$dependency])){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [
|
||||
$name,
|
||||
$this->server->getLanguage()->translateString("%pocketmine.plugin.unknownDependency", [$dependency])
|
||||
]));
|
||||
unset($plugins[$name]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(count($dependencies[$name]) === 0){
|
||||
unset($dependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($softDependencies[$name])){
|
||||
foreach($softDependencies[$name] as $key => $dependency){
|
||||
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
|
||||
unset($softDependencies[$name][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($softDependencies[$name]) === 0){
|
||||
unset($softDependencies[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
|
||||
if(!isset($dependencies[$name])){
|
||||
unset($softDependencies[$name]);
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
@ -325,34 +339,17 @@ class PluginManager{
|
||||
}
|
||||
}
|
||||
|
||||
//No plugins loaded :(
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
if(!isset($dependencies[$name])){
|
||||
unset($softDependencies[$name]);
|
||||
unset($plugins[$name]);
|
||||
$missingDependency = false;
|
||||
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
|
||||
$loadedPlugins[$name] = $plugin;
|
||||
}else{
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//No plugins loaded :(
|
||||
if($missingDependency){
|
||||
foreach($plugins as $name => $file){
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
|
||||
}
|
||||
$plugins = [];
|
||||
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
|
||||
}
|
||||
$plugins = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $loadedPlugins;
|
||||
}else{
|
||||
return [];
|
||||
}
|
||||
|
||||
return $loadedPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -568,7 +565,7 @@ class PluginManager{
|
||||
|
||||
$this->enabledPlugins[$plugin->getDescription()->getName()] = $plugin;
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new PluginEnableEvent($plugin));
|
||||
(new PluginEnableEvent($plugin))->call();
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->logException($e);
|
||||
$this->disablePlugin($plugin);
|
||||
@ -645,7 +642,7 @@ class PluginManager{
|
||||
public function disablePlugin(Plugin $plugin){
|
||||
if($plugin->isEnabled()){
|
||||
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.disable", [$plugin->getDescription()->getFullName()]));
|
||||
$this->callEvent(new PluginDisableEvent($plugin));
|
||||
(new PluginDisableEvent($plugin))->call();
|
||||
|
||||
unset($this->enabledPlugins[$plugin->getDescription()->getName()]);
|
||||
|
||||
@ -679,44 +676,13 @@ class PluginManager{
|
||||
/**
|
||||
* Calls an event
|
||||
*
|
||||
* @deprecated
|
||||
* @see Event::call()
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function callEvent(Event $event){
|
||||
if($this->eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
|
||||
//this exception will be caught by the parent event call if all else fails
|
||||
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
|
||||
}
|
||||
|
||||
$handlerList = HandlerList::getHandlerListFor(get_class($event));
|
||||
assert($handlerList !== null, "Called event should have a valid HandlerList");
|
||||
|
||||
++$this->eventCallDepth;
|
||||
foreach(EventPriority::ALL as $priority){
|
||||
$currentList = $handlerList;
|
||||
while($currentList !== null){
|
||||
foreach($currentList->getListenersByPriority($priority) as $registration){
|
||||
if(!$registration->getPlugin()->isEnabled()){
|
||||
continue;
|
||||
}
|
||||
|
||||
try{
|
||||
$registration->callEvent($event);
|
||||
}catch(\Throwable $e){
|
||||
$this->server->getLogger()->critical(
|
||||
$this->server->getLanguage()->translateString("pocketmine.plugin.eventError", [
|
||||
$event->getEventName(),
|
||||
$registration->getPlugin()->getDescription()->getFullName(),
|
||||
$e->getMessage(),
|
||||
get_class($registration->getListener())
|
||||
]));
|
||||
$this->server->getLogger()->logException($e);
|
||||
}
|
||||
}
|
||||
|
||||
$currentList = $currentList->getParent();
|
||||
}
|
||||
}
|
||||
--$this->eventCallDepth;
|
||||
$event->call();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -740,10 +706,31 @@ class PluginManager{
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = $method->getParameters();
|
||||
if(count($parameters) !== 1){
|
||||
continue;
|
||||
}
|
||||
|
||||
$handlerClosure = $method->getClosure($listener);
|
||||
|
||||
try{
|
||||
$eventClass = $parameters[0]->getClass();
|
||||
}catch(\ReflectionException $e){ //class doesn't exist
|
||||
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
|
||||
$this->server->getLogger()->debug("Not registering @softDepend listener " . Utils::getNiceClosureName($handlerClosure) . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
if($eventClass === null or !$eventClass->isSubclassOf(Event::class)){
|
||||
continue;
|
||||
}
|
||||
|
||||
try{
|
||||
$priority = isset($tags["priority"]) ? EventPriority::fromString($tags["priority"]) : EventPriority::NORMAL;
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
|
||||
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
|
||||
}
|
||||
|
||||
$ignoreCancelled = false;
|
||||
@ -757,25 +744,11 @@ class PluginManager{
|
||||
$ignoreCancelled = false;
|
||||
break;
|
||||
default:
|
||||
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
|
||||
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $method->getParameters();
|
||||
try{
|
||||
$isHandler = count($parameters) === 1 && $parameters[0]->getClass() instanceof \ReflectionClass && is_subclass_of($parameters[0]->getClass()->getName(), Event::class);
|
||||
}catch(\ReflectionException $e){
|
||||
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
|
||||
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
if($isHandler){
|
||||
$class = $parameters[0]->getClass()->getName();
|
||||
$this->registerEvent($class, $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
|
||||
}
|
||||
$this->registerEvent($eventClass->getName(), $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,6 @@
|
||||
# New settings/defaults won't appear automatically in this file when upgrading.
|
||||
|
||||
settings:
|
||||
#Three-letter language code for server-side localization
|
||||
#Check your language code on https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
|
||||
language: "eng"
|
||||
#Whether to send all strings translated to server locale or let the device handle them
|
||||
force-language: false
|
||||
shutdown-message: "Server closed"
|
||||
|
File diff suppressed because one or more lines are too long
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
/**
|
||||
* Manages general-purpose worker threads used for processing asynchronous tasks, and the tasks submitted to those
|
||||
@ -96,6 +97,7 @@ class AsyncPool{
|
||||
* @param \Closure $hook
|
||||
*/
|
||||
public function addWorkerStartHook(\Closure $hook) : void{
|
||||
Utils::validateCallableSignature(function(int $worker) : void{}, $hook);
|
||||
$this->workerStartHooks[spl_object_hash($hook)] = $hook;
|
||||
foreach($this->workers as $i => $worker){
|
||||
$hook($i);
|
||||
@ -292,26 +294,19 @@ class AsyncPool{
|
||||
$task->checkProgressUpdates($this->server);
|
||||
if($task->isGarbage() and !$task->isRunning() and !$task->isCrashed()){
|
||||
if(!$task->hasCancelledRun()){
|
||||
try{
|
||||
/*
|
||||
* It's possible for a task to submit a progress update and then finish before the progress
|
||||
* update is detected by the parent thread, so here we consume any missed updates.
|
||||
*
|
||||
* When this happens, it's possible for a progress update to arrive between the previous
|
||||
* checkProgressUpdates() call and the next isGarbage() call, causing progress updates to be
|
||||
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
|
||||
* been consumed before completing.
|
||||
*/
|
||||
$task->checkProgressUpdates($this->server);
|
||||
$task->onCompletion($this->server);
|
||||
if($task->removeDanglingStoredObjects()){
|
||||
$this->logger->notice("AsyncTask " . get_class($task) . " stored local complex data but did not remove them after completion");
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->critical("Could not execute completion of asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": " . $e->getMessage());
|
||||
$this->logger->logException($e);
|
||||
|
||||
$task->removeDanglingStoredObjects(); //silent
|
||||
/*
|
||||
* It's possible for a task to submit a progress update and then finish before the progress
|
||||
* update is detected by the parent thread, so here we consume any missed updates.
|
||||
*
|
||||
* When this happens, it's possible for a progress update to arrive between the previous
|
||||
* checkProgressUpdates() call and the next isGarbage() call, causing progress updates to be
|
||||
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
|
||||
* been consumed before completing.
|
||||
*/
|
||||
$task->checkProgressUpdates($this->server);
|
||||
$task->onCompletion($this->server);
|
||||
if($task->removeDanglingStoredObjects()){
|
||||
$this->logger->notice("AsyncTask " . get_class($task) . " stored local complex data but did not remove them after completion");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,11 +107,9 @@ abstract class AsyncTask extends Collectable{
|
||||
|
||||
/**
|
||||
* @param mixed $result
|
||||
* @param bool $serialize
|
||||
*/
|
||||
public function setResult($result, bool $serialize = true){
|
||||
$this->result = $serialize ? serialize($result) : $result;
|
||||
$this->serialized = $serialize;
|
||||
public function setResult($result){
|
||||
$this->result = ($this->serialized = !is_scalar($result)) ? serialize($result) : $result;
|
||||
}
|
||||
|
||||
public function setTaskId(int $taskId){
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user