mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-15 22:05:06 +00:00
Compare commits
212 Commits
Author | SHA1 | Date | |
---|---|---|---|
a66dd4a7d9 | |||
3617eba4a3 | |||
e79cc98883 | |||
d259b2c9ee | |||
10fa74b417 | |||
17ceb27af4 | |||
adbd1c7bed | |||
cf20e626e2 | |||
d75c830a7e | |||
722924a779 | |||
60e1b29462 | |||
5b511f6d06 | |||
426dee04a6 | |||
bb1944ca40 | |||
d1a20ecb4a | |||
f6a8ec83a1 | |||
28137efb53 | |||
7b0836d399 | |||
cea146e335 | |||
8db1ccc1ae | |||
5d56030afa | |||
d9c251b613 | |||
8085b81f5c | |||
33d3fff3c5 | |||
7c092b93b4 | |||
aa05650994 | |||
758d9b9784 | |||
24a6bf7365 | |||
9a5d51fd3d | |||
6a7f39978b | |||
c52e1ea9f9 | |||
a0bb747d6d | |||
4bc0d850b1 | |||
97583c8b04 | |||
107192c753 | |||
870f9abc20 | |||
0e2bbc44db | |||
d9768abe47 | |||
e9b84ecc8b | |||
c83d12790e | |||
5863d4c066 | |||
7d54d18732 | |||
bfbc845efa | |||
2ff4228fb7 | |||
06c4f31db7 | |||
6c70e84fa2 | |||
7d0e631a75 | |||
65b751d080 | |||
27effff403 | |||
a940cc5b5e | |||
15e654131c | |||
6e6cda91ce | |||
53a76c0d14 | |||
69500fe183 | |||
5af4dd20df | |||
c7d58db7eb | |||
a3b78236eb | |||
d8e27e6081 | |||
14a2ffa51b | |||
c447d51e3f | |||
9f4722f537 | |||
cb04f287eb | |||
f649ef5195 | |||
b615cad22d | |||
b93e219231 | |||
a4a9309193 | |||
56ee957fda | |||
1193efd69e | |||
f466fd5568 | |||
d5a5209334 | |||
3a85e6cab9 | |||
bca493a682 | |||
ba12dfafd6 | |||
e09087de26 | |||
888dba704b | |||
511249c562 | |||
17f1bf5512 | |||
05d9bb45d0 | |||
e5cda34548 | |||
032b20f659 | |||
fe6d546190 | |||
3e6f70ddf6 | |||
bea634a9b7 | |||
8daf3dc8b4 | |||
d419d4308f | |||
9ca38ba868 | |||
424c50e1e9 | |||
566f3c6262 | |||
0d05dcec08 | |||
986077e03c | |||
ddcb2f002a | |||
c496480d2b | |||
2eda8cfad3 | |||
21e7b5ea43 | |||
8304675af7 | |||
ebffff0caa | |||
0dc4bd36e1 | |||
9d17c9a09d | |||
bfa415e108 | |||
b66095cb36 | |||
0336ae8229 | |||
4b3e17e681 | |||
5424644ca1 | |||
aa7c4bc64d | |||
df8e10cad9 | |||
d98a6e566c | |||
ade2be9eee | |||
39ed6a7cdf | |||
bec5aaa54b | |||
cf29ab1f17 | |||
c5c5a53a13 | |||
63a65680ac | |||
47cd6fe105 | |||
f582b5a3db | |||
7f0fa2ac3d | |||
f3b2bcfd13 | |||
c947909c2e | |||
09dadc72bc | |||
ca541032ae | |||
bcf9915082 | |||
8d6dc4e188 | |||
12d8d925c8 | |||
f3f229ef7c | |||
6614183c7f | |||
7ebf3c7bf4 | |||
334caaaa34 | |||
6fcaef068f | |||
c09ad9263b | |||
4cc2f037a9 | |||
99045fe21a | |||
bda271ca63 | |||
b3f2396ea5 | |||
ab0510cb37 | |||
9a423be1db | |||
08be51dc23 | |||
94352782d5 | |||
8fae79f85b | |||
9a2845640b | |||
580f71d496 | |||
24f11779f2 | |||
706c620d04 | |||
951870e6ec | |||
1405099768 | |||
1464487945 | |||
40c28f4d26 | |||
90bf94f8f7 | |||
522ef042a7 | |||
76ee6bc298 | |||
04f20c703c | |||
efe4b0cd3a | |||
527d8e9374 | |||
c1c70a8a98 | |||
b7f15b6574 | |||
6ab2fa84da | |||
b480c63060 | |||
f6b54f5116 | |||
89bfc380e3 | |||
40030e9800 | |||
ad1cf38c21 | |||
5d769147ca | |||
6f00a30ad7 | |||
b4bf6901e3 | |||
71c3c34976 | |||
16c253d7a9 | |||
7efe767f1f | |||
2e18fe710c | |||
878dd3b842 | |||
478a131aa5 | |||
53068caf3c | |||
fe7ad7a5b3 | |||
24f749a933 | |||
af80aefd45 | |||
1d5c741f28 | |||
3a373b880d | |||
1b7cd156aa | |||
7a164a8254 | |||
066c990301 | |||
287ff8d7bf | |||
1087212d75 | |||
0c350f2f57 | |||
bfcef2ab6b | |||
2994d0f3ae | |||
57cc0ebe75 | |||
7554d9a370 | |||
32574118ea | |||
5a3135659b | |||
b90d7d1839 | |||
670b940837 | |||
6cad7be3ef | |||
28a72a93b4 | |||
0f0d12bebc | |||
dfc11abf2d | |||
17eef9f902 | |||
b04319a4ab | |||
0afbf6c547 | |||
ec2cca04a7 | |||
272b76d24c | |||
8c672cb7c8 | |||
4d9368f205 | |||
97c267c70c | |||
85a3c0e7dc | |||
2f70a1eefb | |||
7ba6e92b6c | |||
47c862bc38 | |||
860c20109b | |||
1c0b49343c | |||
814a949580 | |||
b393f5f17e | |||
f1970492c1 | |||
4c9ca53b32 | |||
390db976e5 | |||
98ac534820 |
@ -27,7 +27,7 @@
|
||||
"pocketmine/raklib": "^0.12.0",
|
||||
"pocketmine/spl": "^0.3.0",
|
||||
"pocketmine/binaryutils": "^0.1.0",
|
||||
"pocketmine/nbt": "^0.2.0",
|
||||
"pocketmine/nbt": "^0.2.1",
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/snooze": "^0.1.0"
|
||||
},
|
||||
|
14
composer.lock
generated
14
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "2670b9e2a730ff758909be8b9e9d609a",
|
||||
"content-hash": "3536995c56bfc3dbd6ccc0994e88a636",
|
||||
"packages": [
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -76,16 +76,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c"
|
||||
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
|
||||
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/a4ee39f313c6870153fb7c7e513b211217f7daf8",
|
||||
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -109,10 +109,10 @@
|
||||
],
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.0",
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.1",
|
||||
"issues": "https://github.com/pmmp/NBT/issues"
|
||||
},
|
||||
"time": "2018-06-13T09:56:00+00:00"
|
||||
"time": "2018-09-04T10:36:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
|
@ -243,6 +243,9 @@ class MemoryManager{
|
||||
|
||||
if($this->garbageCollectionAsync){
|
||||
$pool = $this->server->getAsyncPool();
|
||||
if(($w = $pool->shutdownUnusedWorkers()) > 0){
|
||||
$this->server->getLogger()->debug("Shut down $w idle async pool workers");
|
||||
}
|
||||
foreach($pool->getRunningWorkers() as $i){
|
||||
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
|
||||
}
|
||||
|
@ -32,11 +32,9 @@ use pocketmine\entity\Effect;
|
||||
use pocketmine\entity\EffectInstance;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Human;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\object\ItemEntity;
|
||||
use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\entity\Skin;
|
||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\inventory\InventoryCloseEvent;
|
||||
@ -68,6 +66,8 @@ use pocketmine\event\player\PlayerToggleSneakEvent;
|
||||
use pocketmine\event\player\PlayerToggleSprintEvent;
|
||||
use pocketmine\event\player\PlayerTransferEvent;
|
||||
use pocketmine\event\server\DataPacketSendEvent;
|
||||
use pocketmine\form\Form;
|
||||
use pocketmine\form\FormValidationException;
|
||||
use pocketmine\inventory\CraftingGrid;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\PlayerCursorInventory;
|
||||
@ -77,6 +77,8 @@ use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\inventory\transaction\TransactionValidationException;
|
||||
use pocketmine\item\Consumable;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\EnchantmentInstance;
|
||||
use pocketmine\item\enchantment\MeleeWeaponEnchantment;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\WritableBook;
|
||||
use pocketmine\item\WrittenBook;
|
||||
@ -116,6 +118,7 @@ use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LoginPacket;
|
||||
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\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
|
||||
@ -146,6 +149,7 @@ use pocketmine\network\SourceInterface;
|
||||
use pocketmine\permission\PermissibleBase;
|
||||
use pocketmine\permission\PermissionAttachment;
|
||||
use pocketmine\permission\PermissionAttachmentInfo;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\resourcepacks\ResourcePack;
|
||||
use pocketmine\tile\ItemFrame;
|
||||
@ -193,9 +197,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
*/
|
||||
protected $sessionAdapter;
|
||||
|
||||
/** @var int */
|
||||
protected $protocol = -1;
|
||||
|
||||
/** @var string */
|
||||
protected $ip;
|
||||
/** @var int */
|
||||
@ -322,6 +323,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/** @var int[] ID => ticks map */
|
||||
protected $usedItemsCooldown = [];
|
||||
|
||||
/** @var int */
|
||||
protected $formIdCounter = 0;
|
||||
/** @var Form[] */
|
||||
protected $forms = [];
|
||||
|
||||
/**
|
||||
* @return TranslationContainer|string
|
||||
*/
|
||||
@ -637,8 +643,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function recalculatePermissions(){
|
||||
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
$permManager = PermissionManager::getInstance();
|
||||
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
|
||||
if($this->perm === null){
|
||||
return;
|
||||
@ -647,10 +654,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->perm->recalculatePermissions();
|
||||
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
|
||||
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
}
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
|
||||
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
}
|
||||
|
||||
if($this->spawned){
|
||||
@ -1018,10 +1025,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
|
||||
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
|
||||
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
}
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
|
||||
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this,
|
||||
@ -1839,8 +1846,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->protocol = $packet->protocol;
|
||||
|
||||
if($packet->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
|
||||
if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){
|
||||
$this->sendPlayStatus(PlayStatusPacket::LOGIN_FAILED_CLIENT, true);
|
||||
@ -1923,7 +1928,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
public function sendPlayStatus(int $status, bool $immediate = false){
|
||||
$pk = new PlayStatusPacket();
|
||||
$pk->status = $status;
|
||||
$pk->protocol = $this->protocol;
|
||||
$this->sendDataPacket($pk, false, $immediate);
|
||||
}
|
||||
|
||||
@ -2031,7 +2035,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
|
||||
$manager = $this->server->getResourcePackManager();
|
||||
foreach($packet->packIds as $uuid){
|
||||
$pack = $manager->getPackById($uuid);
|
||||
$pack = $manager->getPackById(substr($uuid, 0, strpos($uuid, "_"))); //dirty hack for mojang's dirty hack for versions
|
||||
if(!($pack instanceof ResourcePack)){
|
||||
//Client requested a resource pack but we don't have it available on the server
|
||||
$this->close("", "disconnectionScreen.resourcePack", true);
|
||||
@ -2499,6 +2503,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints());
|
||||
|
||||
$meleeEnchantmentDamage = 0;
|
||||
/** @var EnchantmentInstance[] $meleeEnchantments */
|
||||
$meleeEnchantments = [];
|
||||
foreach($heldItem->getEnchantments() as $enchantment){
|
||||
$type = $enchantment->getType();
|
||||
if($type instanceof MeleeWeaponEnchantment and $type->isApplicableTo($target)){
|
||||
$meleeEnchantmentDamage += $type->getDamageBonus($enchantment->getLevel());
|
||||
$meleeEnchantments[] = $enchantment;
|
||||
}
|
||||
}
|
||||
$ev->setModifier($meleeEnchantmentDamage, EntityDamageEvent::MODIFIER_WEAPON_ENCHANTMENTS);
|
||||
|
||||
if($cancelled){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
@ -2526,6 +2543,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
foreach($meleeEnchantments as $enchantment){
|
||||
$type = $enchantment->getType();
|
||||
assert($type instanceof MeleeWeaponEnchantment);
|
||||
$type->onPostAttack($this, $target, $enchantment->getLevel());
|
||||
}
|
||||
|
||||
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
|
||||
@ -2698,7 +2721,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$target = $this->level->getBlock($pos);
|
||||
|
||||
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, $target->getId() === 0 ? PlayerInteractEvent::LEFT_CLICK_AIR : PlayerInteractEvent::LEFT_CLICK_BLOCK);
|
||||
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
|
||||
if($this->level->checkSpawnProtection($this, $target)){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
@ -2762,7 +2785,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
break; //TODO
|
||||
case PlayerActionPacket::ACTION_CONTINUE_BREAK:
|
||||
$block = $this->level->getBlock($pos);
|
||||
$this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, BlockFactory::toStaticRuntimeId($block->getId(), $block->getDamage()) | ($packet->face << 24));
|
||||
$this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, $block->getRuntimeId() | ($packet->face << 24));
|
||||
//TODO: destroy-progress level event
|
||||
break;
|
||||
case PlayerActionPacket::ACTION_START_SWIMMING:
|
||||
@ -3147,12 +3170,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
*
|
||||
* @param string $reason
|
||||
* @param bool $isAdmin
|
||||
* @param TextContainer|string $quitMessage
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function kick(string $reason = "", bool $isAdmin = true) : bool{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $this->getLeaveMessage()));
|
||||
public function kick(string $reason = "", bool $isAdmin = true, $quitMessage = null) : bool{
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage()));
|
||||
if(!$ev->isCancelled()){
|
||||
$reason = $ev->getReason();
|
||||
$message = $reason;
|
||||
if($isAdmin){
|
||||
if(!$this->isBanned()){
|
||||
@ -3330,6 +3355,45 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->dataPacket($pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Form to the player, or queue to send it if a form is already open.
|
||||
*
|
||||
* @param Form $form
|
||||
*/
|
||||
public function sendForm(Form $form) : void{
|
||||
$id = $this->formIdCounter++;
|
||||
$pk = new ModalFormRequestPacket();
|
||||
$pk->formId = $id;
|
||||
$pk->formData = json_encode($form);
|
||||
if($this->dataPacket($pk)){
|
||||
$this->forms[$id] = $form;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $formId
|
||||
* @param mixed $responseData
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onFormSubmit(int $formId, $responseData) : bool{
|
||||
if(!isset($this->forms[$formId])){
|
||||
$this->server->getLogger()->debug("Got unexpected response for form $formId");
|
||||
return false;
|
||||
}
|
||||
|
||||
try{
|
||||
$this->forms[$formId]->handleResponse($this, $responseData);
|
||||
}catch(FormValidationException $e){
|
||||
$this->server->getLogger()->critical("Failed to validate form " . get_class($this->forms[$formId]) . ": " . $e->getMessage());
|
||||
$this->server->getLogger()->logException($e);
|
||||
}finally{
|
||||
unset($this->forms[$formId]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note for plugin developers: use kick() with the isAdmin
|
||||
* flag set to kick without the "Kicked by admin" part instead of this method.
|
||||
@ -3350,8 +3414,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->interface->close($this, $notify ? $reason : "");
|
||||
$this->sessionAdapter = null;
|
||||
|
||||
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
|
||||
$this->stopSleep();
|
||||
|
||||
@ -3500,123 +3564,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
protected function onDeath() : void{
|
||||
$message = "death.attack.generic";
|
||||
|
||||
$params = [
|
||||
$this->getDisplayName()
|
||||
];
|
||||
|
||||
$cause = $this->getLastDamageCause();
|
||||
|
||||
switch($cause === null ? EntityDamageEvent::CAUSE_CUSTOM : $cause->getCause()){
|
||||
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
|
||||
if($cause instanceof EntityDamageByEntityEvent){
|
||||
$e = $cause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.player";
|
||||
$params[] = $e->getDisplayName();
|
||||
break;
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.mob";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}else{
|
||||
$params[] = "Unknown";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_PROJECTILE:
|
||||
if($cause instanceof EntityDamageByEntityEvent){
|
||||
$e = $cause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.arrow";
|
||||
$params[] = $e->getDisplayName();
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.arrow";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}else{
|
||||
$params[] = "Unknown";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_SUICIDE:
|
||||
$message = "death.attack.generic";
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_VOID:
|
||||
$message = "death.attack.outOfWorld";
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_FALL:
|
||||
if($cause instanceof EntityDamageEvent){
|
||||
if($cause->getFinalDamage() > 2){
|
||||
$message = "death.fell.accident.generic";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$message = "death.attack.fall";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_SUFFOCATION:
|
||||
$message = "death.attack.inWall";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_LAVA:
|
||||
$message = "death.attack.lava";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_FIRE:
|
||||
$message = "death.attack.onFire";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_FIRE_TICK:
|
||||
$message = "death.attack.inFire";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_DROWNING:
|
||||
$message = "death.attack.drown";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_CONTACT:
|
||||
if($cause instanceof EntityDamageByBlockEvent){
|
||||
if($cause->getDamager()->getId() === Block::CACTUS){
|
||||
$message = "death.attack.cactus";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
|
||||
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
|
||||
if($cause instanceof EntityDamageByEntityEvent){
|
||||
$e = $cause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.explosion.player";
|
||||
$params[] = $e->getDisplayName();
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.explosion.player";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
$message = "death.attack.explosion";
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_MAGIC:
|
||||
$message = "death.attack.magic";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_CUSTOM:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//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->doCloseInventory();
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)));
|
||||
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops()));
|
||||
|
||||
if(!$ev->getKeepInventory()){
|
||||
foreach($ev->getDrops() as $item){
|
||||
|
@ -37,7 +37,7 @@ namespace pocketmine {
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.1.5";
|
||||
const BASE_VERSION = "3.2.4";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
||||
@ -53,83 +53,89 @@ namespace pocketmine {
|
||||
* Enjoy it as much as I did writing it. I don't want to do it again.
|
||||
*/
|
||||
|
||||
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
|
||||
critical_error(\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . ".");
|
||||
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(PHP_INT_SIZE < 8){
|
||||
critical_error("Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported.");
|
||||
critical_error("Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.");
|
||||
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Dependencies check */
|
||||
|
||||
$errors = 0;
|
||||
|
||||
if(php_sapi_name() !== "cli"){
|
||||
critical_error("You must run " . \pocketmine\NAME . " using the CLI.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
$extensions = [
|
||||
"bcmath" => "BC Math",
|
||||
"curl" => "cURL",
|
||||
"ctype" => "ctype",
|
||||
"date" => "Date",
|
||||
"hash" => "Hash",
|
||||
"json" => "JSON",
|
||||
"mbstring" => "Multibyte String",
|
||||
"openssl" => "OpenSSL",
|
||||
"pcre" => "PCRE",
|
||||
"phar" => "Phar",
|
||||
"pthreads" => "pthreads",
|
||||
"reflection" => "Reflection",
|
||||
"sockets" => "Sockets",
|
||||
"spl" => "SPL",
|
||||
"yaml" => "YAML",
|
||||
"zip" => "Zip",
|
||||
"zlib" => "Zlib"
|
||||
];
|
||||
|
||||
foreach($extensions as $ext => $name){
|
||||
if(!extension_loaded($ext)){
|
||||
critical_error("Unable to find the $name ($ext) extension.");
|
||||
++$errors;
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
function check_platform_dependencies(){
|
||||
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
|
||||
//If PHP version isn't high enough, anything below might break, so don't bother checking it.
|
||||
return [
|
||||
\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . "."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("pthreads")){
|
||||
$pthreads_version = phpversion("pthreads");
|
||||
if(substr_count($pthreads_version, ".") < 2){
|
||||
$pthreads_version = "0.$pthreads_version";
|
||||
$messages = [];
|
||||
|
||||
if(PHP_INT_SIZE < 8){
|
||||
$messages[] = "Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
|
||||
}
|
||||
if(version_compare($pthreads_version, "3.1.7dev") < 0){
|
||||
critical_error("pthreads >= 3.1.7dev is required, while you have $pthreads_version.");
|
||||
++$errors;
|
||||
|
||||
if(php_sapi_name() !== "cli"){
|
||||
$messages[] = "You must run " . \pocketmine\NAME . " using the CLI.";
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("leveldb")){
|
||||
$leveldb_version = phpversion("leveldb");
|
||||
if(version_compare($leveldb_version, "0.2.1") < 0){
|
||||
critical_error("php-leveldb >= 0.2.1 is required, while you have $leveldb_version");
|
||||
++$errors;
|
||||
$extensions = [
|
||||
"bcmath" => "BC Math",
|
||||
"curl" => "cURL",
|
||||
"ctype" => "ctype",
|
||||
"date" => "Date",
|
||||
"hash" => "Hash",
|
||||
"json" => "JSON",
|
||||
"mbstring" => "Multibyte String",
|
||||
"openssl" => "OpenSSL",
|
||||
"pcre" => "PCRE",
|
||||
"phar" => "Phar",
|
||||
"pthreads" => "pthreads",
|
||||
"reflection" => "Reflection",
|
||||
"sockets" => "Sockets",
|
||||
"spl" => "SPL",
|
||||
"yaml" => "YAML",
|
||||
"zip" => "Zip",
|
||||
"zlib" => "Zlib"
|
||||
];
|
||||
|
||||
foreach($extensions as $ext => $name){
|
||||
if(!extension_loaded($ext)){
|
||||
$messages[] = "Unable to find the $name ($ext) extension.";
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("pthreads")){
|
||||
$pthreads_version = phpversion("pthreads");
|
||||
if(substr_count($pthreads_version, ".") < 2){
|
||||
$pthreads_version = "0.$pthreads_version";
|
||||
}
|
||||
if(version_compare($pthreads_version, "3.1.7dev") < 0){
|
||||
$messages[] = "pthreads >= 3.1.7dev is required, while you have $pthreads_version.";
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("leveldb")){
|
||||
$leveldb_version = phpversion("leveldb");
|
||||
if(version_compare($leveldb_version, "0.2.1") < 0){
|
||||
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("pocketmine")){
|
||||
$messages[] = "The native PocketMine extension is no longer supported.";
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
if(extension_loaded("pocketmine")){
|
||||
critical_error("The native PocketMine extension is no longer supported.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
if($errors > 0){
|
||||
if(!empty($messages = check_platform_dependencies())){
|
||||
echo PHP_EOL;
|
||||
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
|
||||
critical_error("Selected PHP binary ($binary) does not satisfy some requirements.");
|
||||
foreach($messages as $m){
|
||||
echo " - $m" . PHP_EOL;
|
||||
}
|
||||
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
|
||||
echo PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
unset($messages);
|
||||
|
||||
error_reporting(-1);
|
||||
|
||||
@ -139,12 +145,18 @@ namespace pocketmine {
|
||||
define('pocketmine\PATH', dirname(__FILE__, 3) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
define('pocketmine\COMPOSER_AUTOLOADER_PATH', \pocketmine\PATH . 'vendor/autoload.php');
|
||||
$opts = getopt("", ["bootstrap:"]);
|
||||
if(isset($opts["bootstrap"])){
|
||||
$bootstrap = realpath($opts["bootstrap"]) ?: $opts["bootstrap"];
|
||||
}else{
|
||||
$bootstrap = \pocketmine\PATH . 'vendor/autoload.php';
|
||||
}
|
||||
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
|
||||
|
||||
if(is_file(\pocketmine\COMPOSER_AUTOLOADER_PATH)){
|
||||
if(\pocketmine\COMPOSER_AUTOLOADER_PATH !== false and is_file(\pocketmine\COMPOSER_AUTOLOADER_PATH)){
|
||||
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
|
||||
}else{
|
||||
critical_error("Composer autoloader not found.");
|
||||
critical_error("Composer autoloader not found at " . $bootstrap);
|
||||
critical_error("Please install/update Composer dependencies or use provided builds.");
|
||||
exit(1);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ use pocketmine\event\HandlerList;
|
||||
use pocketmine\event\level\LevelInitEvent;
|
||||
use pocketmine\event\level\LevelLoadEvent;
|
||||
use pocketmine\event\player\PlayerDataSaveEvent;
|
||||
use pocketmine\event\server\CommandEvent;
|
||||
use pocketmine\event\server\QueryRegenerateEvent;
|
||||
use pocketmine\event\server\ServerCommandEvent;
|
||||
use pocketmine\inventory\CraftingManager;
|
||||
@ -82,6 +83,7 @@ use pocketmine\network\rcon\RCON;
|
||||
use pocketmine\network\upnp\UPnP;
|
||||
use pocketmine\permission\BanList;
|
||||
use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\plugin\PharPluginLoader;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginLoadOrder;
|
||||
@ -99,6 +101,7 @@ use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\updater\AutoUpdater;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\TextFormat;
|
||||
@ -1025,15 +1028,8 @@ class Server{
|
||||
return false;
|
||||
}
|
||||
|
||||
try{
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
}catch(\Throwable $e){
|
||||
|
||||
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()]));
|
||||
$this->logger->logException($e);
|
||||
return false;
|
||||
}
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
@ -1076,21 +1072,15 @@ class Server{
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
$path = $this->getDataPath() . "worlds/" . $name . "/";
|
||||
/** @var LevelProvider $providerClass */
|
||||
$providerClass::generate($path, $name, $seed, $generator, $options);
|
||||
$path = $this->getDataPath() . "worlds/" . $name . "/";
|
||||
/** @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;
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.generationError", [$name, $e->getMessage()]));
|
||||
$this->logger->logException($e);
|
||||
return false;
|
||||
}
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
|
||||
$this->getPluginManager()->callEvent(new LevelInitEvent($level));
|
||||
|
||||
@ -1148,17 +1138,12 @@ class Server{
|
||||
* Useful for tracking entities across multiple worlds without needing strong references.
|
||||
*
|
||||
* @param int $entityId
|
||||
* @param Level|null $expectedLevel Level to look in first for the target
|
||||
* @param Level|null $expectedLevel @deprecated Level to look in first for the target
|
||||
*
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function findEntity(int $entityId, Level $expectedLevel = null){
|
||||
$levels = $this->levels;
|
||||
if($expectedLevel !== null){
|
||||
array_unshift($levels, $expectedLevel);
|
||||
}
|
||||
|
||||
foreach($levels as $level){
|
||||
foreach($this->levels as $level){
|
||||
assert(!$level->isClosed());
|
||||
if(($entity = $level->getEntity($entityId)) instanceof Entity){
|
||||
return $entity;
|
||||
@ -1305,7 +1290,7 @@ class Server{
|
||||
if(($player = $this->getPlayerExact($name)) !== null){
|
||||
$player->recalculatePermissions();
|
||||
}
|
||||
$this->operators->save(true);
|
||||
$this->operators->save();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1325,7 +1310,7 @@ class Server{
|
||||
*/
|
||||
public function addWhitelist(string $name){
|
||||
$this->whitelist->set(strtolower($name), true);
|
||||
$this->whitelist->save(true);
|
||||
$this->whitelist->save();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1638,7 +1623,7 @@ 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);
|
||||
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
|
||||
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());
|
||||
@ -1696,7 +1681,7 @@ class Server{
|
||||
}
|
||||
|
||||
if($this->properties->hasChanged()){
|
||||
$this->properties->save(true);
|
||||
$this->properties->save();
|
||||
}
|
||||
|
||||
if(!($this->getDefaultLevel() instanceof Level)){
|
||||
@ -1747,7 +1732,7 @@ class Server{
|
||||
if(!is_array($recipients)){
|
||||
/** @var Player[] $recipients */
|
||||
$recipients = [];
|
||||
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
|
||||
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
|
||||
}
|
||||
@ -1773,7 +1758,7 @@ class Server{
|
||||
/** @var Player[] $recipients */
|
||||
$recipients = [];
|
||||
|
||||
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
|
||||
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
|
||||
}
|
||||
@ -1803,7 +1788,7 @@ class Server{
|
||||
/** @var Player[] $recipients */
|
||||
$recipients = [];
|
||||
|
||||
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
|
||||
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
|
||||
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
|
||||
}
|
||||
@ -1828,7 +1813,7 @@ class Server{
|
||||
/** @var CommandSender[] $recipients */
|
||||
$recipients = [];
|
||||
foreach(explode(";", $permissions) as $permission){
|
||||
foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){
|
||||
foreach(PermissionManager::getInstance()->getPermissionSubscriptions($permission) as $permissible){
|
||||
if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){
|
||||
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
|
||||
}
|
||||
@ -1953,10 +1938,20 @@ class Server{
|
||||
*
|
||||
* @param CommandSender $sender
|
||||
* @param string $commandLine
|
||||
* @param bool $internal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dispatchCommand(CommandSender $sender, string $commandLine) : bool{
|
||||
public function dispatchCommand(CommandSender $sender, string $commandLine, bool $internal = false) : bool{
|
||||
if(!$internal){
|
||||
$this->pluginManager->callEvent($ev = new CommandEvent($sender, $commandLine));
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
||||
$commandLine = $ev->getCommand();
|
||||
}
|
||||
|
||||
if($this->commandMap->dispatch($sender, $commandLine)){
|
||||
return true;
|
||||
}
|
||||
@ -1976,6 +1971,7 @@ class Server{
|
||||
|
||||
$this->pluginManager->disablePlugins();
|
||||
$this->pluginManager->clearPlugins();
|
||||
PermissionManager::getInstance()->clearPermissions();
|
||||
$this->commandMap->clearCommands();
|
||||
|
||||
$this->logger->info("Reloading properties...");
|
||||
@ -2218,7 +2214,7 @@ class Server{
|
||||
|
||||
if($report){
|
||||
$url = ($this->getProperty("auto-report.use-https", true) ? "https" : "http") . "://" . $this->getProperty("auto-report.host", "crash.pmmp.io") . "/submit/api";
|
||||
$reply = Utils::postURL($url, [
|
||||
$reply = Internet::postURL($url, [
|
||||
"report" => "yes",
|
||||
"name" => $this->getName() . " " . $this->getPocketMineVersion(),
|
||||
"email" => "crash@pocketmine.net",
|
||||
@ -2352,41 +2348,37 @@ class Server{
|
||||
}
|
||||
|
||||
//Do level ticks
|
||||
foreach($this->getLevels() as $level){
|
||||
foreach($this->levels as $k => $level){
|
||||
if(!isset($this->levels[$k])){
|
||||
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
|
||||
continue;
|
||||
}
|
||||
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$levelTime = microtime(true);
|
||||
$level->doTick($currentTick);
|
||||
$tickMs = (microtime(true) - $levelTime) * 1000;
|
||||
$level->tickRateTime = $tickMs;
|
||||
|
||||
if($this->autoTickRate){
|
||||
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
|
||||
$level->setTickRate($r = $level->getTickRate() - 1);
|
||||
if($r > $this->baseTickRate){
|
||||
$level->tickRateCounter = $level->getTickRate();
|
||||
}
|
||||
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
|
||||
}elseif($tickMs >= 50){
|
||||
if($level->getTickRate() === $this->baseTickRate){
|
||||
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
|
||||
$level->setTickRate($level->getTickRate() + 1);
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}
|
||||
$levelTime = microtime(true);
|
||||
$level->doTick($currentTick);
|
||||
$tickMs = (microtime(true) - $levelTime) * 1000;
|
||||
$level->tickRateTime = $tickMs;
|
||||
|
||||
if($this->autoTickRate){
|
||||
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
|
||||
$level->setTickRate($r = $level->getTickRate() - 1);
|
||||
if($r > $this->baseTickRate){
|
||||
$level->tickRateCounter = $level->getTickRate();
|
||||
}
|
||||
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
|
||||
}elseif($tickMs >= 50){
|
||||
if($level->getTickRate() === $this->baseTickRate){
|
||||
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
|
||||
$level->setTickRate($level->getTickRate() + 1);
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}
|
||||
$level->tickRateCounter = $level->getTickRate();
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
if(!$level->isClosed()){
|
||||
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickError", [$level->getName(), $e->getMessage()]));
|
||||
}else{
|
||||
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickUnloadError", [$level->getName()]));
|
||||
}
|
||||
$this->logger->logException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2533,13 +2525,9 @@ class Server{
|
||||
}
|
||||
|
||||
if(($this->tickCounter & 0b111111111) === 0){
|
||||
try{
|
||||
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
|
||||
if($this->queryHandler !== null){
|
||||
$this->queryHandler->regenerateInfo();
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->logException($e);
|
||||
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
|
||||
if($this->queryHandler !== null){
|
||||
$this->queryHandler->regenerateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,21 +192,25 @@ class Bed extends Transparent{
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
if($this->isHeadPart()){
|
||||
$tile = $this->getLevel()->getTile($this);
|
||||
if($tile instanceof TileBed){
|
||||
return [
|
||||
ItemFactory::get($this->getItemId(), $tile->getColor())
|
||||
];
|
||||
}else{
|
||||
return [
|
||||
ItemFactory::get($this->getItemId(), 14) //Red
|
||||
];
|
||||
}
|
||||
return [$this->getItem()];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
return $this->getItem();
|
||||
}
|
||||
|
||||
private function getItem() : Item{
|
||||
$tile = $this->getLevel()->getTile($this);
|
||||
if($tile instanceof TileBed){
|
||||
return ItemFactory::get($this->getItemId(), $tile->getColor());
|
||||
}
|
||||
|
||||
return ItemFactory::get($this->getItemId(), 14); //Red
|
||||
}
|
||||
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
@ -109,6 +109,14 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
return $this->itemId ?? $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return int
|
||||
*/
|
||||
public function getRuntimeId() : int{
|
||||
return BlockFactory::toStaticRuntimeId($this->getId(), $this->getDamage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
|
@ -421,8 +421,8 @@ class BlockFactory{
|
||||
public static function registerStaticRuntimeIdMappings() : void{
|
||||
/** @var mixed[] $runtimeIdMap */
|
||||
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
|
||||
foreach($runtimeIdMap as $obj){
|
||||
self::registerMapping($obj["runtimeID"], $obj["id"], $obj["data"]);
|
||||
foreach($runtimeIdMap as $k => $obj){
|
||||
self::registerMapping($k, $obj["id"], $obj["data"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\TieredTool;
|
||||
|
||||
class BrewingStand extends Transparent{
|
||||
|
||||
protected $id = self::BREWING_STAND_BLOCK;
|
||||
|
||||
protected $itemId = Item::BREWING_STAND;
|
||||
|
||||
public function __construct(int $meta = 0){
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ class Cake extends Transparent implements FoodSource{
|
||||
|
||||
protected $id = self::CAKE_BLOCK;
|
||||
|
||||
protected $itemId = Item::CAKE;
|
||||
|
||||
public function __construct(int $meta = 0){
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
@ -47,4 +47,8 @@ abstract class DoubleSlab extends Solid{
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
return ItemFactory::get($this->getSlabId(), $this->getVariant());
|
||||
}
|
||||
}
|
||||
|
@ -111,4 +111,8 @@ class Farmland extends Transparent{
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
return ItemFactory::get(Item::DIRT);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,8 @@ class RedstoneOre extends Solid{
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
return $this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
return false; //this shouldn't prevent block placement
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
|
@ -71,18 +71,20 @@ class Skull extends Flowable{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
private function getItem() : Item{
|
||||
$tile = $this->level->getTile($this);
|
||||
if($tile instanceof TileSkull){
|
||||
return [
|
||||
ItemFactory::get(Item::SKULL, $tile->getType())
|
||||
];
|
||||
}
|
||||
return ItemFactory::get(Item::SKULL, $tile instanceof TileSkull ? $tile->getType() : 0);
|
||||
}
|
||||
|
||||
return [];
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [$this->getItem()];
|
||||
}
|
||||
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
return $this->getItem();
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\item\FlintSteel;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -56,6 +57,16 @@ class TNT extends Solid{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasEntityCollision() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onEntityCollide(Entity $entity) : void{
|
||||
if($entity instanceof Arrow and $entity->isOnFire()){
|
||||
$this->ignite();
|
||||
}
|
||||
}
|
||||
|
||||
public function ignite(int $fuse = 80){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
|
||||
|
@ -100,11 +100,7 @@ abstract class Thin extends Transparent{
|
||||
}
|
||||
|
||||
//FIXME: currently there's no proper way to tell if a block is a full-block, so we check the bounding box size
|
||||
$bbs = $block->getCollisionBoxes();
|
||||
if(count($bbs) === 1){
|
||||
return $bbs[0]->getAverageEdgeLength() >= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
$bb = $block->getBoundingBox();
|
||||
return $bb !== null and $bb->getAverageEdgeLength() >= 1;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class Torch extends Flowable{
|
||||
5 => Vector3::SIDE_DOWN
|
||||
];
|
||||
|
||||
if($this->getSide($faces[$side])->isTransparent() and !($side === Vector3::SIDE_DOWN and ($below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL))){
|
||||
if($this->getSide($faces[$side])->isTransparent() and !($faces[$side] === Vector3::SIDE_DOWN and ($below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL))){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ namespace pocketmine\command;
|
||||
|
||||
use pocketmine\lang\TextContainer;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\TextFormat;
|
||||
@ -293,7 +294,7 @@ abstract class Command{
|
||||
$m = clone $message;
|
||||
$result = "[" . $source->getName() . ": " . ($source->getServer()->getLanguage()->get($m->getText()) !== $m->getText() ? "%" : "") . $m->getText() . "]";
|
||||
|
||||
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$colored = TextFormat::GRAY . TextFormat::ITALIC . $result;
|
||||
|
||||
$m->setText($result);
|
||||
@ -301,7 +302,7 @@ abstract class Command{
|
||||
$m->setText($colored);
|
||||
$colored = clone $m;
|
||||
}else{
|
||||
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$result = new TranslationContainer("chat.type.admin", [$source->getName(), $message]);
|
||||
$colored = new TranslationContainer(TextFormat::GRAY . TextFormat::ITALIC . "%chat.type.admin", [$source->getName(), $message]);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\command;
|
||||
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\Thread;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
class CommandReader extends Thread{
|
||||
|
||||
@ -47,9 +48,9 @@ class CommandReader extends Thread{
|
||||
$this->buffer = new \Threaded;
|
||||
$this->notifier = $notifier;
|
||||
|
||||
$opts = getopt("", ["disable-readline"]);
|
||||
$opts = getopt("", ["disable-readline", "enable-readline"]);
|
||||
|
||||
if(extension_loaded("readline") and !isset($opts["disable-readline"]) and !$this->isPipe(STDIN)){
|
||||
if(extension_loaded("readline") and (Utils::getOS() === "win" ? isset($opts["enable-readline"]) : !isset($opts["disable-readline"])) and !$this->isPipe(STDIN)){
|
||||
$this->type = self::TYPE_READLINE;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class FormattedCommandAlias extends Command{
|
||||
}
|
||||
|
||||
foreach($commands as $command){
|
||||
$result |= Server::getInstance()->dispatchCommand($sender, $command);
|
||||
$result |= Server::getInstance()->dispatchCommand($sender, $command, true);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
|
@ -30,6 +30,7 @@ use pocketmine\Player;
|
||||
use pocketmine\scheduler\BulkCurlTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\InternetException;
|
||||
|
||||
class TimingsCommand extends VanillaCommand{
|
||||
|
||||
@ -131,7 +132,7 @@ class TimingsCommand extends VanillaCommand{
|
||||
return;
|
||||
}
|
||||
$result = $this->getResult()[0];
|
||||
if($result instanceof \RuntimeException){
|
||||
if($result instanceof InternetException){
|
||||
$server->getLogger()->logException($result);
|
||||
return;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ class Effect{
|
||||
public const SATURATION = 23;
|
||||
public const LEVITATION = 24; //TODO
|
||||
public const FATAL_POISON = 25;
|
||||
public const CONDUIT_POWER = 26;
|
||||
|
||||
/** @var Effect[] */
|
||||
protected static $effects = [];
|
||||
@ -86,6 +87,7 @@ class Effect{
|
||||
self::registerEffect(new Effect(Effect::SATURATION, "%potion.saturation", new Color(0xf8, 0x24, 0x23), false, 1));
|
||||
self::registerEffect(new Effect(Effect::LEVITATION, "%potion.levitation", new Color(0xce, 0xff, 0xff)));
|
||||
self::registerEffect(new Effect(Effect::FATAL_POISON, "%potion.poison", new Color(0x4e, 0x93, 0x31), true));
|
||||
self::registerEffect(new Effect(Effect::CONDUIT_POWER, "%potion.conduitPower", new Color(0x1d, 0xc2, 0xd1)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -408,8 +408,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public $boundingBox;
|
||||
/** @var bool */
|
||||
public $onGround;
|
||||
/** @var int */
|
||||
protected $age = 0;
|
||||
|
||||
/** @var float */
|
||||
public $eyeHeight = null;
|
||||
@ -610,6 +608,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->propertyManager->setByte(self::DATA_ALWAYS_SHOW_NAMETAG, $value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getScoreTag() : ?string{
|
||||
return $this->propertyManager->getString(self::DATA_SCORE_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $score
|
||||
*/
|
||||
public function setScoreTag(string $score) : void{
|
||||
$this->propertyManager->setString(self::DATA_SCORE_TAG, $score);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
@ -735,7 +747,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public function getOwningEntity() : ?Entity{
|
||||
$eid = $this->getOwningEntityId();
|
||||
if($eid !== null){
|
||||
return $this->server->findEntity($eid, $this->level);
|
||||
return $this->server->findEntity($eid);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -775,7 +787,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public function getTargetEntity() : ?Entity{
|
||||
$eid = $this->getTargetEntityId();
|
||||
if($eid !== null){
|
||||
return $this->server->findEntity($eid, $this->level);
|
||||
return $this->server->findEntity($eid);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -1027,7 +1039,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
}
|
||||
|
||||
$this->age += $tickDiff;
|
||||
$this->ticksLived += $tickDiff;
|
||||
|
||||
return $hasUpdate;
|
||||
|
@ -34,6 +34,7 @@ use pocketmine\inventory\EntityInventoryEventProcessor;
|
||||
use pocketmine\inventory\InventoryHolder;
|
||||
use pocketmine\inventory\PlayerInventory;
|
||||
use pocketmine\item\Consumable;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\FoodSource;
|
||||
use pocketmine\item\Item;
|
||||
@ -518,6 +519,42 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
return $this->xpCooldown === 0;
|
||||
}
|
||||
|
||||
public function onPickupXp(int $xpValue) : void{
|
||||
static $mainHandIndex = -1;
|
||||
|
||||
//TODO: replace this with a more generic equipment getting/setting interface
|
||||
/** @var Durable[] $equipment */
|
||||
$equipment = [];
|
||||
|
||||
if(($item = $this->inventory->getItemInHand()) instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
|
||||
$equipment[$mainHandIndex] = $item;
|
||||
}
|
||||
//TODO: check offhand
|
||||
foreach($this->armorInventory->getContents() as $k => $item){
|
||||
if($item instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
|
||||
$equipment[$k] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($equipment)){
|
||||
$repairItem = $equipment[$k = array_rand($equipment)];
|
||||
if($repairItem->getDamage() > 0){
|
||||
$repairAmount = min($repairItem->getDamage(), $xpValue * 2);
|
||||
$repairItem->setDamage($repairItem->getDamage() - $repairAmount);
|
||||
$xpValue -= (int) ceil($repairAmount / 2);
|
||||
|
||||
if($k === $mainHandIndex){
|
||||
$this->inventory->setItemInHand($repairItem);
|
||||
}else{
|
||||
$this->armorInventory->setItem($k, $repairItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->addXp($xpValue); //this will still get fired even if the value is 0 due to mending, to play sounds
|
||||
$this->resetXpCooldown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration in ticks until the human can pick up another XP orb.
|
||||
*
|
||||
|
@ -34,6 +34,7 @@ use pocketmine\inventory\ArmorInventory;
|
||||
use pocketmine\inventory\ArmorInventoryEventProcessor;
|
||||
use pocketmine\item\Armor;
|
||||
use pocketmine\item\Consumable;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -476,6 +477,26 @@ abstract class Living extends Entity implements Damageable{
|
||||
protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
|
||||
$this->setAbsorption(max(0, $this->getAbsorption() + $source->getModifier(EntityDamageEvent::MODIFIER_ABSORPTION)));
|
||||
$this->damageArmor($source->getBaseDamage());
|
||||
|
||||
if($source instanceof EntityDamageByEntityEvent){
|
||||
$damage = 0;
|
||||
foreach($this->armorInventory->getContents() as $k => $item){
|
||||
if($item instanceof Armor and ($thornsLevel = $item->getEnchantmentLevel(Enchantment::THORNS)) > 0){
|
||||
if(mt_rand(0, 99) < $thornsLevel * 15){
|
||||
$this->damageItem($item, 3);
|
||||
$damage += ($thornsLevel > 10 ? $thornsLevel - 10 : 1 + mt_rand(0, 3));
|
||||
}else{
|
||||
$this->damageItem($item, 1); //thorns causes an extra +1 durability loss even if it didn't activate
|
||||
}
|
||||
|
||||
$this->armorInventory->setItem($k, $item);
|
||||
}
|
||||
}
|
||||
|
||||
if($damage > 0){
|
||||
$source->getDamager()->attack(new EntityDamageByEntityEvent($this, $source->getDamager(), EntityDamageEvent::CAUSE_MAGIC, $damage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -490,16 +511,20 @@ abstract class Living extends Entity implements Damageable{
|
||||
$armor = $this->armorInventory->getContents(true);
|
||||
foreach($armor as $item){
|
||||
if($item instanceof Armor){
|
||||
$item->applyDamage($durabilityRemoved);
|
||||
if($item->isBroken()){
|
||||
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
|
||||
}
|
||||
$this->damageItem($item, $durabilityRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
$this->armorInventory->setContents($armor);
|
||||
}
|
||||
|
||||
private function damageItem(Durable $item, int $durabilityRemoved) : void{
|
||||
$item->applyDamage($durabilityRemoved);
|
||||
if($item->isBroken()){
|
||||
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
|
||||
}
|
||||
}
|
||||
|
||||
public function attack(EntityDamageEvent $source) : void{
|
||||
if($this->attackTime > 0 or $this->noDamageTicks > 0){
|
||||
$lastCause = $this->getLastDamageCause();
|
||||
@ -535,7 +560,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attackTime = 10; //0.5 seconds cooldown
|
||||
$this->attackTime = $source->getAttackCooldown();
|
||||
|
||||
if($source instanceof EntityDamageByEntityEvent){
|
||||
$e = $source->getDamager();
|
||||
@ -640,13 +665,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
$this->attack($ev);
|
||||
}
|
||||
|
||||
if(!$this->canBreathe()){
|
||||
$this->setBreathing(false);
|
||||
$this->doAirSupplyTick($tickDiff);
|
||||
if($this->doAirSupplyTick($tickDiff)){
|
||||
$hasUpdate = true;
|
||||
}elseif(!$this->isBreathing()){
|
||||
$this->setBreathing(true);
|
||||
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
|
||||
}
|
||||
}
|
||||
|
||||
@ -675,22 +695,42 @@ abstract class Living extends Entity implements Damageable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticks the entity's air supply when it cannot breathe.
|
||||
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
|
||||
*
|
||||
* @param int $tickDiff
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doAirSupplyTick(int $tickDiff) : void{
|
||||
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
|
||||
lcg_value() <= (1 / ($respirationLevel + 1))
|
||||
){
|
||||
$ticks = $this->getAirSupplyTicks() - $tickDiff;
|
||||
protected function doAirSupplyTick(int $tickDiff) : bool{
|
||||
$ticks = $this->getAirSupplyTicks();
|
||||
$oldTicks = $ticks;
|
||||
if(!$this->canBreathe()){
|
||||
$this->setBreathing(false);
|
||||
|
||||
if($ticks <= -20){
|
||||
$this->setAirSupplyTicks(0);
|
||||
$this->onAirExpired();
|
||||
}else{
|
||||
$this->setAirSupplyTicks($ticks);
|
||||
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
|
||||
lcg_value() <= (1 / ($respirationLevel + 1))
|
||||
){
|
||||
$ticks -= $tickDiff;
|
||||
if($ticks <= -20){
|
||||
$ticks = 0;
|
||||
$this->onAirExpired();
|
||||
}
|
||||
}
|
||||
}elseif(!$this->isBreathing()){
|
||||
if($ticks < ($max = $this->getMaxAirSupplyTicks())){
|
||||
$ticks += $tickDiff * 5;
|
||||
}
|
||||
if($ticks >= $max){
|
||||
$ticks = $max;
|
||||
$this->setBreathing(true);
|
||||
}
|
||||
}
|
||||
|
||||
if($ticks !== $oldTicks){
|
||||
$this->setAirSupplyTicks($ticks);
|
||||
}
|
||||
|
||||
return $ticks !== $oldTicks;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -698,7 +738,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
* @return bool
|
||||
*/
|
||||
public function canBreathe() : bool{
|
||||
return $this->hasEffect(Effect::WATER_BREATHING) or !$this->isUnderwater();
|
||||
return $this->hasEffect(Effect::WATER_BREATHING) or $this->hasEffect(Effect::CONDUIT_POWER) or !$this->isUnderwater();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,6 +88,9 @@ class ExperienceOrb extends Entity{
|
||||
public $gravity = 0.04;
|
||||
public $drag = 0.02;
|
||||
|
||||
/** @var int */
|
||||
protected $age = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* Ticker used for determining interval in which to look for new target players.
|
||||
@ -144,7 +147,7 @@ class ExperienceOrb extends Entity{
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity = $this->server->findEntity($this->targetPlayerRuntimeId, $this->level);
|
||||
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
|
||||
if($entity instanceof Human){
|
||||
return $entity;
|
||||
}
|
||||
@ -159,6 +162,7 @@ class ExperienceOrb extends Entity{
|
||||
public function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->age += $tickDiff;
|
||||
if($this->age > 6000){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
@ -200,10 +204,7 @@ class ExperienceOrb extends Entity{
|
||||
if($currentTarget->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){
|
||||
$this->flagForDespawn();
|
||||
|
||||
$currentTarget->addXp($this->getXpValue());
|
||||
$currentTarget->resetXpCooldown();
|
||||
|
||||
//TODO: check Mending enchantment
|
||||
$currentTarget->onPickupXp($this->getXpValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ class FallingBlock extends Entity{
|
||||
|
||||
$this->block = BlockFactory::get($blockId, $damage);
|
||||
|
||||
$this->propertyManager->setInt(self::DATA_VARIANT, BlockFactory::toStaticRuntimeId($this->block->getId(), $this->block->getDamage()));
|
||||
$this->propertyManager->setInt(self::DATA_VARIANT, $this->block->getRuntimeId());
|
||||
}
|
||||
|
||||
public function canCollideWith(Entity $entity) : bool{
|
||||
|
@ -53,6 +53,9 @@ class ItemEntity extends Entity{
|
||||
|
||||
public $canCollide = false;
|
||||
|
||||
/** @var int */
|
||||
protected $age = 0;
|
||||
|
||||
protected function initEntity() : void{
|
||||
parent::initEntity();
|
||||
|
||||
@ -70,6 +73,9 @@ class ItemEntity extends Entity{
|
||||
}
|
||||
|
||||
$this->item = Item::nbtDeserialize($itemTag);
|
||||
if($this->item->isNull()){
|
||||
throw new \UnexpectedValueException("Item for " . get_class($this) . " is invalid");
|
||||
}
|
||||
|
||||
|
||||
$this->server->getPluginManager()->callEvent(new ItemSpawnEvent($this));
|
||||
@ -82,14 +88,13 @@ class ItemEntity extends Entity{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if(!$this->isFlaggedForDespawn()){
|
||||
if($this->pickupDelay > 0 and $this->pickupDelay < 32767){ //Infinite delay
|
||||
$this->pickupDelay -= $tickDiff;
|
||||
if($this->pickupDelay < 0){
|
||||
$this->pickupDelay = 0;
|
||||
}
|
||||
if(!$this->isFlaggedForDespawn() and $this->pickupDelay > -1 and $this->pickupDelay < 32767){ //Infinite delay
|
||||
$this->pickupDelay -= $tickDiff;
|
||||
if($this->pickupDelay < 0){
|
||||
$this->pickupDelay = 0;
|
||||
}
|
||||
|
||||
$this->age += $tickDiff;
|
||||
if($this->age > 6000){
|
||||
$this->server->getPluginManager()->callEvent($ev = new ItemDespawnEvent($this));
|
||||
if($ev->isCancelled()){
|
||||
@ -99,7 +104,6 @@ class ItemEntity extends Entity{
|
||||
$hasUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
|
@ -40,19 +40,49 @@ use pocketmine\Player;
|
||||
class Arrow extends Projectile{
|
||||
public const NETWORK_ID = self::ARROW;
|
||||
|
||||
public const PICKUP_NONE = 0;
|
||||
public const PICKUP_ANY = 1;
|
||||
public const PICKUP_CREATIVE = 2;
|
||||
|
||||
private const TAG_PICKUP = "pickup"; //TAG_Byte
|
||||
|
||||
public $width = 0.25;
|
||||
public $height = 0.25;
|
||||
|
||||
protected $gravity = 0.05;
|
||||
protected $drag = 0.01;
|
||||
|
||||
protected $damage = 2;
|
||||
/** @var float */
|
||||
protected $damage = 2.0;
|
||||
|
||||
/** @var int */
|
||||
protected $pickupMode = self::PICKUP_ANY;
|
||||
|
||||
/** @var float */
|
||||
protected $punchKnockback = 0.0;
|
||||
|
||||
/** @var int */
|
||||
protected $collideTicks = 0;
|
||||
|
||||
public function __construct(Level $level, CompoundTag $nbt, ?Entity $shootingEntity = null, bool $critical = false){
|
||||
parent::__construct($level, $nbt, $shootingEntity);
|
||||
$this->setCritical($critical);
|
||||
}
|
||||
|
||||
protected function initEntity() : void{
|
||||
parent::initEntity();
|
||||
|
||||
$this->pickupMode = $this->namedtag->getByte(self::TAG_PICKUP, self::PICKUP_ANY, true);
|
||||
$this->collideTicks = $this->namedtag->getShort("life", $this->collideTicks);
|
||||
}
|
||||
|
||||
public function saveNBT() : void{
|
||||
parent::saveNBT();
|
||||
|
||||
$this->namedtag->setByte(self::TAG_PICKUP, $this->pickupMode, true);
|
||||
$this->namedtag->setShort("life", $this->collideTicks);
|
||||
}
|
||||
|
||||
public function isCritical() : bool{
|
||||
return $this->getGenericFlag(self::DATA_FLAG_CRITICAL);
|
||||
}
|
||||
@ -70,6 +100,20 @@ class Arrow extends Projectile{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getPunchKnockback() : float{
|
||||
return $this->punchKnockback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $punchKnockback
|
||||
*/
|
||||
public function setPunchKnockback(float $punchKnockback) : void{
|
||||
$this->punchKnockback = $punchKnockback;
|
||||
}
|
||||
|
||||
public function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
if($this->closed){
|
||||
return false;
|
||||
@ -77,9 +121,14 @@ class Arrow extends Projectile{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if($this->age > 1200){
|
||||
$this->flagForDespawn();
|
||||
$hasUpdate = true;
|
||||
if($this->isCollided){
|
||||
$this->collideTicks += $tickDiff;
|
||||
if($this->collideTicks > 1200){
|
||||
$this->flagForDespawn();
|
||||
$hasUpdate = true;
|
||||
}
|
||||
}else{
|
||||
$this->collideTicks = 0;
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
@ -95,6 +144,31 @@ class Arrow extends Projectile{
|
||||
$this->broadcastEntityEvent(EntityEventPacket::ARROW_SHAKE, 7); //7 ticks
|
||||
}
|
||||
|
||||
protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
|
||||
parent::onHitEntity($entityHit, $hitResult);
|
||||
if($this->punchKnockback > 0){
|
||||
$horizontalSpeed = sqrt($this->motion->x ** 2 + $this->motion->z ** 2);
|
||||
if($horizontalSpeed > 0){
|
||||
$multiplier = $this->punchKnockback * 0.6 / $horizontalSpeed;
|
||||
$entityHit->setMotion($entityHit->getMotion()->add($this->motion->x * $multiplier, 0.1, $this->motion->z * $multiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPickupMode() : int{
|
||||
return $this->pickupMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pickupMode
|
||||
*/
|
||||
public function setPickupMode(int $pickupMode) : void{
|
||||
$this->pickupMode = $pickupMode;
|
||||
}
|
||||
|
||||
public function onCollideWithPlayer(Player $player) : void{
|
||||
if($this->blockHit === null){
|
||||
return;
|
||||
@ -107,7 +181,12 @@ class Arrow extends Projectile{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupArrowEvent($playerInventory, $this));
|
||||
$ev = new InventoryPickupArrowEvent($playerInventory, $this);
|
||||
if($this->pickupMode === self::PICKUP_NONE or ($this->pickupMode === self::PICKUP_CREATIVE and !$player->isCreative())){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
@ -66,7 +66,5 @@ class EnderPearl extends Throwable{
|
||||
|
||||
$owner->attack(new EntityDamageEvent($owner, EntityDamageEvent::CAUSE_FALL, 5));
|
||||
}
|
||||
|
||||
$this->flagForDespawn();
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,5 @@ class ExperienceBottle extends Throwable{
|
||||
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_GLASS);
|
||||
|
||||
$this->level->dropExperience($this, mt_rand(3, 11));
|
||||
|
||||
$this->flagForDespawn();
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ abstract class Projectile extends Entity{
|
||||
|
||||
public const DATA_SHOOTER_ID = 17;
|
||||
|
||||
protected $damage = 0;
|
||||
/** @var float */
|
||||
protected $damage = 0.0;
|
||||
|
||||
/** @var Vector3|null */
|
||||
protected $blockHit;
|
||||
@ -73,7 +74,7 @@ abstract class Projectile extends Entity{
|
||||
|
||||
$this->setMaxHealth(1);
|
||||
$this->setHealth(1);
|
||||
$this->age = $this->namedtag->getShort("Age", $this->age);
|
||||
$this->damage = $this->namedtag->getDouble("damage", $this->damage);
|
||||
|
||||
do{
|
||||
$blockHit = null;
|
||||
@ -112,6 +113,25 @@ abstract class Projectile extends Entity{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base damage applied on collision. This is multiplied by the projectile's speed to give a result
|
||||
* damage.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseDamage() : float{
|
||||
return $this->damage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base amount of damage applied by the projectile.
|
||||
*
|
||||
* @param float $damage
|
||||
*/
|
||||
public function setBaseDamage(float $damage) : void{
|
||||
$this->damage = $damage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of damage this projectile will deal to the entity it hits.
|
||||
* @return int
|
||||
@ -123,7 +143,7 @@ abstract class Projectile extends Entity{
|
||||
public function saveNBT() : void{
|
||||
parent::saveNBT();
|
||||
|
||||
$this->namedtag->setShort("Age", $this->age);
|
||||
$this->namedtag->setDouble("damage", $this->damage);
|
||||
|
||||
if($this->blockHit !== null){
|
||||
$this->namedtag->setInt("tileX", $this->blockHit->x);
|
||||
@ -140,17 +160,19 @@ abstract class Projectile extends Entity{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hasMovementUpdate() : bool{
|
||||
$parent = parent::hasMovementUpdate();
|
||||
if($parent and $this->blockHit !== null){
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->blockHit !== null){
|
||||
$blockIn = $this->level->getBlockAt($this->blockHit->x, $this->blockHit->y, $this->blockHit->z);
|
||||
|
||||
if($blockIn->getId() === $this->blockHitId and $blockIn->getDamage() === $this->blockHitData){
|
||||
return false;
|
||||
if($blockIn->getId() !== $this->blockHitId or $blockIn->getDamage() !== $this->blockHitData){
|
||||
$this->blockHit = $this->blockHitId = $this->blockHitData = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $parent;
|
||||
parent::onNearbyBlockChange();
|
||||
}
|
||||
|
||||
public function hasMovementUpdate() : bool{
|
||||
return $this->blockHit === null and parent::hasMovementUpdate();
|
||||
}
|
||||
|
||||
public function move(float $dx, float $dy, float $dz) : void{
|
||||
|
@ -124,8 +124,6 @@ class SplashPotion extends Throwable{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->flagForDespawn();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\entity\projectile;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\math\RayTraceResult;
|
||||
|
||||
abstract class Throwable extends Projectile{
|
||||
|
||||
public $width = 0.25;
|
||||
@ -31,18 +34,8 @@ abstract class Throwable extends Projectile{
|
||||
protected $gravity = 0.03;
|
||||
protected $drag = 0.01;
|
||||
|
||||
public function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
if($this->closed){
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if($this->age > 1200 or $this->isCollided){
|
||||
$this->flagForDespawn();
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
|
||||
parent::onHitBlock($blockHit, $hitResult);
|
||||
$this->flagForDespawn();
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,37 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\plugin\PluginManager;
|
||||
|
||||
/**
|
||||
* Classes implementing this interface can be registered to receive called Events.
|
||||
* @see PluginManager::registerEvents()
|
||||
*
|
||||
* A function in a Listener class must meet the following criteria to be registered as an event handler:
|
||||
*
|
||||
* - MUST be public
|
||||
* - MUST NOT be static
|
||||
* - MUST accept EXACTLY ONE class parameter which:
|
||||
* - MUST be a VALID class extending Event
|
||||
* - MUST NOT be abstract, UNLESS it has an `@allowHandle` annotation
|
||||
*
|
||||
* Event handlers do not have to have any particular name - they are detected using reflection.
|
||||
* They SHOULD NOT return any values (but this is not currently enforced).
|
||||
*
|
||||
* Functions which meet the criteria can have the following annotations in their doc comments:
|
||||
*
|
||||
* - `@notHandler`: Marks a function as NOT being an event handler. Only needed if the function meets the above criteria.
|
||||
* - `@softDepend [PluginName]`: Handler WILL NOT be registered if its event doesn't exist. Useful for soft-depending
|
||||
* on plugin events. Plugin name is optional.
|
||||
* Example: `@softDepend SimpleAuth`
|
||||
* - `@ignoreCancelled`: Cancelled events WILL NOT be passed to this handler.
|
||||
* - `@priority <PRIORITY>`: Sets the priority at which this event handler will receive events.
|
||||
* Example: `@priority HIGHEST`
|
||||
* @see EventPriority for a list of possible options.
|
||||
*
|
||||
* Event handlers will receive any instanceof the Event class they have chosen to receive. For example, an
|
||||
* EntityDamageEvent handler will also receive any subclass of EntityDamageEvent.
|
||||
*/
|
||||
interface Listener{
|
||||
|
||||
}
|
||||
|
@ -51,6 +51,6 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getChild() : ?Entity{
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getDamager() : ?Entity{
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId, $this->getEntity()->getLevel());
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,6 +38,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
|
||||
public const MODIFIER_ARMOR_ENCHANTMENTS = 6;
|
||||
public const MODIFIER_CRITICAL = 7;
|
||||
public const MODIFIER_TOTEM = 8;
|
||||
public const MODIFIER_WEAPON_ENCHANTMENTS = 9;
|
||||
|
||||
public const CAUSE_CONTACT = 0;
|
||||
public const CAUSE_ENTITY_ATTACK = 1;
|
||||
@ -68,6 +69,9 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
|
||||
/** @var float[] */
|
||||
private $originals;
|
||||
|
||||
/** @var int */
|
||||
private $attackCooldown = 10;
|
||||
|
||||
|
||||
/**
|
||||
* @param Entity $entity
|
||||
@ -196,4 +200,24 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cooldown in ticks before the target entity can be attacked again.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAttackCooldown() : int{
|
||||
return $this->attackCooldown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cooldown in ticks before the target entity can be attacked again.
|
||||
*
|
||||
* NOTE: This value is not used in non-Living entities
|
||||
*
|
||||
* @param int $attackCooldown
|
||||
*/
|
||||
public function setAttackCooldown(int $attackCooldown) : void{
|
||||
$this->attackCooldown = $attackCooldown;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
@ -55,7 +56,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
|
||||
$this->format = $format;
|
||||
|
||||
if($recipients === null){
|
||||
$this->recipients = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
|
||||
$this->recipients = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
|
||||
}else{
|
||||
$this->recipients = $recipients;
|
||||
}
|
||||
|
@ -23,9 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityDeathEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\lang\TextContainer;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\Player;
|
||||
|
||||
class PlayerDeathEvent extends EntityDeathEvent{
|
||||
@ -37,13 +43,13 @@ class PlayerDeathEvent extends EntityDeathEvent{
|
||||
private $keepInventory = false;
|
||||
|
||||
/**
|
||||
* @param Player $entity
|
||||
* @param Item[] $drops
|
||||
* @param string|TextContainer $deathMessage
|
||||
* @param Player $entity
|
||||
* @param Item[] $drops
|
||||
* @param string|TextContainer|null $deathMessage Null will cause the default vanilla message to be used
|
||||
*/
|
||||
public function __construct(Player $entity, array $drops, $deathMessage){
|
||||
public function __construct(Player $entity, array $drops, $deathMessage = null){
|
||||
parent::__construct($entity, $drops);
|
||||
$this->deathMessage = $deathMessage;
|
||||
$this->deathMessage = $deathMessage ?? self::deriveMessage($entity->getDisplayName(), $entity->getLastDamageCause());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,4 +87,123 @@ class PlayerDeathEvent extends EntityDeathEvent{
|
||||
public function setKeepInventory(bool $keepInventory) : void{
|
||||
$this->keepInventory = $keepInventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vanilla death message for the given death cause.
|
||||
*
|
||||
* @param string $name
|
||||
* @param null|EntityDamageEvent $deathCause
|
||||
*
|
||||
* @return TranslationContainer
|
||||
*/
|
||||
public static function deriveMessage(string $name, ?EntityDamageEvent $deathCause) : TranslationContainer{
|
||||
$message = "death.attack.generic";
|
||||
$params = [$name];
|
||||
|
||||
switch($deathCause === null ? EntityDamageEvent::CAUSE_CUSTOM : $deathCause->getCause()){
|
||||
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
|
||||
if($deathCause instanceof EntityDamageByEntityEvent){
|
||||
$e = $deathCause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.player";
|
||||
$params[] = $e->getDisplayName();
|
||||
break;
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.mob";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}else{
|
||||
$params[] = "Unknown";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_PROJECTILE:
|
||||
if($deathCause instanceof EntityDamageByEntityEvent){
|
||||
$e = $deathCause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.arrow";
|
||||
$params[] = $e->getDisplayName();
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.arrow";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}else{
|
||||
$params[] = "Unknown";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_SUICIDE:
|
||||
$message = "death.attack.generic";
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_VOID:
|
||||
$message = "death.attack.outOfWorld";
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_FALL:
|
||||
if($deathCause instanceof EntityDamageEvent){
|
||||
if($deathCause->getFinalDamage() > 2){
|
||||
$message = "death.fell.accident.generic";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$message = "death.attack.fall";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_SUFFOCATION:
|
||||
$message = "death.attack.inWall";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_LAVA:
|
||||
$message = "death.attack.lava";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_FIRE:
|
||||
$message = "death.attack.onFire";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_FIRE_TICK:
|
||||
$message = "death.attack.inFire";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_DROWNING:
|
||||
$message = "death.attack.drown";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_CONTACT:
|
||||
if($deathCause instanceof EntityDamageByBlockEvent){
|
||||
if($deathCause->getDamager()->getId() === Block::CACTUS){
|
||||
$message = "death.attack.cactus";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
|
||||
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
|
||||
if($deathCause instanceof EntityDamageByEntityEvent){
|
||||
$e = $deathCause->getDamager();
|
||||
if($e instanceof Player){
|
||||
$message = "death.attack.explosion.player";
|
||||
$params[] = $e->getDisplayName();
|
||||
}elseif($e instanceof Living){
|
||||
$message = "death.attack.explosion.player";
|
||||
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
$message = "death.attack.explosion";
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_MAGIC:
|
||||
$message = "death.attack.magic";
|
||||
break;
|
||||
|
||||
case EntityDamageEvent::CAUSE_CUSTOM:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new TranslationContainer($message, $params);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,13 @@ class PlayerKickEvent extends PlayerEvent implements Cancellable{
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $reason
|
||||
*/
|
||||
public function setReason(string $reason) : void{
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
public function getReason() : string{
|
||||
return $this->reason;
|
||||
}
|
||||
|
73
src/pocketmine/event/server/CommandEvent.php
Normal file
73
src/pocketmine/event/server/CommandEvent.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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\event\server;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\Cancellable;
|
||||
|
||||
/**
|
||||
* Called when any CommandSender runs a command, early in the process
|
||||
*
|
||||
* You don't want to use this except for a few cases like logging commands,
|
||||
* blocking commands on certain places, or applying modifiers.
|
||||
*
|
||||
* The message DOES NOT contain a slash at the start
|
||||
*/
|
||||
class CommandEvent extends ServerEvent implements Cancellable{
|
||||
/** @var string */
|
||||
protected $command;
|
||||
|
||||
/** @var CommandSender */
|
||||
protected $sender;
|
||||
|
||||
/**
|
||||
* @param CommandSender $sender
|
||||
* @param string $command
|
||||
*/
|
||||
public function __construct(CommandSender $sender, string $command){
|
||||
$this->sender = $sender;
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CommandSender
|
||||
*/
|
||||
public function getSender() : CommandSender{
|
||||
return $this->sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand() : string{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $command
|
||||
*/
|
||||
public function setCommand(string $command) : void{
|
||||
$this->command = $command;
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ use pocketmine\command\CommandSender;
|
||||
|
||||
/**
|
||||
* This event is called when a command is received over RCON.
|
||||
*
|
||||
* @deprecated Use CommandEvent instead.
|
||||
*/
|
||||
class RemoteServerCommandEvent extends ServerCommandEvent{
|
||||
|
||||
|
@ -33,6 +33,8 @@ use pocketmine\event\Cancellable;
|
||||
* blocking commands on certain places, or applying modifiers.
|
||||
*
|
||||
* The message DOES NOT contain a slash at the start
|
||||
*
|
||||
* @deprecated Use CommandEvent instead.
|
||||
*/
|
||||
class ServerCommandEvent extends ServerEvent implements Cancellable{
|
||||
/** @var string */
|
||||
|
43
src/pocketmine/form/Form.php
Normal file
43
src/pocketmine/form/Form.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?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\form;
|
||||
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Form implementations must implement this interface to be able to utilize the Player form-sending mechanism.
|
||||
* There is no restriction on custom implementations other than that they must implement this.
|
||||
*/
|
||||
interface Form extends \JsonSerializable{
|
||||
|
||||
/**
|
||||
* Handles a form response from a player.
|
||||
*
|
||||
* @param Player $player
|
||||
* @param mixed $data
|
||||
*
|
||||
* @throws FormValidationException if the data could not be processed
|
||||
*/
|
||||
public function handleResponse(Player $player, $data) : void;
|
||||
}
|
28
src/pocketmine/form/FormValidationException.php
Normal file
28
src/pocketmine/form/FormValidationException.php
Normal file
@ -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 pocketmine\form;
|
||||
|
||||
class FormValidationException extends \RuntimeException{
|
||||
|
||||
}
|
@ -24,9 +24,11 @@ declare(strict_types=1);
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\projectile\Arrow as ArrowEntity;
|
||||
use pocketmine\entity\projectile\Projectile;
|
||||
use pocketmine\event\entity\EntityShootBowEvent;
|
||||
use pocketmine\event\entity\ProjectileLaunchEvent;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
@ -64,6 +66,21 @@ class Bow extends Tool{
|
||||
|
||||
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
|
||||
if($entity instanceof Projectile){
|
||||
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
|
||||
if($entity instanceof ArrowEntity){
|
||||
if($infinity){
|
||||
$entity->setPickupMode(ArrowEntity::PICKUP_CREATIVE);
|
||||
}
|
||||
if(($punchLevel = $this->getEnchantmentLevel(Enchantment::PUNCH)) > 0){
|
||||
$entity->setPunchKnockback($punchLevel);
|
||||
}
|
||||
}
|
||||
if(($powerLevel = $this->getEnchantmentLevel(Enchantment::POWER)) > 0){
|
||||
$entity->setBaseDamage($entity->getBaseDamage() + (($powerLevel + 1) / 2));
|
||||
}
|
||||
if($this->hasEnchantment(Enchantment::FLAME)){
|
||||
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
|
||||
}
|
||||
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
|
||||
|
||||
if($force < 0.1 or $diff < 5){
|
||||
@ -80,7 +97,9 @@ class Bow extends Tool{
|
||||
}else{
|
||||
$entity->setMotion($entity->getMotion()->multiply($ev->getForce()));
|
||||
if($player->isSurvival()){
|
||||
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
|
||||
if(!$infinity){ //TODO: tipped arrows are still consumed when Infinity is applied
|
||||
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
|
||||
}
|
||||
$this->applyDamage(1);
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ class Bucket extends Item implements Consumable{
|
||||
if($blockClicked instanceof Liquid and $blockClicked->getDamage() === 0){
|
||||
$stack = clone $this;
|
||||
|
||||
$resultItem = $stack->pop();
|
||||
$resultItem->setDamage($blockClicked->getFlowingForm()->getId());
|
||||
$stack->pop();
|
||||
$resultItem = ItemFactory::get(Item::BUCKET, $blockClicked->getFlowingForm()->getId());
|
||||
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem));
|
||||
if(!$ev->isCancelled()){
|
||||
$player->getLevel()->setBlock($blockClicked, BlockFactory::get(Block::AIR), true, true);
|
||||
@ -80,9 +80,7 @@ class Bucket extends Item implements Consumable{
|
||||
}
|
||||
}
|
||||
}elseif($resultBlock instanceof Liquid and $blockReplace->canBeReplaced()){
|
||||
$resultItem = clone $this;
|
||||
$resultItem->setDamage(0);
|
||||
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, $resultItem));
|
||||
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, ItemFactory::get(Item::BUCKET)));
|
||||
if(!$ev->isCancelled()){
|
||||
$player->getLevel()->setBlock($blockReplace, $resultBlock->getFlowingForm(), true, true);
|
||||
$player->getLevel()->broadcastLevelSoundEvent($blockClicked->add(0.5, 0.5, 0.5), $resultBlock->getBucketEmptySound());
|
||||
|
@ -47,17 +47,17 @@ class ChorusFruit extends Food{
|
||||
}
|
||||
|
||||
public function onConsume(Living $consumer){
|
||||
$level = $consumer->getLevel();
|
||||
assert($level !== null);
|
||||
|
||||
$minX = $consumer->getFloorX() - 8;
|
||||
$minY = $consumer->getFloorY() - 8;
|
||||
$minY = min($consumer->getFloorY(), $consumer->getLevel()->getWorldHeight()) - 8;
|
||||
$minZ = $consumer->getFloorZ() - 8;
|
||||
|
||||
$maxX = $minX + 16;
|
||||
$maxY = $minY + 16;
|
||||
$maxZ = $minZ + 16;
|
||||
|
||||
$level = $consumer->getLevel();
|
||||
assert($level !== null);
|
||||
|
||||
for($attempts = 0; $attempts < 16; ++$attempts){
|
||||
$x = mt_rand($minX, $maxX);
|
||||
$y = mt_rand($minY, $maxY);
|
||||
|
@ -196,7 +196,10 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(int $id, int $meta = 0, string $name = "Unknown"){
|
||||
$this->id = $id & 0xffff;
|
||||
if($id < -0x8000 or $id > 0x7fff){ //signed short range
|
||||
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
|
||||
}
|
||||
$this->id = $id;
|
||||
$this->setDamage($meta);
|
||||
$this->name = $name;
|
||||
}
|
||||
@ -916,7 +919,7 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
*/
|
||||
public function nbtSerialize(int $slot = -1, string $tagName = "") : CompoundTag{
|
||||
$result = new CompoundTag($tagName, [
|
||||
new ShortTag("id", Binary::signShort($this->id)),
|
||||
new ShortTag("id", $this->id),
|
||||
new ByteTag("Count", Binary::signByte($this->count)),
|
||||
new ShortTag("Damage", $this->meta)
|
||||
]);
|
||||
@ -951,9 +954,14 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
|
||||
$idTag = $tag->getTag("id");
|
||||
if($idTag instanceof ShortTag){
|
||||
$item = ItemFactory::get(Binary::unsignShort($idTag->getValue()), $meta, $count);
|
||||
$item = ItemFactory::get($idTag->getValue(), $meta, $count);
|
||||
}elseif($idTag instanceof StringTag){ //PC item save format
|
||||
$item = ItemFactory::fromString($idTag->getValue());
|
||||
try{
|
||||
$item = ItemFactory::fromString($idTag->getValue());
|
||||
}catch(\InvalidArgumentException $e){
|
||||
//TODO: improve error handling
|
||||
return ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
$item->setDamage($meta);
|
||||
$item->setCount($count);
|
||||
}else{
|
||||
|
@ -246,6 +246,8 @@ class ItemFactory{
|
||||
self::registerItem(new Item(Item::NAUTILUS_SHELL, 0, "Nautilus Shell"));
|
||||
self::registerItem(new GoldenAppleEnchanted());
|
||||
self::registerItem(new Item(Item::HEART_OF_THE_SEA, 0, "Heart of the Sea"));
|
||||
self::registerItem(new Item(Item::TURTLE_SHELL_PIECE, 0, "Scute"));
|
||||
//TODO: TURTLE_HELMET
|
||||
|
||||
//TODO: COMPOUND
|
||||
//TODO: RECORD_13
|
||||
@ -281,7 +283,7 @@ class ItemFactory{
|
||||
throw new \RuntimeException("Trying to overwrite an already registered item");
|
||||
}
|
||||
|
||||
self::$list[$id] = clone $item;
|
||||
self::$list[self::getListOffset($id)] = clone $item;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,10 +304,10 @@ class ItemFactory{
|
||||
|
||||
try{
|
||||
/** @var Item|null $listed */
|
||||
$listed = self::$list[$id];
|
||||
$listed = self::$list[self::getListOffset($id)];
|
||||
if($listed !== null){
|
||||
$item = clone $listed;
|
||||
}elseif($id < 256){
|
||||
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
|
||||
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
|
||||
* crafting ingredients with any-damage. */
|
||||
$item = new ItemBlock($id, $meta);
|
||||
@ -353,13 +355,13 @@ class ItemFactory{
|
||||
if(!isset($b[1])){
|
||||
$meta = 0;
|
||||
}elseif(is_numeric($b[1])){
|
||||
$meta = $b[1] & 0xFFFF;
|
||||
$meta = (int) $b[1];
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Unable to parse \"" . $b[1] . "\" from \"" . $str . "\" as a valid meta value");
|
||||
}
|
||||
|
||||
if(is_numeric($b[0])){
|
||||
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
|
||||
$item = self::get((int) $b[0], $meta);
|
||||
}elseif(defined(ItemIds::class . "::" . strtoupper($b[0]))){
|
||||
$item = self::get(constant(ItemIds::class . "::" . strtoupper($b[0])), $meta);
|
||||
}else{
|
||||
@ -380,6 +382,13 @@ class ItemFactory{
|
||||
if($id < 256){
|
||||
return BlockFactory::isRegistered($id);
|
||||
}
|
||||
return self::$list[$id] !== null;
|
||||
return self::$list[self::getListOffset($id)] !== null;
|
||||
}
|
||||
|
||||
private static function getListOffset(int $id) : int{
|
||||
if($id < -0x8000 or $id > 0x7fff){
|
||||
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
|
||||
}
|
||||
return $id & 0xffff;
|
||||
}
|
||||
}
|
||||
|
@ -235,6 +235,8 @@ interface ItemIds extends BlockIds{
|
||||
public const NAUTILUS_SHELL = 465;
|
||||
public const APPLEENCHANTED = 466, APPLE_ENCHANTED = 466, ENCHANTED_GOLDEN_APPLE = 466;
|
||||
public const HEART_OF_THE_SEA = 467;
|
||||
public const TURTLE_SHELL_PIECE = 468;
|
||||
public const TURTLE_HELMET = 469;
|
||||
|
||||
public const COMPOUND = 499;
|
||||
public const RECORD_13 = 500;
|
||||
|
@ -72,8 +72,6 @@ class Potion extends Item implements Consumable{
|
||||
*
|
||||
* @param int $id
|
||||
* @return EffectInstance[]
|
||||
*
|
||||
* @throws \InvalidArgumentException if the potion type is unknown
|
||||
*/
|
||||
public static function getPotionEffectsById(int $id) : array{
|
||||
switch($id){
|
||||
@ -213,7 +211,7 @@ class Potion extends Item implements Consumable{
|
||||
];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unknown potion type $id");
|
||||
return [];
|
||||
}
|
||||
|
||||
public function __construct(int $meta = 0){
|
||||
|
@ -114,13 +114,26 @@ class Enchantment{
|
||||
self::registerEnchantment(new ProtectionEnchantment(self::PROJECTILE_PROTECTION, "%enchantment.protect.projectile", self::RARITY_UNCOMMON, self::SLOT_ARMOR, self::SLOT_NONE, 4, 1.5, [
|
||||
EntityDamageEvent::CAUSE_PROJECTILE
|
||||
]));
|
||||
|
||||
self::registerEnchantment(new Enchantment(self::THORNS, "%enchantment.thorns", self::RARITY_MYTHIC, self::SLOT_TORSO, self::SLOT_HEAD | self::SLOT_LEGS | self::SLOT_FEET, 3));
|
||||
self::registerEnchantment(new Enchantment(self::RESPIRATION, "%enchantment.oxygen", self::RARITY_RARE, self::SLOT_HEAD, self::SLOT_NONE, 3));
|
||||
|
||||
self::registerEnchantment(new SharpnessEnchantment(self::SHARPNESS, "%enchantment.damage.all", self::RARITY_COMMON, self::SLOT_SWORD, self::SLOT_AXE, 5));
|
||||
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
|
||||
|
||||
self::registerEnchantment(new KnockbackEnchantment(self::KNOCKBACK, "%enchantment.knockback", self::RARITY_UNCOMMON, self::SLOT_SWORD, self::SLOT_NONE, 2));
|
||||
self::registerEnchantment(new FireAspectEnchantment(self::FIRE_ASPECT, "%enchantment.fire", self::RARITY_RARE, self::SLOT_SWORD, self::SLOT_NONE, 2));
|
||||
|
||||
self::registerEnchantment(new Enchantment(self::EFFICIENCY, "%enchantment.digging", self::RARITY_COMMON, self::SLOT_DIG, self::SLOT_SHEARS, 5));
|
||||
self::registerEnchantment(new Enchantment(self::SILK_TOUCH, "%enchantment.untouching", self::RARITY_MYTHIC, self::SLOT_DIG, self::SLOT_SHEARS, 1));
|
||||
self::registerEnchantment(new Enchantment(self::UNBREAKING, "%enchantment.durability", self::RARITY_UNCOMMON, self::SLOT_DIG | self::SLOT_ARMOR | self::SLOT_FISHING_ROD | self::SLOT_BOW, self::SLOT_TOOL | self::SLOT_CARROT_STICK | self::SLOT_ELYTRA, 3));
|
||||
|
||||
self::registerEnchantment(new Enchantment(self::POWER, "%enchantment.arrowDamage", self::RARITY_COMMON, self::SLOT_BOW, self::SLOT_NONE, 5));
|
||||
self::registerEnchantment(new Enchantment(self::PUNCH, "%enchantment.arrowKnockback", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 2));
|
||||
self::registerEnchantment(new Enchantment(self::FLAME, "%enchantment.arrowFire", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 1));
|
||||
self::registerEnchantment(new Enchantment(self::INFINITY, "%enchantment.arrowInfinite", self::RARITY_MYTHIC, self::SLOT_BOW, self::SLOT_NONE, 1));
|
||||
|
||||
self::registerEnchantment(new Enchantment(self::MENDING, "%enchantment.mending", self::RARITY_RARE, self::SLOT_NONE, self::SLOT_ALL, 1));
|
||||
|
||||
self::registerEnchantment(new Enchantment(self::VANISHING, "%enchantment.curse.vanishing", self::RARITY_MYTHIC, self::SLOT_NONE, self::SLOT_ALL, 1));
|
||||
}
|
||||
|
||||
|
41
src/pocketmine/item/enchantment/FireAspectEnchantment.php
Normal file
41
src/pocketmine/item/enchantment/FireAspectEnchantment.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?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\item\enchantment;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
class FireAspectEnchantment extends MeleeWeaponEnchantment{
|
||||
|
||||
public function isApplicableTo(Entity $victim) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDamageBonus(int $enchantmentLevel) : float{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
|
||||
$victim->setOnFire($enchantmentLevel * 4);
|
||||
}
|
||||
}
|
44
src/pocketmine/item/enchantment/KnockbackEnchantment.php
Normal file
44
src/pocketmine/item/enchantment/KnockbackEnchantment.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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\item\enchantment;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Living;
|
||||
|
||||
class KnockbackEnchantment extends MeleeWeaponEnchantment{
|
||||
|
||||
public function isApplicableTo(Entity $victim) : bool{
|
||||
return $victim instanceof Living;
|
||||
}
|
||||
|
||||
public function getDamageBonus(int $enchantmentLevel) : float{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
|
||||
if($victim instanceof Living){
|
||||
$victim->knockBack($attacker, 0, $victim->x - $attacker->x, $victim->z - $attacker->z, $enchantmentLevel * 0.5);
|
||||
}
|
||||
}
|
||||
}
|
63
src/pocketmine/item/enchantment/MeleeWeaponEnchantment.php
Normal file
63
src/pocketmine/item/enchantment/MeleeWeaponEnchantment.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?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\item\enchantment;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Classes extending this class can be applied to weapons and activate when used by a mob to attack another mob in melee
|
||||
* combat.
|
||||
*/
|
||||
abstract class MeleeWeaponEnchantment extends Enchantment{
|
||||
|
||||
/**
|
||||
* Returns whether this melee enchantment has an effect on the target entity. For example, Smite only applies to
|
||||
* undead mobs.
|
||||
*
|
||||
* @param Entity $victim
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isApplicableTo(Entity $victim) : bool;
|
||||
|
||||
/**
|
||||
* Returns the amount of additional damage caused by this enchantment to applicable targets.
|
||||
*
|
||||
* @param int $enchantmentLevel
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
abstract public function getDamageBonus(int $enchantmentLevel) : float;
|
||||
|
||||
/**
|
||||
* Called after damaging the entity to apply any post damage effects to the target.
|
||||
*
|
||||
* @param Entity $attacker
|
||||
* @param Entity $victim
|
||||
* @param int $enchantmentLevel
|
||||
*/
|
||||
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
|
||||
|
||||
}
|
||||
}
|
37
src/pocketmine/item/enchantment/SharpnessEnchantment.php
Normal file
37
src/pocketmine/item/enchantment/SharpnessEnchantment.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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\item\enchantment;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
class SharpnessEnchantment extends MeleeWeaponEnchantment{
|
||||
|
||||
public function isApplicableTo(Entity $victim) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDamageBonus(int $enchantmentLevel) : float{
|
||||
return 0.5 * ($enchantmentLevel + 1);
|
||||
}
|
||||
}
|
Submodule src/pocketmine/lang/locale updated: 638dc473ca...0cd3f8ef30
@ -200,6 +200,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
private $chunkPopulationLock = [];
|
||||
/** @var int */
|
||||
private $chunkPopulationQueueSize = 2;
|
||||
/** @var bool[] */
|
||||
private $generatorRegisteredWorkers = [];
|
||||
|
||||
/** @var bool */
|
||||
private $autoSave = true;
|
||||
@ -245,9 +247,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var bool */
|
||||
private $closed = false;
|
||||
|
||||
/** @var \Closure */
|
||||
private $asyncPoolStartHook;
|
||||
|
||||
public static function chunkHash(int $x, int $z) : int{
|
||||
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF);
|
||||
}
|
||||
@ -363,10 +362,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->temporalPosition = new Position(0, 0, 0, $this);
|
||||
$this->temporalVector = new Vector3(0, 0, 0);
|
||||
$this->tickRate = 1;
|
||||
|
||||
$this->server->getAsyncPool()->addWorkerStartHook($this->asyncPoolStartHook = function(int $worker) : void{
|
||||
$this->registerGeneratorToWorker($worker);
|
||||
});
|
||||
}
|
||||
|
||||
public function getTickRate() : int{
|
||||
@ -382,15 +377,18 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function registerGeneratorToWorker(int $worker) : void{
|
||||
$this->generatorRegisteredWorkers[$worker] = true;
|
||||
$this->server->getAsyncPool()->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getGeneratorOptions()), $worker);
|
||||
}
|
||||
|
||||
public function unregisterGenerator(){
|
||||
$pool = $this->server->getAsyncPool();
|
||||
$pool->removeWorkerStartHook($this->asyncPoolStartHook);
|
||||
foreach($pool->getRunningWorkers() as $i){
|
||||
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
|
||||
if(isset($this->generatorRegisteredWorkers[$i])){
|
||||
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
|
||||
}
|
||||
}
|
||||
$this->generatorRegisteredWorkers = [];
|
||||
}
|
||||
|
||||
public function getBlockMetadata() : BlockMetadataStore{
|
||||
@ -848,18 +846,18 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player[] $target
|
||||
* @param Block[] $blocks
|
||||
* @param int $flags
|
||||
* @param bool $optimizeRebuilds
|
||||
* @param Player[] $target
|
||||
* @param Vector3[] $blocks
|
||||
* @param int $flags
|
||||
* @param bool $optimizeRebuilds
|
||||
*/
|
||||
public function sendBlocks(array $target, array $blocks, int $flags = UpdateBlockPacket::FLAG_NONE, bool $optimizeRebuilds = false){
|
||||
$packets = [];
|
||||
if($optimizeRebuilds){
|
||||
$chunks = [];
|
||||
foreach($blocks as $b){
|
||||
if($b === null){
|
||||
continue;
|
||||
if(!($b instanceof Vector3)){
|
||||
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
|
||||
}
|
||||
$pk = new UpdateBlockPacket();
|
||||
|
||||
@ -874,24 +872,20 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->z = $b->z;
|
||||
|
||||
if($b instanceof Block){
|
||||
$blockId = $b->getId();
|
||||
$blockData = $b->getDamage();
|
||||
$pk->blockRuntimeId = $b->getRuntimeId();
|
||||
}else{
|
||||
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
|
||||
$blockId = $fullBlock >> 4;
|
||||
$blockData = $fullBlock & 0xf;
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
}
|
||||
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($blockId, $blockData);
|
||||
|
||||
$pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE;
|
||||
|
||||
$packets[] = $pk;
|
||||
}
|
||||
}else{
|
||||
foreach($blocks as $b){
|
||||
if($b === null){
|
||||
continue;
|
||||
if(!($b instanceof Vector3)){
|
||||
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
|
||||
}
|
||||
$pk = new UpdateBlockPacket();
|
||||
|
||||
@ -900,16 +894,12 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->z = $b->z;
|
||||
|
||||
if($b instanceof Block){
|
||||
$blockId = $b->getId();
|
||||
$blockData = $b->getDamage();
|
||||
$pk->blockRuntimeId = $b->getRuntimeId();
|
||||
}else{
|
||||
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
|
||||
$blockId = $fullBlock >> 4;
|
||||
$blockData = $fullBlock & 0xf;
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
}
|
||||
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($blockId, $blockData);
|
||||
|
||||
$pk->flags = $flags;
|
||||
|
||||
$packets[] = $pk;
|
||||
@ -1778,7 +1768,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
if($player !== null){
|
||||
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, $blockClicked->getId() === 0 ? PlayerInteractEvent::RIGHT_CLICK_AIR : PlayerInteractEvent::RIGHT_CLICK_BLOCK);
|
||||
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK);
|
||||
if($this->checkSpawnProtection($player, $blockClicked)){
|
||||
$ev->setCancelled(); //set it to cancelled so plugins can bypass this
|
||||
}
|
||||
@ -1866,7 +1856,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
if($playSound){
|
||||
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, BlockFactory::toStaticRuntimeId($hand->getId(), $hand->getDamage()));
|
||||
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, $hand->getRuntimeId());
|
||||
}
|
||||
|
||||
$item->pop();
|
||||
@ -2953,8 +2943,13 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$task = new PopulationTask($this, $chunk);
|
||||
$this->server->getAsyncPool()->submitTask($task);
|
||||
$workerId = $this->server->getAsyncPool()->selectWorker();
|
||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->registerGeneratorToWorker($workerId);
|
||||
}
|
||||
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
|
||||
}
|
||||
|
||||
Timings::$populationTimer->stopTiming();
|
||||
|
@ -75,8 +75,8 @@ class Chunk{
|
||||
/** @var Entity[] */
|
||||
protected $entities = [];
|
||||
|
||||
/** @var int[] */
|
||||
protected $heightMap = [];
|
||||
/** @var \SplFixedArray|int[] */
|
||||
protected $heightMap;
|
||||
|
||||
/** @var string */
|
||||
protected $biomeIds;
|
||||
@ -110,11 +110,11 @@ class Chunk{
|
||||
}
|
||||
|
||||
if(count($heightMap) === 256){
|
||||
$this->heightMap = $heightMap;
|
||||
$this->heightMap = \SplFixedArray::fromArray($heightMap);
|
||||
}else{
|
||||
assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap));
|
||||
$val = ($this->height * 16);
|
||||
$this->heightMap = array_fill(0, 256, $val);
|
||||
$this->heightMap = \SplFixedArray::fromArray(array_fill(0, 256, $val));
|
||||
}
|
||||
|
||||
if(strlen($biomeIds) === 256){
|
||||
@ -739,7 +739,7 @@ class Chunk{
|
||||
* @return int[]
|
||||
*/
|
||||
public function getHeightMapArray() : array{
|
||||
return $this->heightMap;
|
||||
return $this->heightMap->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\level\format\io\leveldb\LevelDB;
|
||||
use pocketmine\level\format\io\region\Anvil;
|
||||
use pocketmine\level\format\io\region\McRegion;
|
||||
use pocketmine\level\format\io\region\PMAnvil;
|
||||
use pocketmine\level\LevelException;
|
||||
|
||||
abstract class LevelProviderManager{
|
||||
protected static $providers = [];
|
||||
@ -42,12 +41,21 @@ abstract class LevelProviderManager{
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @throws LevelException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function addProvider(string $class){
|
||||
if(!is_subclass_of($class, LevelProvider::class)){
|
||||
throw new LevelException("Class is not a subclass of LevelProvider");
|
||||
try{
|
||||
$reflection = new \ReflectionClass($class);
|
||||
}catch(\ReflectionException $e){
|
||||
throw new \InvalidArgumentException("Class $class does not exist");
|
||||
}
|
||||
if(!$reflection->implementsInterface(LevelProvider::class)){
|
||||
throw new \InvalidArgumentException("Class $class does not implement " . LevelProvider::class);
|
||||
}
|
||||
if(!$reflection->isInstantiable()){
|
||||
throw new \InvalidArgumentException("Class $class cannot be constructed");
|
||||
}
|
||||
|
||||
/** @var LevelProvider $class */
|
||||
self::$providers[strtolower($class::getProviderName())] = $class;
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ class RegionLoader{
|
||||
|
||||
public const MAX_SECTOR_LENGTH = 256 << 12; //256 sectors, (1 MiB)
|
||||
public const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
|
||||
public const MAX_REGION_FILE_SIZE = 32 * 32 * self::MAX_SECTOR_LENGTH + self::REGION_HEADER_LENGTH; //32 * 32 1MiB chunks + header size
|
||||
|
||||
public static $COMPRESSION_LEVEL = 7;
|
||||
|
||||
@ -64,13 +63,8 @@ class RegionLoader{
|
||||
$exists = file_exists($this->filePath);
|
||||
if(!$exists){
|
||||
touch($this->filePath);
|
||||
}else{
|
||||
$fileSize = filesize($this->filePath);
|
||||
if($fileSize > self::MAX_REGION_FILE_SIZE){
|
||||
throw new CorruptedRegionException("Corrupted oversized region file found, should be a maximum of " . self::MAX_REGION_FILE_SIZE . " bytes, got " . $fileSize . " bytes");
|
||||
}elseif($fileSize % 4096 !== 0){
|
||||
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
|
||||
}
|
||||
}elseif(filesize($this->filePath) % 4096 !== 0){
|
||||
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
|
||||
}
|
||||
|
||||
$this->filePointer = fopen($this->filePath, "r+b");
|
||||
|
@ -26,11 +26,13 @@ namespace pocketmine\level\generator;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\level\ChunkManager;
|
||||
use pocketmine\level\format\Chunk;
|
||||
use pocketmine\level\generator\object\OreType;
|
||||
use pocketmine\level\generator\populator\Ore;
|
||||
use pocketmine\level\generator\populator\Populator;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Random;
|
||||
|
||||
class Flat extends Generator{
|
||||
/** @var Chunk */
|
||||
@ -41,6 +43,8 @@ class Flat extends Generator{
|
||||
private $structure;
|
||||
/** @var int */
|
||||
private $floorLevel;
|
||||
/** @var int */
|
||||
private $biome;
|
||||
/** @var mixed[] */
|
||||
private $options;
|
||||
/** @var string */
|
||||
@ -55,9 +59,15 @@ class Flat extends Generator{
|
||||
}
|
||||
|
||||
public function __construct(array $options = []){
|
||||
$this->preset = "2;7,2x3,2;1;";
|
||||
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
|
||||
$this->options = $options;
|
||||
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
|
||||
$this->preset = $this->options["preset"];
|
||||
}else{
|
||||
$this->preset = "2;7,2x3,2;1;";
|
||||
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
|
||||
}
|
||||
|
||||
$this->parsePreset();
|
||||
|
||||
if(isset($this->options["decoration"])){
|
||||
$ores = new Ore();
|
||||
@ -90,39 +100,14 @@ class Flat extends Generator{
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function generateBaseChunk(string $preset) : void{
|
||||
$this->preset = $preset;
|
||||
$preset = explode(";", $preset);
|
||||
$version = (int) $preset[0];
|
||||
protected function parsePreset() : void{
|
||||
$preset = explode(";", $this->preset);
|
||||
$blocks = (string) ($preset[1] ?? "");
|
||||
$biome = (int) ($preset[2] ?? 1);
|
||||
$this->biome = (int) ($preset[2] ?? 1);
|
||||
$options = (string) ($preset[3] ?? "");
|
||||
$this->structure = self::parseLayers($blocks);
|
||||
|
||||
$this->floorLevel = $y = count($this->structure);
|
||||
|
||||
$this->chunk = new Chunk(0, 0);
|
||||
$this->chunk->setGenerated();
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$this->chunk->setBiomeId($X, $Z, $biome);
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($this->structure);
|
||||
for($sy = 0; $sy < $count; $sy += 16){
|
||||
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
|
||||
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
|
||||
list($id, $meta) = $this->structure[$y | $sy];
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$subchunk->setBlock($X, $y, $Z, $id, $meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->floorLevel = count($this->structure);
|
||||
|
||||
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
|
||||
foreach($matches[2] as $i => $option){
|
||||
@ -141,14 +126,37 @@ class Flat extends Generator{
|
||||
}
|
||||
}
|
||||
|
||||
public function generateChunk(int $chunkX, int $chunkZ) : void{
|
||||
if($this->chunk === null){
|
||||
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
|
||||
$this->generateBaseChunk($this->options["preset"]);
|
||||
}else{
|
||||
$this->generateBaseChunk($this->preset);
|
||||
protected function generateBaseChunk() : void{
|
||||
$this->chunk = new Chunk(0, 0);
|
||||
$this->chunk->setGenerated();
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$this->chunk->setBiomeId($X, $Z, $this->biome);
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($this->structure);
|
||||
for($sy = 0; $sy < $count; $sy += 16){
|
||||
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
|
||||
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
|
||||
list($id, $meta) = $this->structure[$y | $sy];
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$subchunk->setBlock($X, $y, $Z, $id, $meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function init(ChunkManager $level, Random $random) : void{
|
||||
parent::init($level, $random);
|
||||
$this->generateBaseChunk();
|
||||
}
|
||||
|
||||
public function generateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$chunk = clone $this->chunk;
|
||||
$chunk->setX($chunkX);
|
||||
$chunk->setZ($chunkZ);
|
||||
|
@ -54,14 +54,6 @@ abstract class LightUpdate{
|
||||
$this->subChunkHandler = new SubChunkIteratorManager($this->level);
|
||||
}
|
||||
|
||||
public function addSpreadNode(int $x, int $y, int $z){
|
||||
$this->spreadQueue->enqueue([$x, $y, $z]);
|
||||
}
|
||||
|
||||
public function addRemoveNode(int $x, int $y, int $z, int $oldLight){
|
||||
$this->spreadQueue->enqueue([$x, $y, $z, $oldLight]);
|
||||
}
|
||||
|
||||
abstract protected function getLight(int $x, int $y, int $z) : int;
|
||||
|
||||
abstract protected function setLight(int $x, int $y, int $z, int $level);
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
@ -35,7 +34,7 @@ class DestroyBlockParticle extends Particle{
|
||||
|
||||
public function __construct(Vector3 $pos, Block $b){
|
||||
parent::__construct($pos->x, $pos->y, $pos->z);
|
||||
$this->data = BlockFactory::toStaticRuntimeId($b->getId(), $b->getDamage());
|
||||
$this->data = $b->getRuntimeId();
|
||||
}
|
||||
|
||||
public function encode(){
|
||||
|
@ -24,11 +24,10 @@ declare(strict_types=1);
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
|
||||
class TerrainParticle extends GenericParticle{
|
||||
public function __construct(Vector3 $pos, Block $b){
|
||||
parent::__construct($pos, Particle::TYPE_TERRAIN, BlockFactory::toStaticRuntimeId($b->getId(), $b->getDamage()));
|
||||
parent::__construct($pos, Particle::TYPE_TERRAIN, $b->getRuntimeId());
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,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\NetworkStackLatencyPacket;
|
||||
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
|
||||
@ -115,6 +116,7 @@ use pocketmine\network\mcpe\protocol\SetLastHurtByPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetScorePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetScoreboardIdentityPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTitlePacket;
|
||||
@ -134,6 +136,7 @@ use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateSoftEnumPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
|
||||
use pocketmine\network\mcpe\protocol\WSConnectPacket;
|
||||
|
||||
@ -585,7 +588,19 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleSetScoreboardIdentity(SetScoreboardIdentityPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleUpdateSoftEnum(UpdateSoftEnumPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +239,37 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
}
|
||||
|
||||
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
|
||||
return false; //TODO: GUI stuff
|
||||
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
|
||||
*
|
||||
* @param string $json
|
||||
* @param bool $assoc
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function stupid_json_decode(string $json, bool $assoc = false){
|
||||
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
|
||||
$parts = preg_split('/(?:"(?:\\"|[^"])*"|)\K(,)/', $matches[1]); //Splits on commas not inside quotes, ignoring escaped quotes
|
||||
foreach($parts as $k => $part){
|
||||
$part = trim($part);
|
||||
if($part === ""){
|
||||
$part = "\"\"";
|
||||
}
|
||||
$parts[$k] = $part;
|
||||
}
|
||||
|
||||
$fixed = "[" . implode(",", $parts) . "]";
|
||||
if(($ret = json_decode($fixed, $assoc)) === null){
|
||||
throw new \InvalidArgumentException("Failed to fix JSON: " . json_last_error_msg() . "(original: $json, modified: $fixed)");
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return json_decode($json, $assoc);
|
||||
}
|
||||
|
||||
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
|
||||
|
@ -46,7 +46,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
* Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't
|
||||
* communicate. It's important that we check this to avoid catastrophes.
|
||||
*/
|
||||
private const MCPE_RAKNET_PROTOCOL_VERSION = 8;
|
||||
private const MCPE_RAKNET_PROTOCOL_VERSION = 9;
|
||||
|
||||
/** @var Server */
|
||||
private $server;
|
||||
@ -76,10 +76,6 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
$this->server = $server;
|
||||
|
||||
$this->sleeper = new SleeperNotifier();
|
||||
$server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
|
||||
$this->server->getNetwork()->processInterface($this);
|
||||
});
|
||||
|
||||
$this->rakLib = new RakLibServer(
|
||||
$this->server->getLogger(),
|
||||
\pocketmine\COMPOSER_AUTOLOADER_PATH,
|
||||
@ -92,6 +88,9 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
}
|
||||
|
||||
public function start(){
|
||||
$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
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ class VerifyLoginTask extends AsyncTask{
|
||||
public function onCompletion(Server $server){
|
||||
/** @var Player $player */
|
||||
$player = $this->fetchLocal();
|
||||
if($player->isClosed()){
|
||||
if(!$player->isConnected()){
|
||||
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
|
||||
}else{
|
||||
$player->onVerifyCompleted($this->packet, $this->error, $this->authenticated);
|
||||
|
@ -75,6 +75,9 @@ class AddPlayerPacket extends DataPacket{
|
||||
/** @var EntityLink[] */
|
||||
public $links = [];
|
||||
|
||||
/** @var string */
|
||||
public $deviceId = ""; //TODO: fill player's device ID (???)
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->uuid = $this->getUUID();
|
||||
$this->username = $this->getString();
|
||||
@ -103,6 +106,8 @@ class AddPlayerPacket extends DataPacket{
|
||||
for($i = 0; $i < $linkCount; ++$i){
|
||||
$this->links[$i] = $this->getEntityLink();
|
||||
}
|
||||
|
||||
$this->deviceId = $this->getString();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
@ -133,6 +138,8 @@ class AddPlayerPacket extends DataPacket{
|
||||
foreach($this->links as $link){
|
||||
$this->putEntityLink($link);
|
||||
}
|
||||
|
||||
$this->putString($this->deviceId);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -103,6 +103,13 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
*/
|
||||
public $commandData = [];
|
||||
|
||||
/**
|
||||
* @var CommandEnum[]
|
||||
* List of dynamic command enums, also referred to as "soft" enums. These can by dynamically updated mid-game
|
||||
* without resending this packet.
|
||||
*/
|
||||
public $softEnums = [];
|
||||
|
||||
protected function decodePayload(){
|
||||
for($i = 0, $this->enumValuesCount = $this->getUnsignedVarInt(); $i < $this->enumValuesCount; ++$i){
|
||||
$this->enumValues[] = $this->getString();
|
||||
@ -119,6 +126,10 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$this->commandData[] = $this->getCommandData();
|
||||
}
|
||||
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$this->softEnums[] = $this->getSoftEnum();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEnum() : CommandEnum{
|
||||
@ -133,6 +144,18 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
return $retval;
|
||||
}
|
||||
|
||||
protected function getSoftEnum() : CommandEnum{
|
||||
$retval = new CommandEnum();
|
||||
$retval->enumName = $this->getString();
|
||||
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
//Get the enum value from the initial pile of mess
|
||||
$retval->enumValues[] = $this->getString();
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
protected function putEnum(CommandEnum $enum){
|
||||
$this->putString($enum->enumName);
|
||||
|
||||
@ -147,6 +170,15 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
}
|
||||
}
|
||||
|
||||
protected function putSoftEnum(CommandEnum $enum) : void{
|
||||
$this->putString($enum->enumName);
|
||||
|
||||
$this->putUnsignedVarInt(count($enum->enumValues));
|
||||
foreach($enum->enumValues as $value){
|
||||
$this->putString($value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEnumValueIndex() : int{
|
||||
if($this->enumValuesCount < 256){
|
||||
return $this->getByte();
|
||||
@ -185,13 +217,17 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
if($parameter->paramType & self::ARG_FLAG_ENUM){
|
||||
$index = ($parameter->paramType & 0xffff);
|
||||
$parameter->enum = $this->enums[$index] ?? null;
|
||||
|
||||
assert($parameter->enum !== null, "expected enum at $index, but got none");
|
||||
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){ //postfix (guessing)
|
||||
if($parameter->enum === null){
|
||||
throw new \UnexpectedValueException("expected enum at $index, but got none");
|
||||
}
|
||||
}elseif($parameter->paramType & self::ARG_FLAG_POSTFIX){
|
||||
$index = ($parameter->paramType & 0xffff);
|
||||
$parameter->postfix = $this->postfixes[$index] ?? null;
|
||||
|
||||
assert($parameter->postfix !== null, "expected postfix at $index, but got none");
|
||||
if($parameter->postfix === null){
|
||||
throw new \UnexpectedValueException("expected postfix at $index, but got none");
|
||||
}
|
||||
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
|
||||
throw new \UnexpectedValueException("Invalid parameter type 0x" . dechex($parameter->paramType));
|
||||
}
|
||||
|
||||
$retval->overloads[$overloadIndex][$paramIndex] = $parameter;
|
||||
@ -227,7 +263,7 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
if($key === false){
|
||||
throw new \InvalidStateException("Postfix '$parameter->postfix' not in postfixes array");
|
||||
}
|
||||
$type = $parameter->paramType << 24 | $key;
|
||||
$type = self::ARG_FLAG_POSTFIX | $key;
|
||||
}else{
|
||||
$type = $parameter->paramType;
|
||||
}
|
||||
@ -266,13 +302,12 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
case self::ARG_TYPE_COMMAND:
|
||||
return "command";
|
||||
}
|
||||
}elseif($argtype !== 0){
|
||||
//guessed
|
||||
$baseType = $argtype >> 24;
|
||||
$typeName = $this->argTypeToString(self::ARG_FLAG_VALID | $baseType);
|
||||
}elseif($argtype & self::ARG_FLAG_POSTFIX){
|
||||
$postfix = $this->postfixes[$argtype & 0xffff];
|
||||
|
||||
return $typeName . " (postfix $postfix)";
|
||||
return "int (postfix $postfix)";
|
||||
}else{
|
||||
throw new \UnexpectedValueException("Unknown arg type 0x" . dechex($argtype));
|
||||
}
|
||||
|
||||
return "unknown ($argtype)";
|
||||
@ -334,6 +369,11 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
foreach($this->commandData as $data){
|
||||
$this->putCommandData($data);
|
||||
}
|
||||
|
||||
$this->putUnsignedVarInt(count($this->softEnums));
|
||||
foreach($this->softEnums as $enum){
|
||||
$this->putSoftEnum($enum);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -74,10 +74,6 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
protected function decodeHeader(){
|
||||
$pid = $this->getUnsignedVarInt();
|
||||
assert($pid === static::NETWORK_ID);
|
||||
|
||||
$this->senderSubId = $this->getByte();
|
||||
$this->recipientSubId = $this->getByte();
|
||||
assert($this->senderSubId === 0 and $this->recipientSubId === 0, "Got unexpected non-zero split-screen bytes (byte1: $this->senderSubId, byte2: $this->recipientSubId");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,9 +92,6 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
|
||||
protected function encodeHeader(){
|
||||
$this->putUnsignedVarInt(static::NETWORK_ID);
|
||||
|
||||
$this->putByte($this->senderSubId);
|
||||
$this->putByte($this->recipientSubId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,13 +79,6 @@ class LoginPacket extends DataPacket{
|
||||
protected function decodePayload(){
|
||||
$this->protocol = $this->getInt();
|
||||
|
||||
if($this->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
|
||||
if($this->protocol > 0xffff){ //guess MCPE <= 1.1
|
||||
$this->offset -= 6;
|
||||
$this->protocol = $this->getInt();
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
$this->decodeConnectionRequest();
|
||||
}catch(\Throwable $e){
|
||||
|
@ -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 NetworkStackLatencyPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::NETWORK_STACK_LATENCY_PACKET;
|
||||
|
||||
/** @var int */
|
||||
public $timestamp;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->timestamp = $this->getLLong();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putLLong($this->timestamp);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
return $session->handleNetworkStackLatency($this);
|
||||
}
|
||||
}
|
@ -142,7 +142,10 @@ class PacketPool{
|
||||
static::registerPacket(new LabTablePacket());
|
||||
static::registerPacket(new UpdateBlockSyncedPacket());
|
||||
static::registerPacket(new MoveEntityDeltaPacket());
|
||||
static::registerPacket(new SetScoreboardIdentityPacket());
|
||||
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
|
||||
static::registerPacket(new UpdateSoftEnumPacket());
|
||||
static::registerPacket(new NetworkStackLatencyPacket());
|
||||
|
||||
static::registerPacket(new BatchPacket());
|
||||
}
|
||||
|
@ -43,12 +43,6 @@ class PlayStatusPacket extends DataPacket{
|
||||
/** @var int */
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* Used to determine how to write the packet when we disconnect incompatible clients.
|
||||
*/
|
||||
public $protocol = ProtocolInfo::CURRENT_PROTOCOL;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->status = $this->getInt();
|
||||
}
|
||||
@ -57,14 +51,6 @@ class PlayStatusPacket extends DataPacket{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function encodeHeader(){
|
||||
if($this->protocol < 130){ //MCPE <= 1.1
|
||||
$this->putByte(static::NETWORK_ID);
|
||||
}else{
|
||||
parent::encodeHeader();
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putInt($this->status);
|
||||
}
|
||||
|
@ -39,15 +39,15 @@ interface ProtocolInfo{
|
||||
/**
|
||||
* Actual Minecraft: PE protocol version
|
||||
*/
|
||||
public const CURRENT_PROTOCOL = 274;
|
||||
public const CURRENT_PROTOCOL = 282;
|
||||
/**
|
||||
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
|
||||
*/
|
||||
public const MINECRAFT_VERSION = 'v1.5.0';
|
||||
public const MINECRAFT_VERSION = 'v1.6.0';
|
||||
/**
|
||||
* Version number sent to clients in ping responses.
|
||||
*/
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.5.0';
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.6.0';
|
||||
|
||||
public const LOGIN_PACKET = 0x01;
|
||||
public const PLAY_STATUS_PACKET = 0x02;
|
||||
@ -160,6 +160,9 @@ interface ProtocolInfo{
|
||||
public const LAB_TABLE_PACKET = 0x6d;
|
||||
public const UPDATE_BLOCK_SYNCED_PACKET = 0x6e;
|
||||
public const MOVE_ENTITY_DELTA_PACKET = 0x6f;
|
||||
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x70;
|
||||
public const SET_SCOREBOARD_IDENTITY_PACKET = 0x70;
|
||||
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
|
||||
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
|
||||
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
|
||||
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ class ResourcePacksInfoPacket extends DataPacket{
|
||||
$this->putLLong($entry->getPackSize());
|
||||
$this->putString(""); //TODO: encryption key
|
||||
$this->putString(""); //TODO: subpack name
|
||||
$this->putString(""); //TODO: content identity
|
||||
}
|
||||
$this->putLShort(count($this->resourcePackEntries));
|
||||
foreach($this->resourcePackEntries as $entry){
|
||||
@ -78,6 +79,7 @@ class ResourcePacksInfoPacket extends DataPacket{
|
||||
$this->putLLong($entry->getPackSize());
|
||||
$this->putString(""); //TODO: encryption key
|
||||
$this->putString(""); //TODO: subpack name
|
||||
$this->putString(""); //TODO: content identity
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 pocketmine\network\mcpe\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\ScoreboardIdentityPacketEntry;
|
||||
|
||||
class SetScoreboardIdentityPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::SET_SCOREBOARD_IDENTITY_PACKET;
|
||||
|
||||
public const TYPE_REGISTER_IDENTITY = 0;
|
||||
public const TYPE_CLEAR_IDENTITY = 1;
|
||||
|
||||
/** @var int */
|
||||
public $type;
|
||||
/** @var ScoreboardIdentityPacketEntry[] */
|
||||
public $entries = [];
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->type = $this->getByte();
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$entry = new ScoreboardIdentityPacketEntry();
|
||||
$entry->scoreboardId = $this->getVarLong();
|
||||
if($this->type === self::TYPE_REGISTER_IDENTITY){
|
||||
$entry->uuid = $this->getUUID();
|
||||
}
|
||||
|
||||
$this->entries[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putByte($this->type);
|
||||
$this->putUnsignedVarInt(count($this->entries));
|
||||
foreach($this->entries as $entry){
|
||||
$this->putVarLong($entry->scoreboardId);
|
||||
if($this->type === self::TYPE_REGISTER_IDENTITY){
|
||||
$this->putUUID($entry->uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
return $session->handleSetScoreboardIdentity($this);
|
||||
}
|
||||
}
|
@ -27,12 +27,16 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkBinaryStream;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
|
||||
class StartGamePacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET;
|
||||
|
||||
/** @var string|null */
|
||||
private static $runtimeIdTable;
|
||||
|
||||
/** @var int */
|
||||
public $entityUniqueId;
|
||||
/** @var int */
|
||||
@ -125,6 +129,8 @@ class StartGamePacket extends DataPacket{
|
||||
public $currentTick = 0; //only used if isTrial is true
|
||||
/** @var int */
|
||||
public $enchantmentSeed = 0;
|
||||
/** @var string */
|
||||
public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->entityUniqueId = $this->getEntityUniqueId();
|
||||
@ -175,6 +181,14 @@ class StartGamePacket extends DataPacket{
|
||||
$this->currentTick = $this->getLLong();
|
||||
|
||||
$this->enchantmentSeed = $this->getVarInt();
|
||||
|
||||
$count = $this->getUnsignedVarInt();
|
||||
for($i = 0; $i < $count; ++$i){
|
||||
$this->getString();
|
||||
$this->getLShort();
|
||||
}
|
||||
|
||||
$this->multiplayerCorrelationId = $this->getString();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
@ -226,6 +240,21 @@ class StartGamePacket extends DataPacket{
|
||||
$this->putLLong($this->currentTick);
|
||||
|
||||
$this->putVarInt($this->enchantmentSeed);
|
||||
|
||||
if(self::$runtimeIdTable === null){
|
||||
//this is a really nasty hack, but it'll do for now
|
||||
$stream = new NetworkBinaryStream();
|
||||
$data = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
|
||||
$stream->putUnsignedVarInt(count($data));
|
||||
foreach($data as $v){
|
||||
$stream->putString($v["name"]);
|
||||
$stream->putLShort($v["data"]);
|
||||
}
|
||||
self::$runtimeIdTable = $stream->buffer;
|
||||
}
|
||||
$this->put(self::$runtimeIdTable);
|
||||
|
||||
$this->putString($this->multiplayerCorrelationId);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -0,0 +1,64 @@
|
||||
<?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 UpdateSoftEnumPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::UPDATE_SOFT_ENUM_PACKET;
|
||||
|
||||
public const TYPE_ADD = 0;
|
||||
public const TYPE_REMOVE = 1;
|
||||
public const TYPE_SET = 2;
|
||||
|
||||
/** @var string */
|
||||
public $enumName;
|
||||
/** @var string[] */
|
||||
public $values = [];
|
||||
/** @var int */
|
||||
public $type;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->enumName = $this->getString();
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$this->values[] = $this->getString();
|
||||
}
|
||||
$this->type = $this->getByte();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putString($this->enumName);
|
||||
$this->putUnsignedVarInt(count($this->values));
|
||||
foreach($this->values as $v){
|
||||
$this->putString($v);
|
||||
}
|
||||
$this->putByte($this->type);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
return $session->handleUpdateSoftEnum($this);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\protocol\types;
|
||||
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class ScoreboardIdentityPacketEntry{
|
||||
/** @var int */
|
||||
public $scoreboardId;
|
||||
/** @var UUID|null */
|
||||
public $uuid;
|
||||
|
||||
}
|
@ -85,7 +85,6 @@ class RCON{
|
||||
public function stop(){
|
||||
$this->instance->close();
|
||||
socket_write($this->ipcMainSocket, "\x00"); //make select() return
|
||||
Server::microSleep(50000);
|
||||
$this->instance->quit();
|
||||
|
||||
@socket_close($this->socket);
|
||||
|
@ -26,12 +26,13 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\network\upnp;
|
||||
|
||||
use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
abstract class UPnP{
|
||||
|
||||
public static function PortForward(int $port) : void{
|
||||
if(!Utils::$online){
|
||||
if(!Internet::$online){
|
||||
throw new \RuntimeException("Server is offline");
|
||||
}
|
||||
if(Utils::getOS() !== "win"){
|
||||
@ -46,8 +47,9 @@ abstract class UPnP{
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$com = new \COM("HNetCfg.NATUPnP");
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
|
||||
if($com === false or !is_object($com->StaticPortMappingCollection)){
|
||||
throw new \RuntimeException("Failed to portforward (unsupported?)");
|
||||
throw new \RuntimeException("Failed to portforward using UPnP. Ensure that network discovery is enabled in Control Panel.");
|
||||
}
|
||||
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
@ -55,7 +57,7 @@ abstract class UPnP{
|
||||
}
|
||||
|
||||
public static function RemovePortForward(int $port) : bool{
|
||||
if(!Utils::$online){
|
||||
if(!Internet::$online){
|
||||
return false;
|
||||
}
|
||||
if(Utils::getOS() != "win" or !class_exists("COM")){
|
||||
|
@ -23,8 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\Server;
|
||||
|
||||
abstract class DefaultPermissions{
|
||||
public const ROOT = "pocketmine";
|
||||
|
||||
@ -40,9 +38,9 @@ abstract class DefaultPermissions{
|
||||
|
||||
return self::registerPermission($perm);
|
||||
}
|
||||
Server::getInstance()->getPluginManager()->addPermission($perm);
|
||||
PermissionManager::getInstance()->addPermission($perm);
|
||||
|
||||
return Server::getInstance()->getPluginManager()->getPermission($perm->getName());
|
||||
return PermissionManager::getInstance()->getPermission($perm->getName());
|
||||
}
|
||||
|
||||
public static function registerCorePermissions(){
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginException;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
|
||||
class PermissibleBase implements Permissible{
|
||||
@ -92,7 +91,7 @@ class PermissibleBase implements Permissible{
|
||||
return $this->permissions[$name]->getValue();
|
||||
}
|
||||
|
||||
if(($perm = Server::getInstance()->getPluginManager()->getPermission($name)) !== null){
|
||||
if(($perm = PermissionManager::getInstance()->getPermission($name)) !== null){
|
||||
$perm = $perm->getDefault();
|
||||
|
||||
return $perm === Permission::DEFAULT_TRUE or ($this->isOp() and $perm === Permission::DEFAULT_OP) or (!$this->isOp() and $perm === Permission::DEFAULT_NOT_OP);
|
||||
@ -147,13 +146,14 @@ class PermissibleBase implements Permissible{
|
||||
Timings::$permissibleCalculationTimer->startTiming();
|
||||
|
||||
$this->clearPermissions();
|
||||
$defaults = Server::getInstance()->getPluginManager()->getDefaultPermissions($this->isOp());
|
||||
Server::getInstance()->getPluginManager()->subscribeToDefaultPerms($this->isOp(), $this->parent ?? $this);
|
||||
$permManager = PermissionManager::getInstance();
|
||||
$defaults = $permManager->getDefaultPermissions($this->isOp());
|
||||
$permManager->subscribeToDefaultPerms($this->isOp(), $this->parent ?? $this);
|
||||
|
||||
foreach($defaults as $perm){
|
||||
$name = $perm->getName();
|
||||
$this->permissions[$name] = new PermissionAttachmentInfo($this->parent ?? $this, $name, null, true);
|
||||
Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent ?? $this);
|
||||
$permManager->subscribeToPermission($name, $this->parent ?? $this);
|
||||
$this->calculateChildPermissions($perm->getChildren(), false, null);
|
||||
}
|
||||
|
||||
@ -165,13 +165,11 @@ class PermissibleBase implements Permissible{
|
||||
}
|
||||
|
||||
public function clearPermissions(){
|
||||
$pluginManager = Server::getInstance()->getPluginManager();
|
||||
foreach(array_keys($this->permissions) as $name){
|
||||
$pluginManager->unsubscribeFromPermission($name, $this->parent ?? $this);
|
||||
}
|
||||
$permManager = PermissionManager::getInstance();
|
||||
$permManager->unsubscribeFromAllPermissions($this->parent ?? $this);
|
||||
|
||||
$pluginManager->unsubscribeFromDefaultPerms(false, $this->parent ?? $this);
|
||||
$pluginManager->unsubscribeFromDefaultPerms(true, $this->parent ?? $this);
|
||||
$permManager->unsubscribeFromDefaultPerms(false, $this->parent ?? $this);
|
||||
$permManager->unsubscribeFromDefaultPerms(true, $this->parent ?? $this);
|
||||
|
||||
$this->permissions = [];
|
||||
}
|
||||
@ -182,11 +180,12 @@ class PermissibleBase implements Permissible{
|
||||
* @param PermissionAttachment|null $attachment
|
||||
*/
|
||||
private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment){
|
||||
$permManager = PermissionManager::getInstance();
|
||||
foreach($children as $name => $v){
|
||||
$perm = Server::getInstance()->getPluginManager()->getPermission($name);
|
||||
$perm = $permManager->getPermission($name);
|
||||
$value = ($v xor $invert);
|
||||
$this->permissions[$name] = new PermissionAttachmentInfo($this->parent ?? $this, $name, $attachment, $value);
|
||||
Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent ?? $this);
|
||||
$permManager->subscribeToPermission($name, $this->parent ?? $this);
|
||||
|
||||
if($perm instanceof Permission){
|
||||
$this->calculateChildPermissions($perm->getChildren(), !$value, $attachment);
|
||||
|
@ -27,8 +27,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\Server;
|
||||
|
||||
/**
|
||||
* Represents a permission
|
||||
*/
|
||||
@ -78,6 +76,65 @@ class Permission{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param string $default
|
||||
*
|
||||
* @return Permission[]
|
||||
*/
|
||||
public static function loadPermissions(array $data, string $default = self::DEFAULT_OP) : array{
|
||||
$result = [];
|
||||
foreach($data as $key => $entry){
|
||||
$result[] = self::loadPermission($key, $entry, $default, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param string $default
|
||||
* @param array $output
|
||||
*
|
||||
* @return Permission
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function loadPermission(string $name, array $data, string $default = self::DEFAULT_OP, array &$output = []) : Permission{
|
||||
$desc = null;
|
||||
$children = [];
|
||||
if(isset($data["default"])){
|
||||
$value = Permission::getByName($data["default"]);
|
||||
if($value !== null){
|
||||
$default = $value;
|
||||
}else{
|
||||
throw new \InvalidStateException("'default' key contained unknown value");
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($data["children"])){
|
||||
if(is_array($data["children"])){
|
||||
foreach($data["children"] as $k => $v){
|
||||
if(is_array($v)){
|
||||
if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){
|
||||
$output[] = $perm;
|
||||
}
|
||||
}
|
||||
$children[$k] = true;
|
||||
}
|
||||
}else{
|
||||
throw new \InvalidStateException("'children' key is of wrong type");
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($data["description"])){
|
||||
$desc = $data["description"];
|
||||
}
|
||||
|
||||
return new Permission($name, $desc, $default, $children);
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
@ -158,13 +215,13 @@ class Permission{
|
||||
* @return Permissible[]
|
||||
*/
|
||||
public function getPermissibles() : array{
|
||||
return Server::getInstance()->getPluginManager()->getPermissionSubscriptions($this->name);
|
||||
return PermissionManager::getInstance()->getPermissionSubscriptions($this->name);
|
||||
}
|
||||
|
||||
public function recalculatePermissibles(){
|
||||
$perms = $this->getPermissibles();
|
||||
|
||||
Server::getInstance()->getPluginManager()->recalculatePermissionDefaults($this);
|
||||
PermissionManager::getInstance()->recalculatePermissionDefaults($this);
|
||||
|
||||
foreach($perms as $p){
|
||||
$p->recalculatePermissions();
|
||||
@ -184,10 +241,10 @@ class Permission{
|
||||
$name->recalculatePermissibles();
|
||||
return null;
|
||||
}else{
|
||||
$perm = Server::getInstance()->getPluginManager()->getPermission($name);
|
||||
$perm = PermissionManager::getInstance()->getPermission($name);
|
||||
if($perm === null){
|
||||
$perm = new Permission($name);
|
||||
Server::getInstance()->getPluginManager()->addPermission($perm);
|
||||
PermissionManager::getInstance()->addPermission($perm);
|
||||
}
|
||||
|
||||
$this->addParent($perm, $value);
|
||||
@ -195,64 +252,4 @@ class Permission{
|
||||
return $perm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param string $default
|
||||
*
|
||||
* @return Permission[]
|
||||
*/
|
||||
public static function loadPermissions(array $data, string $default = self::DEFAULT_OP) : array{
|
||||
$result = [];
|
||||
foreach($data as $key => $entry){
|
||||
$result[] = self::loadPermission($key, $entry, $default, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param string $default
|
||||
* @param array $output
|
||||
*
|
||||
* @return Permission
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function loadPermission(string $name, array $data, string $default = self::DEFAULT_OP, array &$output = []) : Permission{
|
||||
$desc = null;
|
||||
$children = [];
|
||||
if(isset($data["default"])){
|
||||
$value = Permission::getByName($data["default"]);
|
||||
if($value !== null){
|
||||
$default = $value;
|
||||
}else{
|
||||
throw new \InvalidStateException("'default' key contained unknown value");
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($data["children"])){
|
||||
if(is_array($data["children"])){
|
||||
foreach($data["children"] as $k => $v){
|
||||
if(is_array($v)){
|
||||
if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){
|
||||
$output[] = $perm;
|
||||
}
|
||||
}
|
||||
$children[$k] = true;
|
||||
}
|
||||
}else{
|
||||
throw new \InvalidStateException("'children' key is of wrong type");
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($data["description"])){
|
||||
$desc = $data["description"];
|
||||
}
|
||||
|
||||
return new Permission($name, $desc, $default, $children);
|
||||
|
||||
}
|
||||
}
|
||||
|
233
src/pocketmine/permission/PermissionManager.php
Normal file
233
src/pocketmine/permission/PermissionManager.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?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\permission;
|
||||
|
||||
use pocketmine\timings\Timings;
|
||||
|
||||
class PermissionManager{
|
||||
/** @var PermissionManager|null */
|
||||
private static $instance = null;
|
||||
|
||||
public static function getInstance() : PermissionManager{
|
||||
if(self::$instance === null){
|
||||
self::$instance = new self;
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** @var Permission[] */
|
||||
protected $permissions = [];
|
||||
/** @var Permission[] */
|
||||
protected $defaultPerms = [];
|
||||
/** @var Permission[] */
|
||||
protected $defaultPermsOp = [];
|
||||
/** @var Permissible[][] */
|
||||
protected $permSubs = [];
|
||||
/** @var Permissible[] */
|
||||
protected $defSubs = [];
|
||||
/** @var Permissible[] */
|
||||
protected $defSubsOp = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return null|Permission
|
||||
*/
|
||||
public function getPermission(string $name){
|
||||
return $this->permissions[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission $permission
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function addPermission(Permission $permission) : bool{
|
||||
if(!isset($this->permissions[$permission->getName()])){
|
||||
$this->permissions[$permission->getName()] = $permission;
|
||||
$this->calculatePermissionDefault($permission);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Permission $permission
|
||||
*/
|
||||
public function removePermission($permission){
|
||||
if($permission instanceof Permission){
|
||||
unset($this->permissions[$permission->getName()]);
|
||||
}else{
|
||||
unset($this->permissions[$permission]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
*
|
||||
* @return Permission[]
|
||||
*/
|
||||
public function getDefaultPermissions(bool $op) : array{
|
||||
if($op){
|
||||
return $this->defaultPermsOp;
|
||||
}else{
|
||||
return $this->defaultPerms;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission $permission
|
||||
*/
|
||||
public function recalculatePermissionDefaults(Permission $permission){
|
||||
if(isset($this->permissions[$permission->getName()])){
|
||||
unset($this->defaultPermsOp[$permission->getName()]);
|
||||
unset($this->defaultPerms[$permission->getName()]);
|
||||
$this->calculatePermissionDefault($permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission $permission
|
||||
*/
|
||||
private function calculatePermissionDefault(Permission $permission){
|
||||
Timings::$permissionDefaultTimer->startTiming();
|
||||
if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
|
||||
$this->defaultPermsOp[$permission->getName()] = $permission;
|
||||
$this->dirtyPermissibles(true);
|
||||
}
|
||||
|
||||
if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
|
||||
$this->defaultPerms[$permission->getName()] = $permission;
|
||||
$this->dirtyPermissibles(false);
|
||||
}
|
||||
Timings::$permissionDefaultTimer->startTiming();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
*/
|
||||
private function dirtyPermissibles(bool $op){
|
||||
foreach($this->getDefaultPermSubscriptions($op) as $p){
|
||||
$p->recalculatePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function subscribeToPermission(string $permission, Permissible $permissible){
|
||||
if(!isset($this->permSubs[$permission])){
|
||||
$this->permSubs[$permission] = [];
|
||||
}
|
||||
$this->permSubs[$permission][spl_object_hash($permissible)] = $permissible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromPermission(string $permission, Permissible $permissible){
|
||||
if(isset($this->permSubs[$permission])){
|
||||
unset($this->permSubs[$permission][spl_object_hash($permissible)]);
|
||||
if(count($this->permSubs[$permission]) === 0){
|
||||
unset($this->permSubs[$permission]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromAllPermissions(Permissible $permissible) : void{
|
||||
foreach($this->permSubs as $permission => &$subs){
|
||||
unset($subs[spl_object_hash($permissible)]);
|
||||
if(empty($subs)){
|
||||
unset($this->permSubs[$permission]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
*
|
||||
* @return array|Permissible[]
|
||||
*/
|
||||
public function getPermissionSubscriptions(string $permission) : array{
|
||||
return $this->permSubs[$permission] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function subscribeToDefaultPerms(bool $op, Permissible $permissible){
|
||||
if($op){
|
||||
$this->defSubsOp[spl_object_hash($permissible)] = $permissible;
|
||||
}else{
|
||||
$this->defSubs[spl_object_hash($permissible)] = $permissible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromDefaultPerms(bool $op, Permissible $permissible){
|
||||
if($op){
|
||||
unset($this->defSubsOp[spl_object_hash($permissible)]);
|
||||
}else{
|
||||
unset($this->defSubs[spl_object_hash($permissible)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
*
|
||||
* @return Permissible[]
|
||||
*/
|
||||
public function getDefaultPermSubscriptions(bool $op) : array{
|
||||
if($op){
|
||||
return $this->defSubsOp;
|
||||
}
|
||||
|
||||
return $this->defSubs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Permission[]
|
||||
*/
|
||||
public function getPermissions() : array{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
public function clearPermissions() : void{
|
||||
$this->permissions = [];
|
||||
$this->defaultPerms = [];
|
||||
$this->defaultPermsOp = [];
|
||||
}
|
||||
}
|
@ -55,6 +55,12 @@ interface Plugin extends CommandExecutor{
|
||||
public function isEnabled() : bool;
|
||||
|
||||
/**
|
||||
* Called by the plugin manager when the plugin is enabled or disabled to inform the plugin of its enabled state.
|
||||
*
|
||||
* @internal This is intended for core use only and should not be used by plugins
|
||||
* @see PluginManager::enablePlugin()
|
||||
* @see PluginManager::disablePlugin()
|
||||
*
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setEnabled(bool $enabled = true) : void;
|
||||
|
@ -93,6 +93,12 @@ abstract class PluginBase implements Plugin{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the plugin manager when the plugin is enabled or disabled to inform the plugin of its enabled state.
|
||||
*
|
||||
* @internal This is intended for core use only and should not be used by plugins
|
||||
* @see PluginManager::enablePlugin()
|
||||
* @see PluginManager::disablePlugin()
|
||||
*
|
||||
* @param bool $enabled
|
||||
*/
|
||||
final public function setEnabled(bool $enabled = true) : void{
|
||||
@ -256,12 +262,8 @@ abstract class PluginBase implements Plugin{
|
||||
}
|
||||
|
||||
public function reloadConfig(){
|
||||
@mkdir($this->dataFolder);
|
||||
$this->saveDefaultConfig();
|
||||
$this->config = new Config($this->configFile);
|
||||
if(($configStream = $this->getResource("config.yml")) !== null){
|
||||
$this->config->setDefaults(yaml_parse(Config::fixYAMLIndexes(stream_get_contents($configStream))));
|
||||
fclose($configStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,13 +34,13 @@ use pocketmine\event\plugin\PluginEnableEvent;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\permission\Permissible;
|
||||
use pocketmine\permission\Permission;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
/**
|
||||
* Manages all the plugins, Permissions and Permissibles
|
||||
* Manages all the plugins
|
||||
*/
|
||||
class PluginManager{
|
||||
private const MAX_EVENT_CALL_DEPTH = 50;
|
||||
@ -61,36 +61,6 @@ class PluginManager{
|
||||
*/
|
||||
protected $enabledPlugins = [];
|
||||
|
||||
/**
|
||||
* @var Permission[]
|
||||
*/
|
||||
protected $permissions = [];
|
||||
|
||||
/**
|
||||
* @var Permission[]
|
||||
*/
|
||||
protected $defaultPerms = [];
|
||||
|
||||
/**
|
||||
* @var Permission[]
|
||||
*/
|
||||
protected $defaultPermsOp = [];
|
||||
|
||||
/**
|
||||
* @var Permissible[][]
|
||||
*/
|
||||
protected $permSubs = [];
|
||||
|
||||
/**
|
||||
* @var Permissible[]
|
||||
*/
|
||||
protected $defSubs = [];
|
||||
|
||||
/**
|
||||
* @var Permissible[]
|
||||
*/
|
||||
protected $defSubsOp = [];
|
||||
|
||||
/**
|
||||
* @var PluginLoader[]
|
||||
*/
|
||||
@ -178,6 +148,9 @@ class PluginManager{
|
||||
$this->server->getLogger()->error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory");
|
||||
return null;
|
||||
}
|
||||
if(!file_exists($dataFolder)){
|
||||
mkdir($dataFolder, 0777, true);
|
||||
}
|
||||
|
||||
$prefixed = $loader->getAccessProtocol() . $path;
|
||||
$loader->loadPlugin($prefixed);
|
||||
@ -425,166 +398,147 @@ class PluginManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::getPermission()
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return null|Permission
|
||||
*/
|
||||
public function getPermission(string $name){
|
||||
return $this->permissions[$name] ?? null;
|
||||
return PermissionManager::getInstance()->getPermission($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::addPermission()
|
||||
*
|
||||
* @param Permission $permission
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function addPermission(Permission $permission) : bool{
|
||||
if(!isset($this->permissions[$permission->getName()])){
|
||||
$this->permissions[$permission->getName()] = $permission;
|
||||
$this->calculatePermissionDefault($permission);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return PermissionManager::getInstance()->addPermission($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::removePermission()
|
||||
*
|
||||
* @param string|Permission $permission
|
||||
*/
|
||||
public function removePermission($permission){
|
||||
if($permission instanceof Permission){
|
||||
unset($this->permissions[$permission->getName()]);
|
||||
}else{
|
||||
unset($this->permissions[$permission]);
|
||||
}
|
||||
PermissionManager::getInstance()->removePermission($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::getDefaultPermissions()
|
||||
*
|
||||
* @param bool $op
|
||||
*
|
||||
* @return Permission[]
|
||||
*/
|
||||
public function getDefaultPermissions(bool $op) : array{
|
||||
if($op){
|
||||
return $this->defaultPermsOp;
|
||||
}else{
|
||||
return $this->defaultPerms;
|
||||
}
|
||||
return PermissionManager::getInstance()->getDefaultPermissions($op);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::recalculatePermissionDefaults()
|
||||
*
|
||||
* @param Permission $permission
|
||||
*/
|
||||
public function recalculatePermissionDefaults(Permission $permission){
|
||||
if(isset($this->permissions[$permission->getName()])){
|
||||
unset($this->defaultPermsOp[$permission->getName()]);
|
||||
unset($this->defaultPerms[$permission->getName()]);
|
||||
$this->calculatePermissionDefault($permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission $permission
|
||||
*/
|
||||
private function calculatePermissionDefault(Permission $permission){
|
||||
Timings::$permissionDefaultTimer->startTiming();
|
||||
if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
|
||||
$this->defaultPermsOp[$permission->getName()] = $permission;
|
||||
$this->dirtyPermissibles(true);
|
||||
}
|
||||
|
||||
if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
|
||||
$this->defaultPerms[$permission->getName()] = $permission;
|
||||
$this->dirtyPermissibles(false);
|
||||
}
|
||||
Timings::$permissionDefaultTimer->startTiming();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $op
|
||||
*/
|
||||
private function dirtyPermissibles(bool $op){
|
||||
foreach($this->getDefaultPermSubscriptions($op) as $p){
|
||||
$p->recalculatePermissions();
|
||||
}
|
||||
PermissionManager::getInstance()->recalculatePermissionDefaults($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::subscribeToPermission()
|
||||
*
|
||||
* @param string $permission
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function subscribeToPermission(string $permission, Permissible $permissible){
|
||||
if(!isset($this->permSubs[$permission])){
|
||||
$this->permSubs[$permission] = [];
|
||||
}
|
||||
$this->permSubs[$permission][spl_object_hash($permissible)] = $permissible;
|
||||
PermissionManager::getInstance()->subscribeToPermission($permission, $permissible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::unsubscribeFromPermission()
|
||||
*
|
||||
* @param string $permission
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromPermission(string $permission, Permissible $permissible){
|
||||
if(isset($this->permSubs[$permission])){
|
||||
unset($this->permSubs[$permission][spl_object_hash($permissible)]);
|
||||
if(count($this->permSubs[$permission]) === 0){
|
||||
unset($this->permSubs[$permission]);
|
||||
}
|
||||
}
|
||||
PermissionManager::getInstance()->unsubscribeFromPermission($permission, $permissible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::unsubscribeFromAllPermissions()
|
||||
*
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromAllPermissions(Permissible $permissible) : void{
|
||||
PermissionManager::getInstance()->unsubscribeFromAllPermissions($permissible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::getPermissionSubscriptions()
|
||||
*
|
||||
* @param string $permission
|
||||
*
|
||||
* @return array|Permissible[]
|
||||
*/
|
||||
public function getPermissionSubscriptions(string $permission) : array{
|
||||
return $this->permSubs[$permission] ?? [];
|
||||
return PermissionManager::getInstance()->getPermissionSubscriptions($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::subscribeToDefaultPerms()
|
||||
*
|
||||
* @param bool $op
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function subscribeToDefaultPerms(bool $op, Permissible $permissible){
|
||||
if($op){
|
||||
$this->defSubsOp[spl_object_hash($permissible)] = $permissible;
|
||||
}else{
|
||||
$this->defSubs[spl_object_hash($permissible)] = $permissible;
|
||||
}
|
||||
PermissionManager::getInstance()->subscribeToDefaultPerms($op, $permissible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::unsubscribeFromDefaultPerms()
|
||||
*
|
||||
* @param bool $op
|
||||
* @param Permissible $permissible
|
||||
*/
|
||||
public function unsubscribeFromDefaultPerms(bool $op, Permissible $permissible){
|
||||
if($op){
|
||||
unset($this->defSubsOp[spl_object_hash($permissible)]);
|
||||
}else{
|
||||
unset($this->defSubs[spl_object_hash($permissible)]);
|
||||
}
|
||||
PermissionManager::getInstance()->unsubscribeFromDefaultPerms($op, $permissible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::getDefaultPermSubscriptions()
|
||||
*
|
||||
* @param bool $op
|
||||
*
|
||||
* @return Permissible[]
|
||||
*/
|
||||
public function getDefaultPermSubscriptions(bool $op) : array{
|
||||
if($op){
|
||||
return $this->defSubsOp;
|
||||
}
|
||||
|
||||
return $this->defSubs;
|
||||
return PermissionManager::getInstance()->getDefaultPermSubscriptions($op);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see PermissionManager::getPermissions()
|
||||
*
|
||||
* @return Permission[]
|
||||
*/
|
||||
public function getPermissions() : array{
|
||||
return $this->permissions;
|
||||
return PermissionManager::getInstance()->getPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -604,8 +558,9 @@ class PluginManager{
|
||||
try{
|
||||
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.enable", [$plugin->getDescription()->getFullName()]));
|
||||
|
||||
$permManager = PermissionManager::getInstance();
|
||||
foreach($plugin->getDescription()->getPermissions() as $perm){
|
||||
$this->addPermission($perm);
|
||||
$permManager->addPermission($perm);
|
||||
}
|
||||
$plugin->getScheduler()->setEnabled(true);
|
||||
$plugin->setEnabled(true);
|
||||
@ -700,8 +655,9 @@ class PluginManager{
|
||||
}
|
||||
$plugin->getScheduler()->shutdown();
|
||||
HandlerList::unregisterAll($plugin);
|
||||
$permManager = PermissionManager::getInstance();
|
||||
foreach($plugin->getDescription()->getPermissions() as $perm){
|
||||
$this->removePermission($perm);
|
||||
$permManager->removePermission($perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -717,9 +673,6 @@ class PluginManager{
|
||||
$this->plugins = [];
|
||||
$this->enabledPlugins = [];
|
||||
$this->fileAssociations = [];
|
||||
$this->permissions = [];
|
||||
$this->defaultPerms = [];
|
||||
$this->defaultPermsOp = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -791,7 +744,21 @@ class PluginManager{
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
|
||||
}
|
||||
$ignoreCancelled = isset($tags["ignoreCancelled"]) && strtolower($tags["ignoreCancelled"]) === "true";
|
||||
|
||||
$ignoreCancelled = false;
|
||||
if(isset($tags["ignoreCancelled"])){
|
||||
switch(strtolower($tags["ignoreCancelled"])){
|
||||
case "true":
|
||||
case "":
|
||||
$ignoreCancelled = true;
|
||||
break;
|
||||
case "false":
|
||||
$ignoreCancelled = false;
|
||||
break;
|
||||
default:
|
||||
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $method->getParameters();
|
||||
try{
|
||||
|
File diff suppressed because one or more lines are too long
@ -168,18 +168,15 @@ class AsyncPool{
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits an AsyncTask to the worker with the least load. If all workers are busy and the pool is not full, a new
|
||||
* worker may be started.
|
||||
* Selects a worker ID to run a task.
|
||||
*
|
||||
* @param AsyncTask $task
|
||||
* - if an idle worker is found, it will be selected
|
||||
* - else, if the worker pool is not full, a new worker will be selected
|
||||
* - else, the worker with the smallest backlog is chosen.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function submitTask(AsyncTask $task) : int{
|
||||
if($task->getTaskId() !== null){
|
||||
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
|
||||
}
|
||||
|
||||
public function selectWorker() : int{
|
||||
$worker = null;
|
||||
$minUsage = PHP_INT_MAX;
|
||||
foreach($this->workerUsage as $i => $usage){
|
||||
@ -202,7 +199,23 @@ class AsyncPool{
|
||||
}
|
||||
|
||||
assert($worker !== null);
|
||||
return $worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits an AsyncTask to the worker with the least load. If all workers are busy and the pool is not full, a new
|
||||
* worker may be started.
|
||||
*
|
||||
* @param AsyncTask $task
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function submitTask(AsyncTask $task) : int{
|
||||
if($task->getTaskId() !== null){
|
||||
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
|
||||
}
|
||||
|
||||
$worker = $this->selectWorker();
|
||||
$this->submitTaskToWorker($task, $worker);
|
||||
return $worker;
|
||||
}
|
||||
@ -304,6 +317,19 @@ class AsyncPool{
|
||||
$this->collectWorkers();
|
||||
}
|
||||
|
||||
public function shutdownUnusedWorkers() : int{
|
||||
$ret = 0;
|
||||
foreach($this->workerUsage as $i => $usage){
|
||||
if($usage === 0){
|
||||
$this->workers[$i]->quit();
|
||||
unset($this->workers[$i], $this->workerUsage[$i]);
|
||||
$ret++;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all pending tasks and shuts down all the workers in the pool.
|
||||
*/
|
||||
|
@ -23,12 +23,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\InternetException;
|
||||
|
||||
/**
|
||||
* Executes a consecutive list of cURL operations.
|
||||
*
|
||||
* The result of this AsyncTask is an array of arrays (returned from {@link Utils::simpleCurl}) or RuntimeException objects.
|
||||
* The result of this AsyncTask is an array of arrays (returned from {@link Utils::simpleCurl}) or InternetException objects.
|
||||
*
|
||||
* @package pocketmine\scheduler
|
||||
*/
|
||||
@ -55,8 +56,8 @@ class BulkCurlTask extends AsyncTask{
|
||||
$results = [];
|
||||
foreach($operations as $op){
|
||||
try{
|
||||
$results[] = Utils::simpleCurl($op["page"], $op["timeout"] ?? 10, $op["extraHeaders"] ?? [], $op["extraOpts"] ?? []);
|
||||
}catch(\RuntimeException $e){
|
||||
$results[] = Internet::simpleCurl($op["page"], $op["timeout"] ?? 10, $op["extraHeaders"] ?? [], $op["extraOpts"] ?? []);
|
||||
}catch(InternetException $e){
|
||||
$results[] = $e;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\UUID;
|
||||
use pocketmine\utils\VersionString;
|
||||
@ -147,7 +148,7 @@ class SendUsageTask extends AsyncTask{
|
||||
|
||||
public function onRun(){
|
||||
try{
|
||||
Utils::postURL($this->endpoint, $this->data, 5, [
|
||||
Internet::postURL($this->endpoint, $this->data, 5, [
|
||||
"Content-Type: application/json",
|
||||
"Content-Length: " . strlen($this->data)
|
||||
]);
|
||||
|
@ -209,6 +209,9 @@ abstract class Tile extends Position{
|
||||
* @return CompoundTag
|
||||
*/
|
||||
public static function createNBT(Vector3 $pos, ?int $face = null, ?Item $item = null, ?Player $player = null) : CompoundTag{
|
||||
if(static::class === self::class){
|
||||
throw new \BadMethodCallException(__METHOD__ . " must be called from the scope of a child class");
|
||||
}
|
||||
$nbt = new CompoundTag("", [
|
||||
new StringTag(self::TAG_ID, static::getSaveId()),
|
||||
new IntTag(self::TAG_X, (int) $pos->x),
|
||||
|
@ -27,7 +27,7 @@ namespace pocketmine\updater;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\Internet;
|
||||
|
||||
class UpdateCheckTask extends AsyncTask{
|
||||
|
||||
@ -45,7 +45,7 @@ class UpdateCheckTask extends AsyncTask{
|
||||
|
||||
public function onRun(){
|
||||
$error = "";
|
||||
$response = Utils::getURL($this->endpoint . "?channel=" . $this->channel, 4, [], $error);
|
||||
$response = Internet::getURL($this->endpoint . "?channel=" . $this->channel, 4, [], $error);
|
||||
$this->error = $error;
|
||||
|
||||
if($response !== false){
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user