mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-14 13:25:11 +00:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
08897c6941 | |||
4006be35d9 | |||
22fcfffa53 | |||
7dd53f2397 | |||
298259b473 | |||
c123f2d10b | |||
9d80802e53 | |||
ec1e257e21 | |||
64ed8adefc | |||
5df601c817 | |||
0cdf4d0c55 | |||
e6e28b74b5 | |||
3892f2f404 | |||
b4694092b7 | |||
95f6995ae0 | |||
4a24d7909e | |||
a5e38576ef | |||
a604e6835e | |||
b2ca364de0 | |||
565373cee6 | |||
c29723e3c4 | |||
03f8fe62d4 | |||
8fa196efc9 | |||
69c54e789a | |||
9e1fa453ad | |||
70635d0870 | |||
7ad0aa56b1 | |||
1ff6f8846e | |||
87f458f9bd | |||
5a7e575c3a | |||
d6d98183ea | |||
9ff5c65fb6 | |||
1532b0ef6d | |||
9ece971a2b | |||
5546c88f88 | |||
e8c7ae595d | |||
0d9f40873f | |||
a4aee98cba | |||
a97c7d3132 | |||
4a1ed21e52 | |||
1b053c7928 | |||
c684f99cc4 | |||
695793795e | |||
a4965842d6 | |||
1ef6f5d166 | |||
eccc249009 | |||
dbaf7287bc | |||
b3b240e25b | |||
2b30ef1671 | |||
124ebf69c5 | |||
4274640845 | |||
58b665985e | |||
0f5c48e342 | |||
08ad5db05b | |||
94e8623c75 | |||
921f7e8f6a | |||
710e1d014d | |||
7fc22d3227 | |||
7bfe487ee5 | |||
d8cf835f92 | |||
65e44364e5 | |||
ebbbc581ca | |||
8aa8280a63 | |||
6a637d9099 | |||
06b80a9536 | |||
b3ffce9729 | |||
ce9f18c6b4 | |||
9610c55b19 | |||
b01b477a2a | |||
2d454ae56f | |||
066c9d4fd4 | |||
23829952c3 | |||
7ee98ff139 | |||
f1cab91ac9 | |||
e0bc9c5e96 | |||
70caa00266 | |||
ee7c838040 | |||
34e9e93210 | |||
5dbb0d177e | |||
58f0ad3e3e | |||
0df3585c81 | |||
697723b551 | |||
5926d80525 | |||
a57ec1b1ba | |||
905259a4e1 | |||
ca6930006c | |||
33eeeb856e | |||
c43ce5c8fa | |||
57cfe9fd43 | |||
d8824e7ee1 | |||
3455d0f3b9 | |||
6b2250cbce | |||
8dae497610 | |||
cade15e2dd | |||
d3e54db146 | |||
0081e30a89 | |||
76174f1920 | |||
dd6b5902a6 | |||
87852f2fe1 | |||
056d24c67d | |||
484d34fe04 | |||
6c6630d845 | |||
a5a236084f | |||
641a5a5e23 | |||
ebacb8525f | |||
579ab5866b |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -7,9 +7,6 @@
|
||||
[submodule "tests/plugins/PocketMine-DevTools"]
|
||||
path = tests/plugins/PocketMine-DevTools
|
||||
url = https://github.com/pmmp/PocketMine-DevTools.git
|
||||
[submodule "tests/plugins/PocketMine-TesterPlugin"]
|
||||
path = tests/plugins/PocketMine-TesterPlugin
|
||||
url = https://github.com/pmmp/PocketMine-TesterPlugin.git
|
||||
[submodule "src/pocketmine/resources/vanilla"]
|
||||
path = src/pocketmine/resources/vanilla
|
||||
url = https://github.com/pmmp/BedrockData.git
|
||||
|
@ -6,9 +6,9 @@ php:
|
||||
before_script:
|
||||
# - pecl install channel://pecl.php.net/pthreads-3.1.6
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
|
||||
- git clone https://github.com/krakjoe/pthreads.git
|
||||
- git clone https://github.com/pmmp/pthreads.git
|
||||
- cd pthreads
|
||||
- git checkout d32079fb4a88e6e008104d36dbbf0c2dd7deb403
|
||||
- git checkout c8cfacda84f21032d6014b53e72bf345ac901dac
|
||||
- phpize
|
||||
- ./configure
|
||||
- make
|
||||
|
22
composer.lock
generated
22
composer.lock
generated
@ -8,16 +8,16 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BinaryUtils.git",
|
||||
"reference": "c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595"
|
||||
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595",
|
||||
"reference": "c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/54efeb978be0ff9335022729fe63c1e2077bf1be",
|
||||
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -38,20 +38,20 @@
|
||||
"source": "https://github.com/pmmp/BinaryUtils/tree/master",
|
||||
"issues": "https://github.com/pmmp/BinaryUtils/issues"
|
||||
},
|
||||
"time": "2018-04-16T09:05:08+00:00"
|
||||
"time": "2018-08-26T18:11:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/math",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Math.git",
|
||||
"reference": "95ae5600328ed2add44c0bc830a68d3660e9e0ef"
|
||||
"reference": "ee299f5c9c444ca526c9c691b920f321458cf0b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Math/zipball/95ae5600328ed2add44c0bc830a68d3660e9e0ef",
|
||||
"reference": "95ae5600328ed2add44c0bc830a68d3660e9e0ef",
|
||||
"url": "https://api.github.com/repos/pmmp/Math/zipball/ee299f5c9c444ca526c9c691b920f321458cf0b6",
|
||||
"reference": "ee299f5c9c444ca526c9c691b920f321458cf0b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -69,10 +69,10 @@
|
||||
],
|
||||
"description": "PHP library containing math related code used in PocketMine-MP",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/Math/tree/master",
|
||||
"source": "https://github.com/pmmp/Math/tree/0.2.1",
|
||||
"issues": "https://github.com/pmmp/Math/issues"
|
||||
},
|
||||
"time": "2018-06-09T09:26:30+00:00"
|
||||
"time": "2018-08-15T15:43:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
|
@ -44,7 +44,7 @@ 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->getName()) . ".dat")){
|
||||
if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->name) . ".dat")){
|
||||
$this->namedtag = $this->server->getOfflinePlayerData($this->name);
|
||||
}else{
|
||||
$this->namedtag = null;
|
||||
@ -64,7 +64,7 @@ class OfflinePlayer implements IPlayer, Metadatable{
|
||||
}
|
||||
|
||||
public function isOp() : bool{
|
||||
return $this->server->isOp(strtolower($this->getName()));
|
||||
return $this->server->isOp($this->name);
|
||||
}
|
||||
|
||||
public function setOp(bool $value){
|
||||
@ -73,38 +73,38 @@ class OfflinePlayer implements IPlayer, Metadatable{
|
||||
}
|
||||
|
||||
if($value){
|
||||
$this->server->addOp(strtolower($this->getName()));
|
||||
$this->server->addOp($this->name);
|
||||
}else{
|
||||
$this->server->removeOp(strtolower($this->getName()));
|
||||
$this->server->removeOp($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
public function isBanned() : bool{
|
||||
return $this->server->getNameBans()->isBanned(strtolower($this->getName()));
|
||||
return $this->server->getNameBans()->isBanned($this->name);
|
||||
}
|
||||
|
||||
public function setBanned(bool $value){
|
||||
if($value){
|
||||
$this->server->getNameBans()->addBan($this->getName(), null, null, null);
|
||||
$this->server->getNameBans()->addBan($this->name, null, null, null);
|
||||
}else{
|
||||
$this->server->getNameBans()->remove($this->getName());
|
||||
$this->server->getNameBans()->remove($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
public function isWhitelisted() : bool{
|
||||
return $this->server->isWhitelisted(strtolower($this->getName()));
|
||||
return $this->server->isWhitelisted($this->name);
|
||||
}
|
||||
|
||||
public function setWhitelisted(bool $value){
|
||||
if($value){
|
||||
$this->server->addWhitelist(strtolower($this->getName()));
|
||||
$this->server->addWhitelist($this->name);
|
||||
}else{
|
||||
$this->server->removeWhitelist(strtolower($this->getName()));
|
||||
$this->server->removeWhitelist($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlayer(){
|
||||
return $this->server->getPlayerExact($this->getName());
|
||||
return $this->server->getPlayerExact($this->name);
|
||||
}
|
||||
|
||||
public function getFirstPlayed(){
|
||||
|
@ -535,7 +535,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function canBeCollidedWith() : bool{
|
||||
return !$this->isSpectator();
|
||||
return !$this->isSpectator() and parent::canBeCollidedWith();
|
||||
}
|
||||
|
||||
public function resetFallDistance() : void{
|
||||
@ -1487,14 +1487,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
protected function checkGroundState(float $movX, float $movY, float $movZ, float $dx, float $dy, float $dz) : void{
|
||||
if(!$this->onGround or $movY != 0){
|
||||
$bb = clone $this->boundingBox;
|
||||
$bb->minY = $this->y - 0.2;
|
||||
$bb->maxY = $this->y + 0.2;
|
||||
$bb = clone $this->boundingBox;
|
||||
$bb->minY = $this->y - 0.2;
|
||||
$bb->maxY = $this->y + 0.2;
|
||||
|
||||
$this->onGround = count($this->level->getCollisionBlocks($bb, true)) > 0;
|
||||
}
|
||||
$this->isCollided = $this->onGround;
|
||||
$this->onGround = $this->isCollided = count($this->level->getCollisionBlocks($bb, true)) > 0;
|
||||
}
|
||||
|
||||
public function canBeMovedByCurrents() : bool{
|
||||
@ -2168,7 +2165,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
$message = TextFormat::clean($message, $this->removeFormat);
|
||||
foreach(explode("\n", $message) as $messagePart){
|
||||
@ -2243,7 +2240,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if(!$this->spawned or !$this->isAlive()){
|
||||
return true;
|
||||
}
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
switch($packet->event){
|
||||
case EntityEventPacket::EATING_ITEM:
|
||||
@ -2394,7 +2391,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
return true;
|
||||
case InventoryTransactionPacket::USE_ITEM_ACTION_BREAK_BLOCK:
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
$item = $this->inventory->getItemInHand();
|
||||
$oldItem = clone $item;
|
||||
@ -2529,11 +2526,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
if($heldItem->onAttackEntity($target) and $this->isSurvival()){ //always fire the hook, even if we are survival
|
||||
$this->inventory->setItemInHand($heldItem);
|
||||
}
|
||||
if($this->isAlive()){
|
||||
//reactive damage like thorns might cause us to be killed by attacking another mob, which
|
||||
//would mean we'd already have dropped the inventory by the time we reached here
|
||||
if($heldItem->onAttackEntity($target) and $this->isSurvival()){ //always fire the hook, even if we are survival
|
||||
$this->inventory->setItemInHand($heldItem);
|
||||
}
|
||||
|
||||
$this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK);
|
||||
$this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
@ -2631,7 +2632,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
$target = $this->level->getEntity($packet->target);
|
||||
if($target === null){
|
||||
@ -2845,7 +2846,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
if(isset($this->windowIndex[$packet->windowId])){
|
||||
$this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this));
|
||||
@ -2895,7 +2896,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if(!$this->spawned or !$this->isAlive()){
|
||||
return true;
|
||||
}
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
$pos = new Vector3($packet->x, $packet->y, $packet->z);
|
||||
if($pos->distanceSquared($this) > 10000 or $this->level->checkSpawnProtection($this, $pos)){
|
||||
@ -3613,7 +3614,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
//Crafting grid must always be evacuated even if keep-inventory is true. This dumps the contents into the
|
||||
//main inventory and drops the rest on the ground.
|
||||
$this->resetCraftingGridType();
|
||||
$this->doCloseInventory();
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)));
|
||||
|
||||
@ -3796,15 +3797,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->craftingGrid = $grid;
|
||||
}
|
||||
|
||||
public function resetCraftingGridType() : void{
|
||||
$contents = $this->craftingGrid->getContents();
|
||||
if(count($contents) > 0){
|
||||
$drops = $this->inventory->addItem(...$contents);
|
||||
foreach($drops as $drop){
|
||||
$this->dropItem($drop);
|
||||
}
|
||||
public function doCloseInventory() : void{
|
||||
/** @var Inventory[] $inventories */
|
||||
$inventories = [$this->craftingGrid, $this->cursorInventory];
|
||||
foreach($inventories as $inventory){
|
||||
$contents = $inventory->getContents();
|
||||
if(count($contents) > 0){
|
||||
$drops = $this->inventory->addItem(...$contents);
|
||||
foreach($drops as $drop){
|
||||
$this->dropItem($drop);
|
||||
}
|
||||
|
||||
$this->craftingGrid->clearAll();
|
||||
$inventory->clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
if($this->craftingGrid->getGridWidth() > CraftingGrid::SIZE_SMALL){
|
||||
|
@ -37,7 +37,7 @@ namespace pocketmine {
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.0.2";
|
||||
const BASE_VERSION = "3.0.12";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
||||
|
@ -1026,6 +1026,7 @@ class Server{
|
||||
}
|
||||
|
||||
try{
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
}catch(\Throwable $e){
|
||||
|
||||
@ -1070,6 +1071,9 @@ class Server{
|
||||
|
||||
if(($providerClass = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "pmanvil"))) === null){
|
||||
$providerClass = LevelProviderManager::getProviderByName("pmanvil");
|
||||
if($providerClass === null){
|
||||
throw new \InvalidStateException("Default level provider has not been registered");
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
@ -1077,6 +1081,7 @@ class Server{
|
||||
/** @var LevelProvider $providerClass */
|
||||
$providerClass::generate($path, $name, $seed, $generator, $options);
|
||||
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
@ -2010,6 +2015,10 @@ class Server{
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->doTitleTick){
|
||||
echo "\x1b]0;\x07";
|
||||
}
|
||||
|
||||
try{
|
||||
if(!$this->isRunning()){
|
||||
$this->sendUsage(SendUsageTask::TYPE_CLOSE);
|
||||
@ -2141,10 +2150,6 @@ class Server{
|
||||
* @param array|null $trace
|
||||
*/
|
||||
public function exceptionHandler(\Throwable $e, $trace = null){
|
||||
if($e === null){
|
||||
return;
|
||||
}
|
||||
|
||||
global $lastError;
|
||||
|
||||
if($trace === null){
|
||||
@ -2202,7 +2207,7 @@ class Server{
|
||||
}
|
||||
}
|
||||
|
||||
if($dump->getData()["error"]["type"] === "E_PARSE" or $dump->getData()["error"]["type"] === "E_COMPILE_ERROR"){
|
||||
if($dump->getData()["error"]["type"] === \ParseError::class){
|
||||
$report = false;
|
||||
}
|
||||
|
||||
@ -2471,6 +2476,8 @@ class Server{
|
||||
try{
|
||||
if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){
|
||||
$this->queryHandler->handle($interface, $address, $port, $payload);
|
||||
}else{
|
||||
$this->logger->debug("Unhandled raw packet from $address $port: " . bin2hex($payload));
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
if(\pocketmine\DEBUG > 1){
|
||||
|
@ -67,14 +67,10 @@ abstract class Thread extends \Thread{
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
|
||||
if($this->getClassLoader() === null){
|
||||
$this->setClassLoader();
|
||||
}
|
||||
return parent::start($options);
|
||||
if($this->getClassLoader() === null){
|
||||
$this->setClassLoader();
|
||||
}
|
||||
|
||||
return false;
|
||||
return parent::start($options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,12 +79,9 @@ abstract class Thread extends \Thread{
|
||||
public function quit(){
|
||||
$this->isKilled = true;
|
||||
|
||||
$this->notify();
|
||||
|
||||
if(!$this->isJoined()){
|
||||
if(!$this->isTerminated()){
|
||||
$this->join();
|
||||
}
|
||||
$this->notify();
|
||||
$this->join();
|
||||
}
|
||||
|
||||
ThreadManager::getInstance()->remove($this);
|
||||
|
@ -67,14 +67,10 @@ abstract class Worker extends \Worker{
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
|
||||
if($this->getClassLoader() === null){
|
||||
$this->setClassLoader();
|
||||
}
|
||||
return parent::start($options);
|
||||
if($this->getClassLoader() === null){
|
||||
$this->setClassLoader();
|
||||
}
|
||||
|
||||
return false;
|
||||
return parent::start($options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,16 +79,10 @@ abstract class Worker extends \Worker{
|
||||
public function quit(){
|
||||
$this->isKilled = true;
|
||||
|
||||
$this->notify();
|
||||
|
||||
if($this->isRunning()){
|
||||
$this->shutdown();
|
||||
while($this->unstack() !== null);
|
||||
$this->notify();
|
||||
$this->unstack();
|
||||
}elseif(!$this->isJoined()){
|
||||
if(!$this->isTerminated()){
|
||||
$this->join();
|
||||
}
|
||||
$this->shutdown();
|
||||
}
|
||||
|
||||
ThreadManager::getInstance()->remove($this);
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\inventory\AnvilInventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\item\TieredTool;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -55,13 +54,17 @@ class Anvil extends Fallable{
|
||||
return 6000;
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
return 0x0c;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
static $names = [
|
||||
self::TYPE_NORMAL => "Anvil",
|
||||
self::TYPE_SLIGHTLY_DAMAGED => "Slightly Damaged Anvil",
|
||||
self::TYPE_VERY_DAMAGED => "Very Damaged Anvil"
|
||||
];
|
||||
return $names[$this->meta & 0x0c] ?? "Anvil";
|
||||
return $names[$this->getVariant()] ?? "Anvil";
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
@ -106,13 +109,7 @@ class Anvil extends Fallable{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$direction = ($player !== null ? $player->getDirection() : 0) & 0x03;
|
||||
$this->meta = ($this->meta & 0x0c) | $direction;
|
||||
$this->meta = $this->getVariant() | $direction;
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [
|
||||
ItemFactory::get($this->getItemId(), $this->getDamage() >> 2)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\level\Position;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
/**
|
||||
* Manages block registration and instance creation
|
||||
@ -436,19 +435,12 @@ class BlockFactory{
|
||||
* @return int
|
||||
*/
|
||||
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
|
||||
if($id === Block::AIR){
|
||||
//TODO: HACK! (weird air blocks with non-zero damage values shouldn't turn into update! blocks)
|
||||
$meta = 0;
|
||||
}
|
||||
|
||||
$index = ($id << 4) | $meta;
|
||||
if(!isset(self::$staticRuntimeIdMap[$index])){
|
||||
self::registerMapping($rtId = ++self::$lastRuntimeId, $id, $meta);
|
||||
MainLogger::getLogger()->error("ID $id meta $meta does not have a corresponding block static runtime ID, added a new unknown runtime ID ($rtId)");
|
||||
return $rtId;
|
||||
}
|
||||
|
||||
return self::$staticRuntimeIdMap[$index];
|
||||
/*
|
||||
* try id+meta first
|
||||
* if not found, try id+0 (strip meta)
|
||||
* if still not found, return update! block
|
||||
*/
|
||||
return self::$staticRuntimeIdMap[($id << 4) | $meta] ?? self::$staticRuntimeIdMap[$id << 4] ?? self::$staticRuntimeIdMap[BlockIds::INFO_UPDATE << 4];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +77,7 @@ class Cactus extends Transparent{
|
||||
}else{
|
||||
for($side = 2; $side <= 5; ++$side){
|
||||
$b = $this->getSide($side);
|
||||
if(!$b->canBeFlowedInto()){
|
||||
if($b->isSolid()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
break;
|
||||
}
|
||||
@ -117,7 +117,7 @@ class Cactus extends Transparent{
|
||||
$block1 = $this->getSide(Vector3::SIDE_SOUTH);
|
||||
$block2 = $this->getSide(Vector3::SIDE_WEST);
|
||||
$block3 = $this->getSide(Vector3::SIDE_EAST);
|
||||
if($block0->isTransparent() and $block1->isTransparent() and $block2->isTransparent() and $block3->isTransparent()){
|
||||
if(!$block0->isSolid() and !$block1->isSolid() and !$block2->isSolid() and !$block3->isSolid()){
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
|
||||
return true;
|
||||
|
@ -57,7 +57,7 @@ class CoalOre extends Solid{
|
||||
];
|
||||
}
|
||||
|
||||
public function getXpDropForTool(Item $item) : int{
|
||||
protected function getXpDropAmount() : int{
|
||||
return mt_rand(0, 2);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class ConcretePowder extends Fallable{
|
||||
private function checkAdjacentWater() : ?Block{
|
||||
for($i = 1; $i < 6; ++$i){ //Do not check underneath
|
||||
if($this->getSide($i) instanceof Water){
|
||||
return Block::get(Block::CONCRETE, $this->meta);
|
||||
return BlockFactory::get(Block::CONCRETE, $this->meta);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,11 @@ class EmeraldOre extends Solid{
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [
|
||||
ItemFactory::get(Item::EMERALD)
|
||||
ItemFactory::get(Item::EMERALD)
|
||||
];
|
||||
}
|
||||
|
||||
protected function getXpDropAmount() : int{
|
||||
return mt_rand(3, 7);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class SignPost extends Transparent{
|
||||
if($face !== Vector3::SIDE_DOWN){
|
||||
|
||||
if($face === Vector3::SIDE_UP){
|
||||
$this->meta = floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f;
|
||||
$this->meta = $player !== null ? (floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f) : 0;
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
}else{
|
||||
$this->meta = $face;
|
||||
|
@ -127,7 +127,7 @@ abstract class Command{
|
||||
if($this->permissionMessage === null){
|
||||
$target->sendMessage($target->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
}elseif($this->permissionMessage !== ""){
|
||||
$target->sendMessage(str_replace("<permission>", $this->getPermission(), $this->permissionMessage));
|
||||
$target->sendMessage(str_replace("<permission>", $this->permission, $this->permissionMessage));
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -139,11 +139,11 @@ abstract class Command{
|
||||
* @return bool
|
||||
*/
|
||||
public function testPermissionSilent(CommandSender $target) : bool{
|
||||
if(($perm = $this->getPermission()) === null or $perm === ""){
|
||||
if($this->permission === null or $this->permission === ""){
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach(explode(";", $perm) as $permission){
|
||||
foreach(explode(";", $this->permission) as $permission){
|
||||
if($target->hasPermission($permission)){
|
||||
return true;
|
||||
}
|
||||
|
@ -302,9 +302,9 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
$targets = [];
|
||||
$bad = [];
|
||||
$recursive = [];
|
||||
|
||||
$bad = "";
|
||||
$recursive = "";
|
||||
foreach($commandStrings as $commandString){
|
||||
$args = explode(" ", $commandString);
|
||||
$commandName = "";
|
||||
@ -312,27 +312,21 @@ class SimpleCommandMap implements CommandMap{
|
||||
|
||||
|
||||
if($command === null){
|
||||
if(strlen($bad) > 0){
|
||||
$bad .= ", ";
|
||||
}
|
||||
$bad .= $commandString;
|
||||
$bad[] = $commandString;
|
||||
}elseif($commandName === $alias){
|
||||
if($recursive !== ""){
|
||||
$recursive .= ", ";
|
||||
}
|
||||
$recursive .= $commandString;
|
||||
$recursive[] = $commandString;
|
||||
}else{
|
||||
$targets[] = $commandString;
|
||||
}
|
||||
}
|
||||
|
||||
if($recursive !== ""){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, $recursive]));
|
||||
if(!empty($recursive)){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, implode(", ", $recursive)]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strlen($bad) > 0){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad]));
|
||||
if(!empty($bad)){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, implode(", ", $bad)]));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -62,15 +62,7 @@ class KillCommand extends VanillaCommand{
|
||||
$player = $sender->getServer()->getPlayer($args[0]);
|
||||
|
||||
if($player instanceof Player){
|
||||
$sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
|
||||
if($ev->isCancelled()){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player->setLastDamageCause($ev);
|
||||
$player->setHealth(0);
|
||||
|
||||
$player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.kill.successful", [$player->getName()]));
|
||||
}else{
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound"));
|
||||
@ -86,14 +78,7 @@ class KillCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
|
||||
if($ev->isCancelled()){
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->setLastDamageCause($ev);
|
||||
$sender->setHealth(0);
|
||||
$sender->attack(new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
$sender->sendMessage(new TranslationContainer("commands.kill.successful", [$sender->getName()]));
|
||||
}else{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
class PluginsCommand extends VanillaCommand{
|
||||
@ -43,20 +44,12 @@ class PluginsCommand extends VanillaCommand{
|
||||
if(!$this->testPermission($sender)){
|
||||
return true;
|
||||
}
|
||||
$this->sendPluginList($sender);
|
||||
|
||||
$list = array_map(function(Plugin $plugin) : string{
|
||||
return ($plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED) . $plugin->getDescription()->getFullName();
|
||||
}, $sender->getServer()->getPluginManager()->getPlugins());
|
||||
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($list), implode(TextFormat::WHITE . ", ", $list)]));
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendPluginList(CommandSender $sender){
|
||||
$list = "";
|
||||
foreach(($plugins = $sender->getServer()->getPluginManager()->getPlugins()) as $plugin){
|
||||
if(strlen($list) > 0){
|
||||
$list .= TextFormat::WHITE . ", ";
|
||||
}
|
||||
$list .= $plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED;
|
||||
$list .= $plugin->getDescription()->getFullName();
|
||||
}
|
||||
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($plugins), $list]));
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class TeleportCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
$args = array_values(array_filter($args, function($arg){
|
||||
return strlen($arg) > 0;
|
||||
return $arg !== "";
|
||||
}));
|
||||
if(count($args) < 1 or count($args) > 6){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
|
@ -50,9 +50,7 @@ class TellCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$name = strtolower(array_shift($args));
|
||||
|
||||
$player = $sender->getServer()->getPlayer($name);
|
||||
$player = $sender->getServer()->getPlayer(array_shift($args));
|
||||
|
||||
if($player === $sender){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.message.sameTarget"));
|
||||
|
@ -98,21 +98,33 @@ class TimingsCommand extends VanillaCommand{
|
||||
if($paste){
|
||||
fseek($fileTimings, 0);
|
||||
$data = [
|
||||
"syntax" => "text",
|
||||
"poster" => $sender->getServer()->getName(),
|
||||
"content" => stream_get_contents($fileTimings)
|
||||
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
|
||||
"data" => $content = stream_get_contents($fileTimings)
|
||||
];
|
||||
fclose($fileTimings);
|
||||
|
||||
$sender->getServer()->getAsyncPool()->submitTask(new class([
|
||||
["page" => "http://paste.ubuntu.com", "extraOpts" => [
|
||||
CURLOPT_HTTPHEADER => ["User-Agent: " . $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion()],
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_AUTOREFERER => false,
|
||||
CURLOPT_FOLLOWLOCATION => false
|
||||
]]
|
||||
], $sender) extends BulkCurlTask{
|
||||
$host = $sender->getServer()->getProperty("timings.host", "timings.pmmp.io");
|
||||
|
||||
$sender->getServer()->getAsyncPool()->submitTask(new class($sender, $host, $agent, $data) extends BulkCurlTask{
|
||||
/** @var string */
|
||||
private $host;
|
||||
|
||||
public function __construct(CommandSender $sender, string $host, string $agent, array $data){
|
||||
parent::__construct([
|
||||
["page" => "https://$host?upload=true", "extraOpts" => [
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"User-Agent: $agent",
|
||||
"Content-Type: application/x-www-form-urlencoded"
|
||||
],
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($data),
|
||||
CURLOPT_AUTOREFERER => false,
|
||||
CURLOPT_FOLLOWLOCATION => false
|
||||
]]
|
||||
], $sender);
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
public function onCompletion(Server $server){
|
||||
$sender = $this->fetchLocal();
|
||||
if($sender instanceof Player and !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
|
||||
@ -123,23 +135,14 @@ class TimingsCommand extends VanillaCommand{
|
||||
$server->getLogger()->logException($result);
|
||||
return;
|
||||
}
|
||||
list(, $headers) = $result;
|
||||
foreach($headers as $headerGroup){
|
||||
if(isset($headerGroup["location"]) and preg_match('#^http://paste\\.ubuntu\\.com/([A-Za-z0-9+\/=]+)/#', trim($headerGroup["location"]), $match)){
|
||||
$pasteId = $match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isset($pasteId)){
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $pasteId . "/"]));
|
||||
if(isset($result[0]) && is_array($response = json_decode($result[0], true)) && isset($response["id"])){
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead",
|
||||
["http://" . $sender->getServer()->getProperty("timings.host", "timings.pmmp.io") . "/?url=" . urlencode($pasteId)]));
|
||||
["https://" . $this->host . "/?id=" . $response["id"]]));
|
||||
}else{
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}else{
|
||||
fclose($fileTimings);
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));
|
||||
|
@ -169,7 +169,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_LIMITED_LIFE = 77;
|
||||
public const DATA_ARMOR_STAND_POSE_INDEX = 78; //int
|
||||
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; //int
|
||||
/* 80 (byte) something to do with nametag visibility? */
|
||||
public const DATA_ALWAYS_SHOW_NAMETAG = 80; //byte: -1 = default, 0 = only when looked at, 1 = always
|
||||
public const DATA_COLOR_2 = 81; //byte
|
||||
/* 82 (unknown) */
|
||||
public const DATA_SCORE_TAG = 83; //string
|
||||
@ -285,6 +285,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public static function createEntity($type, Level $level, CompoundTag $nbt, ...$args) : ?Entity{
|
||||
if(isset(self::$knownEntities[$type])){
|
||||
$class = self::$knownEntities[$type];
|
||||
/** @see Entity::__construct() */
|
||||
return new $class($level, $nbt, ...$args);
|
||||
}
|
||||
|
||||
@ -606,7 +607,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setNameTagAlwaysVisible(bool $value = true) : void{
|
||||
$this->setGenericFlag(self::DATA_FLAG_ALWAYS_SHOW_NAMETAG, $value);
|
||||
$this->propertyManager->setByte(self::DATA_ALWAYS_SHOW_NAMETAG, $value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1015,8 +1016,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isOnFire()){
|
||||
$hasUpdate = ($hasUpdate || $this->doOnFireTick($tickDiff));
|
||||
if($this->isOnFire() and $this->doOnFireTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->noDamageTicks > 0){
|
||||
@ -1101,7 +1102,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
|
||||
public function canBeCollidedWith() : bool{
|
||||
return true;
|
||||
return $this->isAlive();
|
||||
}
|
||||
|
||||
protected function updateMovement(bool $teleport = false) : void{
|
||||
@ -1346,7 +1347,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
if($this->hasMovementUpdate()){
|
||||
$this->tryChangeMovement();
|
||||
$this->move($this->motion->x, $this->motion->y, $this->motion->z);
|
||||
|
||||
if(abs($this->motion->x) <= self::MOTION_THRESHOLD){
|
||||
$this->motion->x = 0;
|
||||
@ -1358,6 +1358,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->motion->z = 0;
|
||||
}
|
||||
|
||||
if($this->motion->x != 0 or $this->motion->y != 0 or $this->motion->z != 0){
|
||||
$this->move($this->motion->x, $this->motion->y, $this->motion->z);
|
||||
}
|
||||
|
||||
$this->forceMovementUpdate = false;
|
||||
}
|
||||
|
||||
|
@ -469,6 +469,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
/**
|
||||
* Called after EntityDamageEvent execution to apply post-hurt effects, such as reducing absorption or modifying
|
||||
* armour durability.
|
||||
* This will not be called by damage sources causing death.
|
||||
*
|
||||
* @param EntityDamageEvent $source
|
||||
*/
|
||||
@ -534,6 +535,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attackTime = 10; //0.5 seconds cooldown
|
||||
|
||||
if($source instanceof EntityDamageByEntityEvent){
|
||||
$e = $source->getDamager();
|
||||
if($source instanceof EntityDamageByChildEntityEvent){
|
||||
@ -551,15 +554,10 @@ abstract class Living extends Entity implements Damageable{
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyPostDamageEffects($source);
|
||||
|
||||
if($this->isAlive()){
|
||||
$this->applyPostDamageEffects($source);
|
||||
$this->doHitAnimation();
|
||||
}else{
|
||||
$this->startDeathAnimation();
|
||||
}
|
||||
|
||||
$this->attackTime = 10; //0.5 seconds cooldown
|
||||
}
|
||||
|
||||
protected function doHitAnimation() : void{
|
||||
@ -594,6 +592,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
public function kill() : void{
|
||||
parent::kill();
|
||||
$this->onDeath();
|
||||
$this->startDeathAnimation();
|
||||
}
|
||||
|
||||
protected function onDeath() : void{
|
||||
@ -630,7 +629,9 @@ abstract class Living extends Entity implements Damageable{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->doEffectsTick($tickDiff);
|
||||
if($this->doEffectsTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
if($this->isAlive()){
|
||||
if($this->isInsideOfSolid()){
|
||||
@ -642,6 +643,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
if(!$this->canBreathe()){
|
||||
$this->setBreathing(false);
|
||||
$this->doAirSupplyTick($tickDiff);
|
||||
$hasUpdate = true;
|
||||
}elseif(!$this->isBreathing()){
|
||||
$this->setBreathing(true);
|
||||
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
|
||||
@ -657,7 +659,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
protected function doEffectsTick(int $tickDiff = 1) : void{
|
||||
protected function doEffectsTick(int $tickDiff = 1) : bool{
|
||||
foreach($this->effects as $instance){
|
||||
$type = $instance->getType();
|
||||
if($type->canTick($instance)){
|
||||
@ -668,6 +670,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
$this->removeEffect($instance->getId());
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($this->effects);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,7 +165,7 @@ class ExperienceOrb extends Entity{
|
||||
}
|
||||
|
||||
$currentTarget = $this->getTargetPlayer();
|
||||
if($currentTarget !== null and $currentTarget->distanceSquared($this) > self::MAX_TARGET_DISTANCE ** 2){
|
||||
if($currentTarget !== null and (!$currentTarget->isAlive() or $currentTarget->distanceSquared($this) > self::MAX_TARGET_DISTANCE ** 2)){
|
||||
$currentTarget = null;
|
||||
}
|
||||
|
||||
|
@ -134,6 +134,7 @@ class FallingBlock extends Entity{
|
||||
}
|
||||
|
||||
public function saveNBT() : void{
|
||||
parent::saveNBT();
|
||||
$this->namedtag->setInt("TileID", $this->block->getId(), true);
|
||||
$this->namedtag->setByte("Data", $this->block->getDamage());
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class ItemEntity extends Entity{
|
||||
}
|
||||
|
||||
public function onCollideWithPlayer(Player $player) : void{
|
||||
if($this->getPickupDelay() > 0){
|
||||
if($this->getPickupDelay() !== 0){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,8 @@ class Painting extends Entity{
|
||||
|
||||
$this->namedtag->setByte("Facing", (int) $this->direction);
|
||||
$this->namedtag->setByte("Direction", (int) $this->direction); //Save both for full compatibility
|
||||
|
||||
$this->namedtag->setString("Motive", $this->motive);
|
||||
}
|
||||
|
||||
public function kill() : void{
|
||||
|
@ -82,7 +82,7 @@ class SplashPotion extends Throwable{
|
||||
if($hasEffects){
|
||||
if(!$this->willLinger()){
|
||||
foreach($this->level->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living){
|
||||
if($entity instanceof Living and $entity->isAlive()){
|
||||
$distanceSquared = $entity->distanceSquared($this);
|
||||
if($distanceSquared > 16){ //4 blocks
|
||||
continue;
|
||||
|
@ -31,7 +31,6 @@ use pocketmine\event\entity\EntityEvent;
|
||||
* Called when a player gains or loses XP levels and/or progress.
|
||||
*/
|
||||
class PlayerExperienceChangeEvent extends EntityEvent implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
/** @var Human */
|
||||
protected $entity;
|
||||
/** @var int */
|
||||
|
@ -60,15 +60,16 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
|
||||
}
|
||||
|
||||
public function getItem(int $index) : Item{
|
||||
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->right->getSize());
|
||||
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->left->getSize());
|
||||
}
|
||||
|
||||
public function setItem(int $index, Item $item, bool $send = true) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->setItem($index, $item, $send) : $this->right->setItem($index - $this->right->getSize(), $item, $send);
|
||||
}
|
||||
|
||||
public function clear(int $index, bool $send = true) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->clear($index, $send) : $this->right->clear($index - $this->right->getSize(), $send);
|
||||
$old = $this->getItem($index);
|
||||
if($index < $this->left->getSize() ? $this->left->setItem($index, $item, $send) : $this->right->setItem($index - $this->left->getSize(), $item, $send)){
|
||||
$this->onSlotChange($index, $old, $send);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getContents(bool $includeEmpty = false) : array{
|
||||
|
@ -38,7 +38,7 @@ class DropItemAction extends InventoryAction{
|
||||
}
|
||||
|
||||
public function isValid(Player $source) : bool{
|
||||
return true;
|
||||
return !$this->targetItem->isNull();
|
||||
}
|
||||
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
|
@ -193,7 +193,7 @@ interface ItemIds extends BlockIds{
|
||||
public const LEAD = 420;
|
||||
public const NAMETAG = 421, NAME_TAG = 421;
|
||||
public const PRISMARINE_CRYSTALS = 422;
|
||||
public const MUTTONRAW = 423, MUTTON_RAW = 423, RAW_MUTTON = 423;
|
||||
public const MUTTON = 423, MUTTONRAW = 423, MUTTON_RAW = 423, RAW_MUTTON = 423;
|
||||
public const COOKED_MUTTON = 424, MUTTONCOOKED = 424, MUTTON_COOKED = 424;
|
||||
public const ARMOR_STAND = 425;
|
||||
public const END_CRYSTAL = 426;
|
||||
|
@ -236,6 +236,9 @@ class Explosion{
|
||||
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)));
|
||||
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();
|
||||
}
|
||||
$ev->getBlock()->onNearbyBlockChange();
|
||||
}
|
||||
$updateBlocks[$index] = true;
|
||||
|
@ -191,7 +191,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
/** @var Player[][] */
|
||||
private $chunkSendQueue = [];
|
||||
/** @var bool[] */
|
||||
/** @var ChunkRequestTask[] */
|
||||
private $chunkSendTasks = [];
|
||||
|
||||
/** @var bool[] */
|
||||
@ -769,6 +769,9 @@ class Level implements ChunkManager, Metadatable{
|
||||
if(count($this->changedBlocks) > 0){
|
||||
if(count($this->players) > 0){
|
||||
foreach($this->changedBlocks as $index => $blocks){
|
||||
if(empty($blocks)){ //blocks can be set normally and then later re-set with direct send
|
||||
continue;
|
||||
}
|
||||
unset($this->chunkCache[$index]);
|
||||
Level::getXZ($index, $chunkX, $chunkZ);
|
||||
if(count($blocks) > 512){
|
||||
@ -776,7 +779,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
foreach($this->getChunkPlayers($chunkX, $chunkZ) as $p){
|
||||
$p->onChunkChanged($chunk);
|
||||
}
|
||||
}elseif(!empty($blocks)){
|
||||
}else{
|
||||
$this->sendBlocks($this->getChunkPlayers($chunkX, $chunkZ), $blocks, UpdateBlockPacket::FLAG_ALL);
|
||||
}
|
||||
}
|
||||
@ -794,8 +797,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->checkSleep();
|
||||
}
|
||||
|
||||
if(!empty($this->players) and !empty($this->globalPackets)){
|
||||
$this->server->batchPackets($this->players, $this->globalPackets);
|
||||
if(!empty($this->globalPackets)){
|
||||
if(!empty($this->players)){
|
||||
$this->server->batchPackets($this->players, $this->globalPackets);
|
||||
}
|
||||
$this->globalPackets = [];
|
||||
}
|
||||
|
||||
@ -1624,7 +1629,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$spawnLocation = $this->getSpawnLocation();
|
||||
$s = new Vector2($spawnLocation->x, $spawnLocation->z);
|
||||
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){
|
||||
if($t->distance($s) <= $distance){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2346,21 +2351,20 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
|
||||
$oldChunk = $this->getChunk($chunkX, $chunkZ, false);
|
||||
if($unload and $oldChunk !== null){
|
||||
$this->unloadChunk($chunkX, $chunkZ, false, false);
|
||||
}else{
|
||||
$oldEntities = $oldChunk !== null ? $oldChunk->getEntities() : [];
|
||||
$oldTiles = $oldChunk !== null ? $oldChunk->getTiles() : [];
|
||||
if($oldChunk !== null){
|
||||
if($unload){
|
||||
$this->unloadChunk($chunkX, $chunkZ, false, false);
|
||||
}else{
|
||||
foreach($oldChunk->getEntities() as $entity){
|
||||
$chunk->addEntity($entity);
|
||||
$oldChunk->removeEntity($entity);
|
||||
$entity->chunk = $chunk;
|
||||
}
|
||||
|
||||
foreach($oldEntities as $entity){
|
||||
$chunk->addEntity($entity);
|
||||
$oldChunk->removeEntity($entity);
|
||||
$entity->chunk = $chunk;
|
||||
}
|
||||
|
||||
foreach($oldTiles as $tile){
|
||||
$chunk->addTile($tile);
|
||||
$oldChunk->removeTile($tile);
|
||||
foreach($oldChunk->getTiles() as $tile){
|
||||
$chunk->addTile($tile);
|
||||
$oldChunk->removeTile($tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2368,6 +2372,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
unset($this->blockCache[$chunkHash]);
|
||||
unset($this->chunkCache[$chunkHash]);
|
||||
unset($this->changedBlocks[$chunkHash]);
|
||||
$chunk->setChanged();
|
||||
|
||||
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
||||
@ -2453,7 +2458,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
private function sendChunkFromCache(int $x, int $z){
|
||||
if(isset($this->chunkSendTasks[$index = Level::chunkHash($x, $z)])){
|
||||
if(isset($this->chunkSendQueue[$index = Level::chunkHash($x, $z)])){
|
||||
foreach($this->chunkSendQueue[$index] as $player){
|
||||
/** @var Player $player */
|
||||
if($player->isConnected() and isset($player->usedChunks[$index])){
|
||||
@ -2461,7 +2466,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
}
|
||||
unset($this->chunkSendQueue[$index]);
|
||||
unset($this->chunkSendTasks[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2470,11 +2474,17 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->timings->syncChunkSendTimer->startTiming();
|
||||
|
||||
foreach($this->chunkSendQueue as $index => $players){
|
||||
if(isset($this->chunkSendTasks[$index])){
|
||||
continue;
|
||||
}
|
||||
Level::getXZ($index, $x, $z);
|
||||
$this->chunkSendTasks[$index] = true;
|
||||
|
||||
if(isset($this->chunkSendTasks[$index])){
|
||||
if($this->chunkSendTasks[$index]->isCrashed()){
|
||||
unset($this->chunkSendTasks[$index]);
|
||||
$this->server->getLogger()->error("Failed to prepare chunk $x $z for sending, retrying");
|
||||
}else{
|
||||
//Not ready for sending yet
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(isset($this->chunkCache[$index])){
|
||||
$this->sendChunkFromCache($x, $z);
|
||||
continue;
|
||||
@ -2487,7 +2497,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
assert($chunk->getX() === $x and $chunk->getZ() === $z, "Chunk coordinate mismatch: expected $x $z, but chunk has coordinates " . $chunk->getX() . " " . $chunk->getZ() . ", did you forget to clone a chunk before setting?");
|
||||
|
||||
$this->server->getAsyncPool()->submitTask(new ChunkRequestTask($this, $x, $z, $chunk));
|
||||
$this->server->getAsyncPool()->submitTask($task = new ChunkRequestTask($this, $x, $z, $chunk));
|
||||
$this->chunkSendTasks[$index] = $task;
|
||||
|
||||
$this->timings->syncChunkSendPrepareTimer->stopTiming();
|
||||
}
|
||||
@ -2500,24 +2511,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->timings->syncChunkSendTimer->startTiming();
|
||||
|
||||
$index = Level::chunkHash($x, $z);
|
||||
unset($this->chunkSendTasks[$index]);
|
||||
|
||||
if(!isset($this->chunkCache[$index]) and $this->server->getMemoryManager()->canUseChunkCache()){
|
||||
$this->chunkCache[$index] = $payload;
|
||||
$this->sendChunkFromCache($x, $z);
|
||||
$this->timings->syncChunkSendTimer->stopTiming();
|
||||
return;
|
||||
$this->chunkCache[$index] = $payload;
|
||||
$this->sendChunkFromCache($x, $z);
|
||||
if(!$this->server->getMemoryManager()->canUseChunkCache()){
|
||||
unset($this->chunkCache[$index]);
|
||||
}
|
||||
|
||||
if(isset($this->chunkSendTasks[$index])){
|
||||
foreach($this->chunkSendQueue[$index] as $player){
|
||||
/** @var Player $player */
|
||||
if($player->isConnected() and isset($player->usedChunks[$index])){
|
||||
$player->sendChunk($x, $z, $payload);
|
||||
}
|
||||
}
|
||||
unset($this->chunkSendQueue[$index]);
|
||||
unset($this->chunkSendTasks[$index]);
|
||||
}
|
||||
$this->timings->syncChunkSendTimer->stopTiming();
|
||||
}
|
||||
|
||||
@ -2751,6 +2752,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
unset($this->chunkCache[$chunkHash]);
|
||||
unset($this->blockCache[$chunkHash]);
|
||||
unset($this->changedBlocks[$chunkHash]);
|
||||
unset($this->chunkSendQueue[$chunkHash]);
|
||||
unset($this->chunkSendTasks[$chunkHash]);
|
||||
|
||||
$this->timings->doChunkUnload->stopTiming();
|
||||
|
||||
@ -2788,7 +2791,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false);
|
||||
$x = (int) $v->x;
|
||||
$z = (int) $v->z;
|
||||
if($chunk !== null){
|
||||
if($chunk !== null and $chunk->isGenerated()){
|
||||
$y = (int) min($max - 2, $v->y);
|
||||
$wasAir = ($chunk->getBlockId($x & 0x0f, $y - 1, $z & 0x0f) === 0);
|
||||
for(; $y > 0; --$y){
|
||||
@ -2944,16 +2947,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
if($populate){
|
||||
if(!isset($this->chunkPopulationQueue[$index])){
|
||||
$this->chunkPopulationQueue[$index] = true;
|
||||
for($xx = -1; $xx <= 1; ++$xx){
|
||||
for($zz = -1; $zz <= 1; ++$zz){
|
||||
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
|
||||
}
|
||||
$this->chunkPopulationQueue[$index] = true;
|
||||
for($xx = -1; $xx <= 1; ++$xx){
|
||||
for($zz = -1; $zz <= 1; ++$zz){
|
||||
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
|
||||
}
|
||||
$task = new PopulationTask($this, $chunk);
|
||||
$this->server->getAsyncPool()->submitTask($task);
|
||||
}
|
||||
$task = new PopulationTask($this, $chunk);
|
||||
$this->server->getAsyncPool()->submitTask($task);
|
||||
}
|
||||
|
||||
Timings::$populationTimer->stopTiming();
|
||||
|
@ -377,7 +377,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
|
||||
/** @var CompoundTag[] $entities */
|
||||
$entities = [];
|
||||
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){
|
||||
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and $entityData !== ""){
|
||||
$entities = $nbt->read($entityData, true);
|
||||
if(!is_array($entities)){
|
||||
$entities = [$entities];
|
||||
@ -392,7 +392,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
}
|
||||
|
||||
$tiles = [];
|
||||
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and strlen($tileData) > 0){
|
||||
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and $tileData !== ""){
|
||||
$tiles = $nbt->read($tileData, true);
|
||||
if(!is_array($tiles)){
|
||||
$tiles = [$tiles];
|
||||
@ -402,7 +402,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
//TODO: extra data should be converted into blockstorage layers (first they need to be implemented!)
|
||||
/*
|
||||
$extraData = [];
|
||||
if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and strlen($extraRawData) > 0){
|
||||
if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and $extraRawData !== ""){
|
||||
$binaryStream->setBuffer($extraRawData, 0);
|
||||
$count = $binaryStream->getLInt();
|
||||
for($i = 0; $i < $count; ++$i){
|
||||
|
@ -52,7 +52,10 @@ class GeneratorRegisterTask extends AsyncTask{
|
||||
$manager = new SimpleChunkManager($this->seed, $this->worldHeight);
|
||||
$this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager);
|
||||
|
||||
/** @var Generator $generator */
|
||||
/**
|
||||
* @var Generator $generator
|
||||
* @see Generator::__construct()
|
||||
*/
|
||||
$generator = new $this->generatorClass(unserialize($this->settings));
|
||||
$generator->init($manager, new Random($manager->getSeed()));
|
||||
$this->saveToThreadStore("generation.level{$this->levelId}.generator", $generator);
|
||||
|
@ -141,6 +141,10 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
$class = $ev->getPlayerClass();
|
||||
|
||||
/**
|
||||
* @var Player $player
|
||||
* @see Player::__construct()
|
||||
*/
|
||||
$player = new $class($this, $ev->getAddress(), $ev->getPort());
|
||||
$this->players[$identifier] = $player;
|
||||
$this->identifiersACK[$identifier] = 0;
|
||||
|
@ -30,14 +30,22 @@ use pocketmine\network\mcpe\NetworkSession;
|
||||
class GuiDataPickItemPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::GUI_DATA_PICK_ITEM_PACKET;
|
||||
|
||||
/** @var string */
|
||||
public $itemDescription;
|
||||
/** @var string */
|
||||
public $itemEffects;
|
||||
/** @var int */
|
||||
public $hotbarSlot;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->itemDescription = $this->getString();
|
||||
$this->itemEffects = $this->getString();
|
||||
$this->hotbarSlot = $this->getLInt();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putString($this->itemDescription);
|
||||
$this->putString($this->itemEffects);
|
||||
$this->putLInt($this->hotbarSlot);
|
||||
}
|
||||
|
||||
|
@ -105,9 +105,15 @@ class LoginPacket extends DataPacket{
|
||||
$buffer = new BinaryStream($this->getString());
|
||||
|
||||
$this->chainData = json_decode($buffer->get($buffer->getLInt()), true);
|
||||
|
||||
$hasExtraData = false;
|
||||
foreach($this->chainData["chain"] as $chain){
|
||||
$webtoken = Utils::decodeJWT($chain);
|
||||
if(isset($webtoken["extraData"])){
|
||||
if($hasExtraData){
|
||||
throw new \RuntimeException("Found 'extraData' multiple times in key chain");
|
||||
}
|
||||
$hasExtraData = true;
|
||||
if(isset($webtoken["extraData"]["displayName"])){
|
||||
$this->username = $webtoken["extraData"]["displayName"];
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class PlayStatusPacket extends DataPacket{
|
||||
* @var int
|
||||
* Used to determine how to write the packet when we disconnect incompatible clients.
|
||||
*/
|
||||
public $protocol;
|
||||
public $protocol = ProtocolInfo::CURRENT_PROTOCOL;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->status = $this->getInt();
|
||||
|
@ -58,6 +58,11 @@ class QueryHandler{
|
||||
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.server.query.running", [$addr, $port]));
|
||||
}
|
||||
|
||||
private function debug(string $message) : void{
|
||||
//TODO: replace this with a proper prefixed logger
|
||||
$this->server->getLogger()->debug("[Query] $message");
|
||||
}
|
||||
|
||||
public function regenerateInfo(){
|
||||
$ev = $this->server->getQueryInformation();
|
||||
$this->longData = $ev->getLongQuery();
|
||||
@ -91,7 +96,8 @@ class QueryHandler{
|
||||
break;
|
||||
case self::STATISTICS: //Stat
|
||||
$token = Binary::readInt(substr($payload, 0, 4));
|
||||
if($token !== self::getTokenString($this->token, $address) and $token !== self::getTokenString($this->lastToken, $address)){
|
||||
if($token !== ($t1 = self::getTokenString($this->token, $address)) and $token !== ($t2 = self::getTokenString($this->lastToken, $address))){
|
||||
$this->debug("Bad token $token from $address $port, expected $t1 or $t2");
|
||||
break;
|
||||
}
|
||||
$reply = chr(self::STATISTICS);
|
||||
@ -108,6 +114,9 @@ class QueryHandler{
|
||||
}
|
||||
$interface->sendRawPacket($address, $port, $reply);
|
||||
break;
|
||||
default:
|
||||
$this->debug("Unhandled packet from $address $port: 0x" . bin2hex($packet));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +80,13 @@ class RCONInstance extends Thread{
|
||||
}
|
||||
|
||||
private function readPacket($client, ?int &$requestID, ?int &$packetType, ?string &$payload){
|
||||
$d = socket_read($client, 4);
|
||||
$d = @socket_read($client, 4);
|
||||
if($this->stop){
|
||||
return false;
|
||||
}elseif($d === false){
|
||||
if(socket_last_error($client) === SOCKET_ECONNRESET){ //client crashed, terminate connection
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}elseif($d === "" or strlen($d) < 4){
|
||||
return false;
|
||||
@ -174,7 +177,7 @@ class RCONInstance extends Thread{
|
||||
$disconnect[$id] = $sock;
|
||||
break;
|
||||
}
|
||||
if(strlen($payload) > 0){
|
||||
if($payload !== ""){
|
||||
$this->cmd = ltrim($payload);
|
||||
$this->synchronized(function(){
|
||||
$this->notifier->wakeupSleeper();
|
||||
|
@ -168,7 +168,7 @@ class BanEntry{
|
||||
}
|
||||
|
||||
$expire = trim(array_shift($str));
|
||||
if(strtolower($expire) !== "forever" and strlen($expire) > 0){
|
||||
if($expire !== "" and strtolower($expire) !== "forever"){
|
||||
$entry->setExpires(self::parseDate($expire));
|
||||
}
|
||||
if(empty($str)){
|
||||
|
@ -30,7 +30,7 @@ use pocketmine\timings\Timings;
|
||||
|
||||
class PermissibleBase implements Permissible{
|
||||
/** @var ServerOperator */
|
||||
private $opable = null;
|
||||
private $opable;
|
||||
|
||||
/** @var Permissible */
|
||||
private $parent = null;
|
||||
@ -59,24 +59,14 @@ class PermissibleBase implements Permissible{
|
||||
* @return bool
|
||||
*/
|
||||
public function isOp() : bool{
|
||||
if($this->opable === null){
|
||||
return false;
|
||||
}else{
|
||||
return $this->opable->isOp();
|
||||
}
|
||||
return $this->opable->isOp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setOp(bool $value){
|
||||
if($this->opable === null){
|
||||
throw new \LogicException("Cannot change op value as no ServerOperator is set");
|
||||
}else{
|
||||
$this->opable->setOp($value);
|
||||
}
|
||||
$this->opable->setOp($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,6 +256,7 @@ abstract class PluginBase implements Plugin{
|
||||
}
|
||||
|
||||
public function reloadConfig(){
|
||||
@mkdir($this->dataFolder);
|
||||
$this->config = new Config($this->configFile);
|
||||
if(($configStream = $this->getResource("config.yml")) !== null){
|
||||
$this->config->setDefaults(yaml_parse(Config::fixYAMLIndexes(stream_get_contents($configStream))));
|
||||
|
@ -102,9 +102,6 @@ class PluginManager{
|
||||
/** @var string|null */
|
||||
private $pluginDataDirectory;
|
||||
|
||||
/** @var TimingsHandler */
|
||||
public static $pluginParentTimer;
|
||||
|
||||
/**
|
||||
* @param Server $server
|
||||
* @param SimpleCommandMap $commandMap
|
||||
@ -416,7 +413,7 @@ class PluginManager{
|
||||
continue;
|
||||
}
|
||||
|
||||
if($pluginNumbers[2] > $serverNumbers[2]){ //If the plugin requires bug fixes in patches, being backwards compatible
|
||||
if($pluginNumbers[1] === $serverNumbers[1] and $pluginNumbers[2] > $serverNumbers[2]){ //If the plugin requires bug fixes in patches, being backwards compatible
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -596,11 +593,7 @@ class PluginManager{
|
||||
* @return bool
|
||||
*/
|
||||
public function isPluginEnabled(Plugin $plugin) : bool{
|
||||
if($plugin instanceof Plugin and isset($this->plugins[$plugin->getDescription()->getName()])){
|
||||
return $plugin->isEnabled();
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
return isset($this->plugins[$plugin->getDescription()->getName()]) and $plugin->isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -669,7 +662,7 @@ class PluginManager{
|
||||
}elseif(is_string($data["permission"])){
|
||||
$newCmd->setPermission($data["permission"]);
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Permission must be a string or boolean, " . gettype($data["permission"] . " given"));
|
||||
throw new \InvalidArgumentException("Permission must be a string or boolean, " . gettype($data["permission"]) . " given");
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,7 +780,7 @@ class PluginManager{
|
||||
|
||||
$reflection = new \ReflectionClass(get_class($listener));
|
||||
foreach($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method){
|
||||
if(!$method->isStatic()){
|
||||
if(!$method->isStatic() and $method->getDeclaringClass()->implementsInterface(Listener::class)){
|
||||
$tags = Utils::parseDocComment((string) $method->getDocComment());
|
||||
if(isset($tags["notHandler"])){
|
||||
continue;
|
||||
@ -848,7 +841,7 @@ class PluginManager{
|
||||
throw new PluginException("Plugin attempted to register " . $event . " while not enabled");
|
||||
}
|
||||
|
||||
$timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")", self::$pluginParentTimer);
|
||||
$timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")");
|
||||
|
||||
$this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled, $timings));
|
||||
}
|
||||
|
@ -86,8 +86,11 @@ class ZippedResourcePack implements ResourcePack{
|
||||
$archive->close();
|
||||
|
||||
$manifest = json_decode($manifestData);
|
||||
if($manifest === null or !self::verifyManifest($manifest)){
|
||||
throw new ResourcePackException("manifest.json is invalid or incomplete");
|
||||
if($manifest === null){
|
||||
throw new ResourcePackException("Failed to parse manifest.json: " . json_last_error_msg());
|
||||
}
|
||||
if(!self::verifyManifest($manifest)){
|
||||
throw new ResourcePackException("manifest.json is missing required fields");
|
||||
}
|
||||
|
||||
$this->manifest = $manifest;
|
||||
|
@ -295,7 +295,7 @@ class AsyncPool{
|
||||
}
|
||||
|
||||
$this->removeTask($task);
|
||||
}elseif($task->isTerminated() or $task->isCrashed()){
|
||||
}elseif($task->isCrashed()){
|
||||
$this->logger->critical("Could not execute asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": Task crashed");
|
||||
$this->removeTask($task, true);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ abstract class AsyncTask extends Collectable{
|
||||
}
|
||||
|
||||
public function isCrashed() : bool{
|
||||
return $this->crashed;
|
||||
return $this->crashed or $this->isTerminated();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,10 +113,14 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
|
||||
$pair->checkPairing();
|
||||
}
|
||||
if($this->doubleInventory === null){
|
||||
if(($pair->x + ($pair->z << 15)) > ($this->x + ($this->z << 15))){ //Order them correctly
|
||||
$this->doubleInventory = new DoubleChestInventory($pair, $this);
|
||||
if($pair->doubleInventory !== null){
|
||||
$this->doubleInventory = $pair->doubleInventory;
|
||||
}else{
|
||||
$this->doubleInventory = new DoubleChestInventory($this, $pair);
|
||||
if(($pair->x + ($pair->z << 15)) > ($this->x + ($this->z << 15))){ //Order them correctly
|
||||
$this->doubleInventory = new DoubleChestInventory($pair, $this);
|
||||
}else{
|
||||
$this->doubleInventory = new DoubleChestInventory($this, $pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
@ -103,6 +103,7 @@ abstract class Tile extends Position{
|
||||
public static function createTile($type, Level $level, CompoundTag $nbt, ...$args) : ?Tile{
|
||||
if(isset(self::$knownTiles[$type])){
|
||||
$class = self::$knownTiles[$type];
|
||||
/** @see Tile::__construct() */
|
||||
return new $class($level, $nbt, ...$args);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\timings;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\scheduler\TaskHandler;
|
||||
use pocketmine\tile\Tile;
|
||||
|
||||
@ -134,7 +133,7 @@ abstract class Timings{
|
||||
self::$timerEntityBaseTick = new TimingsHandler("** entityBaseTick");
|
||||
self::$timerLivingEntityBaseTick = new TimingsHandler("** livingEntityBaseTick");
|
||||
|
||||
self::$schedulerSyncTimer = new TimingsHandler("** Scheduler - Sync Tasks", PluginManager::$pluginParentTimer);
|
||||
self::$schedulerSyncTimer = new TimingsHandler("** Scheduler - Sync Tasks");
|
||||
self::$schedulerAsyncTimer = new TimingsHandler("** Scheduler - Async Tasks");
|
||||
|
||||
self::$playerCommandTimer = new TimingsHandler("** playerCommand");
|
||||
|
@ -111,7 +111,7 @@ class Config{
|
||||
* @return string
|
||||
*/
|
||||
public static function fixYAMLIndexes(string $str) : string{
|
||||
return preg_replace("#^([ ]*)([a-zA-Z_]{1}[ ]*)\\:$#m", "$1\"$2\":", $str);
|
||||
return preg_replace("#^( *)(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)( *)\:#m", "$1\"$2\"$3:", $str);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,7 +144,6 @@ class Config{
|
||||
$content = file_get_contents($this->file);
|
||||
switch($this->type){
|
||||
case Config::PROPERTIES:
|
||||
case Config::CNF:
|
||||
$this->parseProperties($content);
|
||||
break;
|
||||
case Config::JSON:
|
||||
@ -197,7 +196,6 @@ class Config{
|
||||
$content = null;
|
||||
switch($this->type){
|
||||
case Config::PROPERTIES:
|
||||
case Config::CNF:
|
||||
$content = $this->writeProperties();
|
||||
break;
|
||||
case Config::JSON:
|
||||
@ -550,7 +548,7 @@ class Config{
|
||||
* @param string $content
|
||||
*/
|
||||
private function parseProperties(string $content){
|
||||
if(preg_match_all('/([a-zA-Z0-9\-_\.]*)=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches
|
||||
if(preg_match_all('/([a-zA-Z0-9\-_\.]+)[ \t]*=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches
|
||||
foreach($matches[1] as $i => $k){
|
||||
$v = trim($matches[2][$i]);
|
||||
switch(strtolower($v)){
|
||||
|
@ -204,10 +204,16 @@ class MainLogger extends \AttachableThreadedLogger{
|
||||
$errno = $errorConversion[$errno] ?? $errno;
|
||||
$errstr = preg_replace('/\s+/', ' ', trim($errstr));
|
||||
$errfile = Utils::cleanPath($errfile);
|
||||
$this->log($type, get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline");
|
||||
foreach(Utils::getTrace(0, $trace) as $i => $line){
|
||||
$this->debug($line, true);
|
||||
}
|
||||
|
||||
$message = get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
|
||||
$stack = Utils::getTrace(0, $trace);
|
||||
|
||||
$this->synchronized(function() use ($type, $message, $stack) : void{
|
||||
$this->log($type, $message);
|
||||
foreach($stack as $line){
|
||||
$this->debug($line, true);
|
||||
}
|
||||
});
|
||||
|
||||
$this->syncFlushBuffer();
|
||||
}
|
||||
@ -259,19 +265,22 @@ class MainLogger extends \AttachableThreadedLogger{
|
||||
}
|
||||
|
||||
$message = sprintf($this->format, date("H:i:s", $now), $color, $threadName, $prefix, $message);
|
||||
$cleanMessage = TextFormat::clean($message);
|
||||
|
||||
if($this->mainThreadHasFormattingCodes and Terminal::hasFormattingCodes()){ //hasFormattingCodes() lazy-inits colour codes because we don't know if they've been registered on this thread
|
||||
echo Terminal::toANSI($message) . PHP_EOL;
|
||||
}else{
|
||||
echo $cleanMessage . PHP_EOL;
|
||||
}
|
||||
$this->synchronized(function() use ($message, $level, $now) : void{
|
||||
$cleanMessage = TextFormat::clean($message);
|
||||
|
||||
foreach($this->attachments as $attachment){
|
||||
$attachment->call($level, $message);
|
||||
}
|
||||
if($this->mainThreadHasFormattingCodes and Terminal::hasFormattingCodes()){ //hasFormattingCodes() lazy-inits colour codes because we don't know if they've been registered on this thread
|
||||
echo Terminal::toANSI($message) . PHP_EOL;
|
||||
}else{
|
||||
echo $cleanMessage . PHP_EOL;
|
||||
}
|
||||
|
||||
$this->logStream[] = date("Y-m-d", $now) . " " . $cleanMessage . PHP_EOL;
|
||||
foreach($this->attachments as $attachment){
|
||||
$attachment->call($level, $message);
|
||||
}
|
||||
|
||||
$this->logStream[] = date("Y-m-d", $now) . " " . $cleanMessage . PHP_EOL;
|
||||
});
|
||||
}
|
||||
|
||||
public function syncFlushBuffer(){
|
||||
|
@ -630,9 +630,9 @@ class Utils{
|
||||
* @return string[] an array of tagName => tag value. If the tag has no value, an empty string is used as the value.
|
||||
*/
|
||||
public static function parseDocComment(string $docComment) : array{
|
||||
preg_match_all('/^[\t ]*\* @([a-zA-Z]+)(?:[\t ]+(.+))?[\t ]*$/m', $docComment, $matches);
|
||||
preg_match_all('/(*ANYCRLF)^[\t ]*\* @([a-zA-Z]+)(?:[\t ]+(.+))?[\t ]*$/m', $docComment, $matches);
|
||||
|
||||
return array_combine($matches[1], array_map("trim", $matches[2]));
|
||||
return array_combine($matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
71
tests/phpunit/utils/ConfigTest.php
Normal file
71
tests/phpunit/utils/ConfigTest.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ConfigTest extends TestCase{
|
||||
|
||||
/**
|
||||
* @return \Generator
|
||||
*/
|
||||
public function fixYamlIndexesProvider() : \Generator{
|
||||
yield ["x: 1\ny: 2\nz: 3\n", [
|
||||
"x" => 1,
|
||||
"y" => 2,
|
||||
"z" => 3
|
||||
]];
|
||||
yield [" x : 1\n y : 2\n z : 3\n", [
|
||||
"x" => 1,
|
||||
"y" => 2,
|
||||
"z" => 3
|
||||
]];
|
||||
yield ["parent:\n x: 1\n y: 2\n z: 3\n", [
|
||||
"parent" => [
|
||||
"x" => 1,
|
||||
"y" => 2,
|
||||
"z" => 3
|
||||
]
|
||||
]];
|
||||
yield ["yes: notransform", [
|
||||
"yes" => "notransform"
|
||||
]];
|
||||
yield ["on: 1\nyes: true", [ //this would previously have resulted in a key collision
|
||||
"on" => 1,
|
||||
"yes" => true
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider fixYamlIndexesProvider
|
||||
*
|
||||
* @param string $test
|
||||
* @param array $expected
|
||||
*/
|
||||
public function testFixYamlIndexes(string $test, array $expected) : void{
|
||||
$fixed = Config::fixYAMLIndexes($test);
|
||||
$decoded = yaml_parse($fixed);
|
||||
self::assertEquals($expected, $decoded);
|
||||
}
|
||||
}
|
50
tests/phpunit/utils/UtilsTest.php
Normal file
50
tests/phpunit/utils/UtilsTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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\utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UtilsTest extends TestCase{
|
||||
|
||||
public function parseDocCommentNewlineProvider() : array{
|
||||
return [
|
||||
["\t/**\r\n\t * @param PlayerJoinEvent \$event\r\n\t * @priority HIGHEST\r\n\t * @notHandler\r\n\t */"],
|
||||
["\t/**\n\t * @param PlayerJoinEvent \$event\n\t * @priority HIGHEST\n\t * @notHandler\n\t */"],
|
||||
["\t/**\r\t * @param PlayerJoinEvent \$event\r\t * @priority HIGHEST\r\t * @notHandler\r\t */"]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $docComment
|
||||
* @dataProvider parseDocCommentNewlineProvider
|
||||
*/
|
||||
public function testParseDocCommentNewlines(string $docComment) : void{
|
||||
$tags = Utils::parseDocComment($docComment);
|
||||
|
||||
self::assertArrayHasKey("notHandler", $tags);
|
||||
self::assertEquals("", $tags["notHandler"]);
|
||||
self::assertArrayHasKey("priority", $tags);
|
||||
self::assertEquals("HIGHEST", $tags["priority"]);
|
||||
}
|
||||
}
|
Submodule tests/plugins/PocketMine-TesterPlugin deleted from 5fd76d5718
7
tests/plugins/TesterPlugin/plugin.yml
Normal file
7
tests/plugins/TesterPlugin/plugin.yml
Normal file
@ -0,0 +1,7 @@
|
||||
name: TesterPlugin
|
||||
main: pmmp\TesterPlugin\Main
|
||||
version: 0.1.0
|
||||
api: [3.0.0]
|
||||
load: POSTWORLD
|
||||
author: pmmp
|
||||
description: Plugin used to run tests on PocketMine-MP
|
@ -0,0 +1,50 @@
|
||||
<?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 pmmp\TesterPlugin;
|
||||
|
||||
use pocketmine\scheduler\Task;
|
||||
|
||||
class CheckTestCompletionTask extends Task{
|
||||
|
||||
/** @var Main */
|
||||
private $plugin;
|
||||
|
||||
public function __construct(Main $plugin){
|
||||
$this->plugin = $plugin;
|
||||
}
|
||||
|
||||
public function onRun(int $currentTick){
|
||||
$test = $this->plugin->getCurrentTest();
|
||||
if($test === null){
|
||||
if(!$this->plugin->startNextTest()){
|
||||
$this->plugin->getScheduler()->cancelTask($this->getHandler()->getTaskId());
|
||||
$this->plugin->onAllTestsCompleted();
|
||||
}
|
||||
}elseif($test->isFinished() or $test->isTimedOut()){
|
||||
$this->plugin->onTestCompleted($test);
|
||||
}else{
|
||||
$test->tick();
|
||||
}
|
||||
}
|
||||
}
|
109
tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php
Normal file
109
tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?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 pmmp\TesterPlugin;
|
||||
|
||||
use pocketmine\event\Listener;
|
||||
use pocketmine\event\server\ServerCommandEvent;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
|
||||
class Main extends PluginBase implements Listener{
|
||||
|
||||
/** @var Test[] */
|
||||
protected $waitingTests = [];
|
||||
/** @var Test|null */
|
||||
protected $currentTest = null;
|
||||
/** @var Test[] */
|
||||
protected $completedTests = [];
|
||||
/** @var int */
|
||||
protected $currentTestNumber = 0;
|
||||
|
||||
public function onEnable(){
|
||||
$this->getServer()->getPluginManager()->registerEvents($this, $this);
|
||||
$this->getScheduler()->scheduleRepeatingTask(new CheckTestCompletionTask($this), 10);
|
||||
|
||||
$this->waitingTests = [
|
||||
new tests\AsyncTaskMemoryLeakTest($this),
|
||||
new tests\AsyncTaskMainLoggerTest($this)
|
||||
];
|
||||
}
|
||||
|
||||
public function onServerCommand(ServerCommandEvent $event){
|
||||
//The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to
|
||||
//run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may
|
||||
//be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests
|
||||
//have completed.
|
||||
if($event->getCommand() === "stop"){
|
||||
$event->setCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Test|null
|
||||
*/
|
||||
public function getCurrentTest(){
|
||||
return $this->currentTest;
|
||||
}
|
||||
|
||||
public function startNextTest() : bool{
|
||||
$this->currentTest = array_shift($this->waitingTests);
|
||||
if($this->currentTest !== null){
|
||||
$this->getLogger()->notice("Running test #" . (++$this->currentTestNumber) . " (" . $this->currentTest->getName() . ")");
|
||||
$this->currentTest->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function onTestCompleted(Test $test){
|
||||
$message = "Finished test #" . $this->currentTestNumber . " (" . $test->getName() . "): ";
|
||||
switch($test->getResult()){
|
||||
case Test::RESULT_OK:
|
||||
$message .= "PASS";
|
||||
break;
|
||||
case Test::RESULT_FAILED:
|
||||
$message .= "FAIL";
|
||||
break;
|
||||
case Test::RESULT_ERROR:
|
||||
$message .= "ERROR";
|
||||
break;
|
||||
case Test::RESULT_WAITING:
|
||||
$message .= "TIMEOUT";
|
||||
break;
|
||||
default:
|
||||
$message .= "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
$this->getLogger()->notice($message);
|
||||
|
||||
$this->completedTests[$this->currentTestNumber] = $test;
|
||||
$this->currentTest = null;
|
||||
}
|
||||
|
||||
public function onAllTestsCompleted(){
|
||||
$this->getLogger()->notice("All tests finished, stopping the server");
|
||||
$this->getServer()->shutdown();
|
||||
}
|
||||
}
|
87
tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php
Normal file
87
tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?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 pmmp\TesterPlugin;
|
||||
|
||||
abstract class Test{
|
||||
const RESULT_WAITING = -1;
|
||||
const RESULT_OK = 0;
|
||||
const RESULT_FAILED = 1;
|
||||
const RESULT_ERROR = 2;
|
||||
|
||||
private $plugin;
|
||||
private $result = Test::RESULT_WAITING;
|
||||
private $startTime;
|
||||
private $timeout = 60; //seconds
|
||||
|
||||
public function __construct(Main $plugin){
|
||||
$this->plugin = $plugin;
|
||||
}
|
||||
|
||||
public function getPlugin() : Main{
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
final public function start(){
|
||||
$this->startTime = time();
|
||||
try{
|
||||
$this->run();
|
||||
}catch(TestFailedException $e){
|
||||
$this->getPlugin()->getLogger()->error($e->getMessage());
|
||||
$this->setResult(Test::RESULT_FAILED);
|
||||
}catch(\Throwable $e){
|
||||
$this->getPlugin()->getLogger()->logException($e);
|
||||
$this->setResult(Test::RESULT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function tick(){
|
||||
|
||||
}
|
||||
|
||||
abstract public function run();
|
||||
|
||||
public function isFinished() : bool{
|
||||
return $this->result !== Test::RESULT_WAITING;
|
||||
}
|
||||
|
||||
public function isTimedOut() : bool{
|
||||
return !$this->isFinished() and time() - $this->timeout > $this->startTime;
|
||||
}
|
||||
|
||||
protected function setTimeout(int $timeout){
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function getResult() : int{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public function setResult(int $result){
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
abstract public function getName() : string;
|
||||
|
||||
abstract public function getDescription() : string;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?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 pmmp\TesterPlugin;
|
||||
|
||||
class TestFailedException extends \Exception{
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?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 pmmp\TesterPlugin\tests;
|
||||
|
||||
use pmmp\TesterPlugin\Test;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
class AsyncTaskMainLoggerTest extends Test{
|
||||
|
||||
public function run(){
|
||||
$this->getPlugin()->getServer()->getAsyncPool()->submitTask(new class($this) extends AsyncTask{
|
||||
|
||||
/** @var bool */
|
||||
protected $success = false;
|
||||
|
||||
public function __construct(AsyncTaskMainLoggerTest $testObject){
|
||||
$this->storeLocal($testObject);
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
ob_start();
|
||||
MainLogger::getLogger()->info("Testing");
|
||||
if(strpos(ob_get_contents(), "Testing") !== false){
|
||||
$this->success = true;
|
||||
}
|
||||
ob_end_flush();
|
||||
}
|
||||
|
||||
public function onCompletion(Server $server){
|
||||
/** @var AsyncTaskMainLoggerTest $test */
|
||||
$test = $this->fetchLocal();
|
||||
$test->setResult($this->success ? Test::RESULT_OK : Test::RESULT_FAILED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "MainLogger::getLogger() works in AsyncTasks";
|
||||
}
|
||||
|
||||
public function getDescription() : string{
|
||||
return "Verifies that the MainLogger is accessible by MainLogger::getLogger() in an AsyncTask";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?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 pmmp\TesterPlugin\tests;
|
||||
|
||||
use pmmp\TesterPlugin\Test;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
|
||||
class AsyncTaskMemoryLeakTest extends Test{
|
||||
|
||||
public function run(){
|
||||
$this->getPlugin()->getServer()->getAsyncPool()->submitTask(new TestAsyncTask());
|
||||
}
|
||||
|
||||
public function tick(){
|
||||
if(TestAsyncTask::$destroyed === true){
|
||||
$this->setResult(Test::RESULT_OK);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "AsyncTask memory leak after completion";
|
||||
}
|
||||
|
||||
public function getDescription() : string{
|
||||
return "Regression test for AsyncTasks objects not being destroyed after completion";
|
||||
}
|
||||
}
|
||||
|
||||
class TestAsyncTask extends AsyncTask{
|
||||
public static $destroyed = false;
|
||||
|
||||
public function onRun(){
|
||||
usleep(50 * 1000); //1 server tick
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
self::$destroyed = true;
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ fi
|
||||
mkdir "$DATA_DIR"
|
||||
mkdir "$PLUGINS_DIR"
|
||||
mv DevTools.phar "$PLUGINS_DIR"
|
||||
cp -r tests/plugins/PocketMine-TesterPlugin "$PLUGINS_DIR"
|
||||
cp -r tests/plugins/TesterPlugin "$PLUGINS_DIR"
|
||||
echo -e "stop\n" | "$PHP_BINARY" PocketMine-MP.phar --no-wizard --disable-ansi --disable-readline --debug.level=2 --data="$DATA_DIR" --plugins="$PLUGINS_DIR" --anonymous-statistics.enabled=0 --settings.async-workers="$PM_WORKERS" --settings.enable-dev-builds=1
|
||||
|
||||
output=$(grep '\[TesterPlugin\]' "$DATA_DIR/server.log")
|
||||
|
Reference in New Issue
Block a user