mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-10 19:48:13 +00:00
Compare commits
145 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
@ -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
|
||||
@ -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.1";
|
||||
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);
|
||||
|
||||
|
@ -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",
|
||||
@ -2533,13 +2529,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -610,6 +610,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 +749,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 +789,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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +144,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;
|
||||
}
|
||||
@ -200,10 +200,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,19 +40,44 @@ 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function saveNBT() : void{
|
||||
parent::saveNBT();
|
||||
|
||||
$this->namedtag->setByte(self::TAG_PICKUP, $this->pickupMode, true);
|
||||
}
|
||||
|
||||
public function isCritical() : bool{
|
||||
return $this->getGenericFlag(self::DATA_FLAG_CRITICAL);
|
||||
}
|
||||
@ -70,6 +95,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;
|
||||
@ -95,6 +134,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 +171,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;
|
||||
}
|
||||
|
@ -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;
|
||||
@ -74,6 +75,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 +114,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
|
||||
@ -124,6 +145,7 @@ abstract class Projectile extends Entity{
|
||||
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 +162,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{
|
||||
|
@ -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($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);
|
||||
}
|
||||
|
||||
|
@ -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,7 +954,7 @@ 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());
|
||||
$item->setDamage($meta);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
@ -890,8 +888,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
}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();
|
||||
|
||||
@ -2953,8 +2951,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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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,13 @@ class PermissibleBase implements Permissible{
|
||||
}
|
||||
|
||||
public function clearPermissions(){
|
||||
$pluginManager = Server::getInstance()->getPluginManager();
|
||||
$permManager = PermissionManager::getInstance();
|
||||
foreach(array_keys($this->permissions) as $name){
|
||||
$pluginManager->unsubscribeFromPermission($name, $this->parent ?? $this);
|
||||
$permManager->unsubscribeFromPermission($name, $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 +182,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);
|
||||
|
||||
}
|
||||
}
|
||||
|
221
src/pocketmine/permission/PermissionManager.php
Normal file
221
src/pocketmine/permission/PermissionManager.php
Normal file
@ -0,0 +1,221 @@
|
||||
<?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 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 = [];
|
||||
}
|
||||
}
|
@ -256,12 +256,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,137 @@ 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::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 +548,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 +645,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 +663,6 @@ class PluginManager{
|
||||
$this->plugins = [];
|
||||
$this->enabledPlugins = [];
|
||||
$this->fileAssociations = [];
|
||||
$this->permissions = [];
|
||||
$this->defaultPerms = [];
|
||||
$this->defaultPermsOp = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -791,7 +734,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){
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\scheduler\FileWriteTask;
|
||||
use pocketmine\Server;
|
||||
|
||||
|
||||
@ -186,11 +185,9 @@ class Config{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $async
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(bool $async = false) : bool{
|
||||
public function save() : bool{
|
||||
if($this->correct){
|
||||
try{
|
||||
$content = null;
|
||||
@ -214,11 +211,7 @@ class Config{
|
||||
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
|
||||
}
|
||||
|
||||
if($async){
|
||||
Server::getInstance()->getAsyncPool()->submitTask(new FileWriteTask($this->file, $content));
|
||||
}else{
|
||||
file_put_contents($this->file, $content);
|
||||
}
|
||||
file_put_contents($this->file, $content);
|
||||
}catch(\Throwable $e){
|
||||
$logger = Server::getInstance()->getLogger();
|
||||
$logger->critical("Could not save Config " . $this->file . ": " . $e->getMessage());
|
||||
|
185
src/pocketmine/utils/Internet.php
Normal file
185
src/pocketmine/utils/Internet.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
class Internet{
|
||||
public static $ip = false;
|
||||
public static $online = true;
|
||||
|
||||
/**
|
||||
* Gets the External IP using an external service, it is cached
|
||||
*
|
||||
* @param bool $force default false, force IP check even when cached
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function getIP(bool $force = false){
|
||||
if(!self::$online){
|
||||
return false;
|
||||
}elseif(self::$ip !== false and !$force){
|
||||
return self::$ip;
|
||||
}
|
||||
|
||||
$ip = self::getURL("http://api.ipify.org/");
|
||||
if($ip !== false){
|
||||
return self::$ip = $ip;
|
||||
}
|
||||
|
||||
$ip = self::getURL("http://checkip.dyndns.org/");
|
||||
if($ip !== false and preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', trim(strip_tags($ip)), $matches) > 0){
|
||||
return self::$ip = $matches[1];
|
||||
}
|
||||
|
||||
$ip = self::getURL("http://www.checkip.org/");
|
||||
if($ip !== false and preg_match('#">([0-9a-fA-F\:\.]*)</span>#', $ip, $matches) > 0){
|
||||
return self::$ip = $matches[1];
|
||||
}
|
||||
|
||||
$ip = self::getURL("http://checkmyip.org/");
|
||||
if($ip !== false and preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){
|
||||
return self::$ip = $matches[1];
|
||||
}
|
||||
|
||||
$ip = self::getURL("http://ifconfig.me/ip");
|
||||
if($ip !== false and trim($ip) != ""){
|
||||
return self::$ip = trim($ip);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* GETs an URL using cURL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @param string $page
|
||||
* @param int $timeout default 10
|
||||
* @param array $extraHeaders
|
||||
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
|
||||
* @param array[] &$headers
|
||||
* @param int &$httpCode
|
||||
*
|
||||
* @return bool|mixed false if an error occurred, mixed data if successful.
|
||||
*/
|
||||
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
|
||||
try{
|
||||
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders);
|
||||
return $ret;
|
||||
}catch(InternetException $ex){
|
||||
$err = $ex->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POSTs data to an URL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @param string $page
|
||||
* @param array|string $args
|
||||
* @param int $timeout
|
||||
* @param array $extraHeaders
|
||||
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
|
||||
* @param array[] &$headers
|
||||
* @param int &$httpCode
|
||||
*
|
||||
* @return bool|mixed false if an error occurred, mixed data if successful.
|
||||
*/
|
||||
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
|
||||
try{
|
||||
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders, [
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $args
|
||||
]);
|
||||
return $ret;
|
||||
}catch(InternetException $ex){
|
||||
$err = $ex->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General cURL shorthand function.
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @param string $page
|
||||
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
|
||||
* @param string[] $extraHeaders extra headers to send as a plain string array
|
||||
* @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map
|
||||
* @param callable|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle.
|
||||
*
|
||||
* @return array a plain array of three [result body : string, headers : array[], HTTP response code : int]. Headers are grouped by requests with strtolower(header name) as keys and header value as values
|
||||
*
|
||||
* @throws InternetException if a cURL error occurs
|
||||
*/
|
||||
public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], callable $onSuccess = null){
|
||||
if(!self::$online){
|
||||
throw new InternetException("Cannot execute web request while offline");
|
||||
}
|
||||
|
||||
$ch = curl_init($page);
|
||||
|
||||
curl_setopt_array($ch, $extraOpts + [
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_FORBID_REUSE => 1,
|
||||
CURLOPT_FRESH_CONNECT => 1,
|
||||
CURLOPT_AUTOREFERER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
|
||||
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
|
||||
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
|
||||
CURLOPT_HEADER => true
|
||||
]);
|
||||
try{
|
||||
$raw = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
if($error !== ""){
|
||||
throw new InternetException($error);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$rawHeaders = substr($raw, 0, $headerSize);
|
||||
$body = substr($raw, $headerSize);
|
||||
$headers = [];
|
||||
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
|
||||
$headerGroup = [];
|
||||
foreach(explode("\r\n", $rawHeaderGroup) as $line){
|
||||
$nameValue = explode(":", $line, 2);
|
||||
if(isset($nameValue[1])){
|
||||
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
|
||||
}
|
||||
}
|
||||
$headers[] = $headerGroup;
|
||||
}
|
||||
if($onSuccess !== null){
|
||||
$onSuccess($ch);
|
||||
}
|
||||
return [$body, $headers, $httpCode];
|
||||
}finally{
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
}
|
28
src/pocketmine/utils/InternetException.php
Normal file
28
src/pocketmine/utils/InternetException.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\utils;
|
||||
|
||||
class InternetException extends \RuntimeException{
|
||||
|
||||
}
|
@ -61,7 +61,7 @@ abstract class Timezone{
|
||||
break;
|
||||
}
|
||||
|
||||
if($response = Utils::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
|
||||
if($response = Internet::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
|
||||
and $ip_geolocation_data = json_decode($response, true)
|
||||
and $ip_geolocation_data['status'] !== 'fail'
|
||||
and date_default_timezone_set($ip_geolocation_data['timezone'])
|
||||
|
@ -33,8 +33,6 @@ use pocketmine\ThreadManager;
|
||||
* Big collection of functions
|
||||
*/
|
||||
class Utils{
|
||||
public static $online = true;
|
||||
public static $ip = false;
|
||||
public static $os;
|
||||
/** @var UUID|null */
|
||||
private static $serverUniqueId = null;
|
||||
@ -123,55 +121,15 @@ class Utils{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the External IP using an external service, it is cached
|
||||
* @deprecated
|
||||
* @see Internet::getIP()
|
||||
*
|
||||
* @param bool $force default false, force IP check even when cached
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function getIP(bool $force = false){
|
||||
if(!Utils::$online){
|
||||
return false;
|
||||
}elseif(Utils::$ip !== false and !$force){
|
||||
return Utils::$ip;
|
||||
}
|
||||
|
||||
do{
|
||||
$ip = Utils::getURL("http://api.ipify.org/");
|
||||
if($ip !== false){
|
||||
Utils::$ip = $ip;
|
||||
break;
|
||||
}
|
||||
|
||||
$ip = Utils::getURL("http://checkip.dyndns.org/");
|
||||
if($ip !== false and preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', trim(strip_tags($ip)), $matches) > 0){
|
||||
Utils::$ip = $matches[1];
|
||||
break;
|
||||
}
|
||||
|
||||
$ip = Utils::getURL("http://www.checkip.org/");
|
||||
if($ip !== false and preg_match('#">([0-9a-fA-F\:\.]*)</span>#', $ip, $matches) > 0){
|
||||
Utils::$ip = $matches[1];
|
||||
break;
|
||||
}
|
||||
|
||||
$ip = Utils::getURL("http://checkmyip.org/");
|
||||
if($ip !== false and preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){
|
||||
Utils::$ip = $matches[1];
|
||||
break;
|
||||
}
|
||||
|
||||
$ip = Utils::getURL("http://ifconfig.me/ip");
|
||||
if($ip !== false and trim($ip) != ""){
|
||||
Utils::$ip = trim($ip);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}while(false);
|
||||
|
||||
return Utils::$ip;
|
||||
return Internet::getIP($force);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,8 +331,8 @@ class Utils{
|
||||
}*/
|
||||
|
||||
/**
|
||||
* GETs an URL using cURL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
* @deprecated
|
||||
* @see Internet::getURL()
|
||||
*
|
||||
* @param string $page
|
||||
* @param int $timeout default 10
|
||||
@ -386,18 +344,12 @@ class Utils{
|
||||
* @return bool|mixed false if an error occurred, mixed data if successful.
|
||||
*/
|
||||
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
|
||||
try{
|
||||
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders);
|
||||
return $ret;
|
||||
}catch(\RuntimeException $ex){
|
||||
$err = $ex->getMessage();
|
||||
return false;
|
||||
}
|
||||
return Internet::getURL($page, $timeout, $extraHeaders, $err, $headers, $httpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* POSTs data to an URL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
* @deprecated
|
||||
* @see Internet::postURL()
|
||||
*
|
||||
* @param string $page
|
||||
* @param array|string $args
|
||||
@ -410,22 +362,12 @@ class Utils{
|
||||
* @return bool|mixed false if an error occurred, mixed data if successful.
|
||||
*/
|
||||
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
|
||||
try{
|
||||
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders, [
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $args
|
||||
]);
|
||||
return $ret;
|
||||
}catch(\RuntimeException $ex){
|
||||
$err = $ex->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
return Internet::postURL($page, $args, $timeout, $extraHeaders, $err, $headers, $httpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* General cURL shorthand function.
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
* @deprecated
|
||||
* @see Internet::simpleCurl()
|
||||
*
|
||||
* @param string $page
|
||||
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
|
||||
@ -438,53 +380,7 @@ class Utils{
|
||||
* @throws \RuntimeException if a cURL error occurs
|
||||
*/
|
||||
public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], callable $onSuccess = null){
|
||||
if(!Utils::$online){
|
||||
throw new \RuntimeException("Server is offline");
|
||||
}
|
||||
|
||||
$ch = curl_init($page);
|
||||
|
||||
curl_setopt_array($ch, $extraOpts + [
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_FORBID_REUSE => 1,
|
||||
CURLOPT_FRESH_CONNECT => 1,
|
||||
CURLOPT_AUTOREFERER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
|
||||
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
|
||||
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
|
||||
CURLOPT_HEADER => true
|
||||
]);
|
||||
try{
|
||||
$raw = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
if($error !== ""){
|
||||
throw new \RuntimeException($error);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$rawHeaders = substr($raw, 0, $headerSize);
|
||||
$body = substr($raw, $headerSize);
|
||||
$headers = [];
|
||||
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
|
||||
$headerGroup = [];
|
||||
foreach(explode("\r\n", $rawHeaderGroup) as $line){
|
||||
$nameValue = explode(":", $line, 2);
|
||||
if(isset($nameValue[1])){
|
||||
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
|
||||
}
|
||||
}
|
||||
$headers[] = $headerGroup;
|
||||
}
|
||||
if($onSuccess !== null){
|
||||
$onSuccess($ch);
|
||||
}
|
||||
return [$body, $headers, $httpCode];
|
||||
}finally{
|
||||
curl_close($ch);
|
||||
}
|
||||
return Internet::simpleCurl($page, $timeout, $extraHeaders, $extraOpts, $onSuccess);
|
||||
}
|
||||
|
||||
public static function javaStringHash(string $string) : int{
|
||||
|
@ -29,7 +29,7 @@ namespace pocketmine\wizard;
|
||||
|
||||
use pocketmine\lang\BaseLang;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\Internet;
|
||||
|
||||
class SetupWizard{
|
||||
public const DEFAULT_NAME = \pocketmine\NAME . " Server";
|
||||
@ -91,7 +91,7 @@ class SetupWizard{
|
||||
}
|
||||
|
||||
private function showLicense() : bool{
|
||||
$this->message($this->lang->get("welcome_to_pocketmine"));
|
||||
$this->message($this->lang->translateString("welcome_to_pocketmine", [\pocketmine\NAME]));
|
||||
echo <<<LICENSE
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
@ -102,7 +102,7 @@ class SetupWizard{
|
||||
LICENSE;
|
||||
$this->writeLine();
|
||||
if(strtolower($this->getInput($this->lang->get("accept_license"), "n", "y/N")) !== "y"){
|
||||
$this->error($this->lang->get("you_have_to_accept_the_license"));
|
||||
$this->error($this->lang->translateString("you_have_to_accept_the_license", [\pocketmine\NAME]));
|
||||
sleep(5);
|
||||
|
||||
return false;
|
||||
@ -205,7 +205,7 @@ LICENSE;
|
||||
|
||||
$this->message($this->lang->get("ip_get"));
|
||||
|
||||
$externalIP = Utils::getIP();
|
||||
$externalIP = Internet::getIP();
|
||||
if($externalIP === false){
|
||||
$externalIP = "unknown (server offline)";
|
||||
}
|
||||
@ -219,7 +219,7 @@ LICENSE;
|
||||
private function endWizard(){
|
||||
$this->message($this->lang->get("you_have_finished"));
|
||||
$this->message($this->lang->get("pocketmine_plugins"));
|
||||
$this->message($this->lang->get("pocketmine_will_start"));
|
||||
$this->message($this->lang->translateString("pocketmine_will_start", [\pocketmine\NAME]));
|
||||
|
||||
$this->writeLine();
|
||||
$this->writeLine();
|
||||
|
@ -51,4 +51,29 @@ class ItemTest extends TestCase{
|
||||
self::assertEquals(BlockFactory::isRegistered($id), ItemFactory::isRegistered($id));
|
||||
}
|
||||
}
|
||||
|
||||
public function itemFromStringProvider() : array{
|
||||
return [
|
||||
["dye:4", ItemIds::DYE, 4],
|
||||
["351", ItemIds::DYE, 0],
|
||||
["351:4", ItemIds::DYE, 4],
|
||||
["stone:3", ItemIds::STONE, 3],
|
||||
["minecraft:string", ItemIds::STRING, 0],
|
||||
["diamond_pickaxe", ItemIds::DIAMOND_PICKAXE, 0],
|
||||
["diamond_pickaxe:5", ItemIds::DIAMOND_PICKAXE, 5]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider itemFromStringProvider
|
||||
* @param string $string
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
*/
|
||||
public function testFromStringSingle(string $string, int $id, int $meta) : void{
|
||||
$item = ItemFactory::fromString($string);
|
||||
|
||||
self::assertEquals($id, $item->getId());
|
||||
self::assertEquals($meta, $item->getDamage());
|
||||
}
|
||||
}
|
||||
|
28
tests/phpunit/level/format/io/AbstractLevelProvider.php
Normal file
28
tests/phpunit/level/format/io/AbstractLevelProvider.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\level\format\io;
|
||||
|
||||
abstract class AbstractLevelProvider implements LevelProvider{
|
||||
|
||||
}
|
28
tests/phpunit/level/format/io/InterfaceLevelProvider.php
Normal file
28
tests/phpunit/level/format/io/InterfaceLevelProvider.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\level\format\io;
|
||||
|
||||
interface InterfaceLevelProvider extends LevelProvider{
|
||||
|
||||
}
|
47
tests/phpunit/level/format/io/LevelProviderManagerTest.php
Normal file
47
tests/phpunit/level/format/io/LevelProviderManagerTest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\level\format\io;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LevelProviderManagerTest extends TestCase{
|
||||
|
||||
public function testAddNonClassProvider() : void{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
LevelProviderManager::addProvider("lol");
|
||||
}
|
||||
|
||||
public function testAddAbstractClassProvider() : void{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
LevelProviderManager::addProvider(AbstractLevelProvider::class);
|
||||
}
|
||||
|
||||
public function testAddInterfaceProvider() : void{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
LevelProviderManager::addProvider(InterfaceLevelProvider::class);
|
||||
}
|
||||
}
|
56
tests/phpunit/network/mcpe/StupidJsonDecodeTest.php
Normal file
56
tests/phpunit/network/mcpe/StupidJsonDecodeTest.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?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;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class StupidJsonDecodeTest extends TestCase{
|
||||
|
||||
public function stupidJsonDecodeProvider() : array{
|
||||
return [
|
||||
["[\n \"a\",\"b,c,d,e\\\" \",,0,1,2, false, 0.001]", ['a', 'b,c,d,e" ', '', 0, 1, 2, false, 0.001]],
|
||||
["0", 0],
|
||||
["false", false],
|
||||
["NULL", null],
|
||||
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
|
||||
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider stupidJsonDecodeProvider
|
||||
*
|
||||
* @param string $brokenJson
|
||||
* @param mixed $expect
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function testStupidJsonDecode(string $brokenJson, $expect){
|
||||
$func = new \ReflectionMethod(PlayerNetworkSessionAdapter::class, 'stupid_json_decode');
|
||||
$func->setAccessible(true);
|
||||
|
||||
$decoded = $func->invoke(null, $brokenJson, true);
|
||||
self::assertEquals($expect, $decoded);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
name: TesterPlugin
|
||||
main: pmmp\TesterPlugin\Main
|
||||
version: 0.1.0
|
||||
api: [3.0.0]
|
||||
api: [3.2.0]
|
||||
load: POSTWORLD
|
||||
author: pmmp
|
||||
description: Plugin used to run tests on PocketMine-MP
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pmmp\TesterPlugin;
|
||||
|
||||
use pocketmine\event\Listener;
|
||||
use pocketmine\event\server\ServerCommandEvent;
|
||||
use pocketmine\event\server\CommandEvent;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
|
||||
class Main extends PluginBase implements Listener{
|
||||
@ -48,7 +48,7 @@ class Main extends PluginBase implements Listener{
|
||||
];
|
||||
}
|
||||
|
||||
public function onServerCommand(ServerCommandEvent $event){
|
||||
public function onServerCommand(CommandEvent $event){
|
||||
//The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to
|
||||
//run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may
|
||||
//be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests
|
||||
|
Reference in New Issue
Block a user