mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-19 23:37:45 +00:00
Merge branch 'next-minor' into item-stack-request
This commit is contained in:
@@ -30,7 +30,7 @@ use pocketmine\scheduler\GarbageCollectionTask;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Process;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function arsort;
|
||||
use function count;
|
||||
use function fclose;
|
||||
|
@@ -34,9 +34,10 @@ namespace pocketmine {
|
||||
use pocketmine\utils\Timezone;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
use function getcwd;
|
||||
use function phpversion;
|
||||
use function preg_match;
|
||||
@@ -160,7 +161,7 @@ namespace pocketmine {
|
||||
if(PHP_DEBUG !== 0){
|
||||
$logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
|
||||
}
|
||||
if(extension_loaded("xdebug")){
|
||||
if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
|
||||
$logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
|
||||
}
|
||||
if(((int) ini_get('zend.assertions')) !== -1){
|
||||
@@ -176,10 +177,10 @@ namespace pocketmine {
|
||||
|
||||
|
||||
--------------------------------------- ! WARNING ! ---------------------------------------
|
||||
You're using PHP 8.0 with JIT enabled. This provides significant performance improvements.
|
||||
You're using PHP with JIT enabled. This provides significant performance improvements.
|
||||
HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs.
|
||||
Proceed with caution.
|
||||
If you want to report any bugs, make sure to mention that you are using PHP 8.0 with JIT.
|
||||
If you want to report any bugs, make sure to mention that you have enabled PHP JIT.
|
||||
To turn off JIT, change `opcache.jit` to `0` in your php.ini file.
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
|
@@ -31,7 +31,7 @@ use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\SimpleCommandMap;
|
||||
use pocketmine\console\ConsoleCommandSender;
|
||||
use pocketmine\console\ConsoleReaderThread;
|
||||
use pocketmine\console\ConsoleReaderChildProcessDaemon;
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
||||
use pocketmine\crash\CrashDump;
|
||||
@@ -88,12 +88,12 @@ use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\snooze\SleeperHandler;
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\stats\SendUsageTask;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\updater\UpdateChecker;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Internet;
|
||||
@@ -115,7 +115,7 @@ use pocketmine\world\World;
|
||||
use pocketmine\world\WorldCreationOptions;
|
||||
use pocketmine\world\WorldManager;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_sum;
|
||||
use function base64_encode;
|
||||
use function cli_set_process_title;
|
||||
@@ -132,6 +132,7 @@ use function get_class;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
use function is_int;
|
||||
use function is_object;
|
||||
use function is_resource;
|
||||
use function is_string;
|
||||
@@ -165,7 +166,6 @@ use function zlib_encode;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const PHP_EOL;
|
||||
use const PHP_INT_MAX;
|
||||
use const PTHREADS_INHERIT_NONE;
|
||||
use const ZLIB_ENCODING_GZIP;
|
||||
|
||||
/**
|
||||
@@ -225,7 +225,8 @@ class Server{
|
||||
|
||||
private MemoryManager $memoryManager;
|
||||
|
||||
private ConsoleReaderThread $console;
|
||||
private ?ConsoleReaderChildProcessDaemon $console = null;
|
||||
private ?ConsoleCommandSender $consoleSender = null;
|
||||
|
||||
private SimpleCommandMap $commandMap;
|
||||
|
||||
@@ -570,6 +571,7 @@ class Server{
|
||||
$playerPos = null;
|
||||
$spawn = $world->getSpawnLocation();
|
||||
}
|
||||
/** @phpstan-var PromiseResolver<Player> $playerPromiseResolver */
|
||||
$playerPromiseResolver = new PromiseResolver();
|
||||
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
|
||||
@@ -607,6 +609,10 @@ class Server{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method's results are unpredictable. The string "Steve" will return the player named "SteveJobs",
|
||||
* until another player named "SteveJ" joins the server, at which point it will return that player instead. Prefer
|
||||
* filtering the results of {@link Server::getOnlinePlayers()} yourself.
|
||||
*
|
||||
* Returns an online player whose name begins with or equals the given string (case insensitive).
|
||||
* The closest match will be returned, or null if there are no online matches.
|
||||
*
|
||||
@@ -1043,22 +1049,14 @@ class Server{
|
||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
||||
|
||||
//TODO: move console parts to a separate component
|
||||
$consoleSender = new ConsoleCommandSender($this, $this->language);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $consoleSender);
|
||||
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder);
|
||||
|
||||
$consoleNotifier = new SleeperNotifier();
|
||||
$commandBuffer = new \Threaded();
|
||||
$this->console = new ConsoleReaderThread($commandBuffer, $consoleNotifier);
|
||||
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($commandBuffer, $consoleSender) : void{
|
||||
Timings::$serverCommand->startTiming();
|
||||
while(($line = $commandBuffer->shift()) !== null){
|
||||
$this->dispatchCommand($consoleSender, (string) $line);
|
||||
}
|
||||
Timings::$serverCommand->stopTiming();
|
||||
});
|
||||
$this->console->start(PTHREADS_INHERIT_NONE);
|
||||
//TODO: move console parts to a separate component
|
||||
if($this->configGroup->getPropertyBool("console.enable-input", true)){
|
||||
$this->console = new ConsoleReaderChildProcessDaemon($this->logger);
|
||||
}
|
||||
|
||||
$this->tickProcessor();
|
||||
$this->forceShutdown();
|
||||
@@ -1511,7 +1509,7 @@ class Server{
|
||||
$this->configGroup->save();
|
||||
}
|
||||
|
||||
if(isset($this->console)){
|
||||
if($this->console !== null){
|
||||
$this->getLogger()->debug("Closing console");
|
||||
$this->console->quit();
|
||||
}
|
||||
@@ -1650,12 +1648,14 @@ class Server{
|
||||
], 10, [], $postUrlError);
|
||||
|
||||
if($reply !== null && is_object($data = json_decode($reply->getBody()))){
|
||||
if(isset($data->crashId) && isset($data->crashUrl)){
|
||||
if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){
|
||||
$reportId = $data->crashId;
|
||||
$reportUrl = $data->crashUrl;
|
||||
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
|
||||
}elseif(isset($data->error)){
|
||||
}elseif(isset($data->error) && is_string($data->error)){
|
||||
$this->logger->emergency("Automatic crash report submission failed: $data->error");
|
||||
}else{
|
||||
$this->logger->emergency("Invalid JSON response received from crash archive: " . $reply->getBody());
|
||||
}
|
||||
}else{
|
||||
$this->logger->emergency("Failed to communicate with crash archive: $postUrlError");
|
||||
@@ -1716,7 +1716,7 @@ class Server{
|
||||
$session = $player->getNetworkSession();
|
||||
$position = $player->getPosition();
|
||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
|
||||
TextFormat::AQUA . $player->getName() . TextFormat::WHITE,
|
||||
TextFormat::AQUA . $player->getName() . TextFormat::RESET,
|
||||
$session->getIp(),
|
||||
(string) $session->getPort(),
|
||||
(string) $player->getId(),
|
||||
@@ -1853,6 +1853,15 @@ class Server{
|
||||
|
||||
$this->getMemoryManager()->check();
|
||||
|
||||
if($this->console !== null){
|
||||
Timings::$serverCommand->startTiming();
|
||||
while(($line = $this->console->readLine()) !== null){
|
||||
$this->consoleSender ??= new ConsoleCommandSender($this, $this->language);
|
||||
$this->dispatchCommand($this->consoleSender, $line);
|
||||
}
|
||||
Timings::$serverCommand->stopTiming();
|
||||
}
|
||||
|
||||
Timings::$serverTick->stopTiming();
|
||||
|
||||
$now = microtime(true);
|
||||
|
@@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.7.3";
|
||||
public const BASE_VERSION = "4.9.2";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
|
||||
|
@@ -78,25 +78,47 @@ class Block{
|
||||
$this->position = clone $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing information about how to identify and store this block type, such as its legacy
|
||||
* numeric ID(s), tile type (if any), and legacy variant metadata.
|
||||
*/
|
||||
public function getIdInfo() : BlockIdentifier{
|
||||
return $this->idInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the printable English name of the block.
|
||||
*/
|
||||
public function getName() : string{
|
||||
return $this->fallbackName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Returns the legacy numeric Minecraft block ID.
|
||||
*/
|
||||
public function getId() : int{
|
||||
return $this->idInfo->getBlockId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Returns the full blockstate ID of this block. This is a compact way of representing a blockstate used to store
|
||||
* blocks in chunks at runtime.
|
||||
*
|
||||
* This ID can be used to later obtain a copy of this block using {@link BlockFactory::get()}.
|
||||
*/
|
||||
public function getFullId() : int{
|
||||
return ($this->getId() << self::INTERNAL_METADATA_BITS) | $this->getMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block as an item.
|
||||
* State information such as facing, powered/unpowered, open/closed, etc., is discarded.
|
||||
* Type information such as colour, wood type, etc. is preserved.
|
||||
*/
|
||||
public function asItem() : Item{
|
||||
return ItemFactory::getInstance()->get(
|
||||
$this->idInfo->getItemId(),
|
||||
@@ -104,6 +126,12 @@ class Block{
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Returns the legacy Minecraft block meta value. This is a mixed-purpose value, which is used to store different
|
||||
* things for different blocks.
|
||||
*/
|
||||
public function getMeta() : int{
|
||||
$stateMeta = $this->writeStateToMeta();
|
||||
assert(($stateMeta & ~$this->getStateBitmask()) === 0);
|
||||
@@ -116,6 +144,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* Returns a bitmask used to extract state bits from block metadata.
|
||||
* This is used to remove unwanted information from the legacy meta value when getting the block as an item.
|
||||
*/
|
||||
public function getStateBitmask() : int{
|
||||
return 0;
|
||||
@@ -143,6 +172,12 @@ class Block{
|
||||
$this->collisionBoxes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes information about the block into the world. This writes the blockstate ID into the chunk, and creates
|
||||
* and/or removes tiles as necessary.
|
||||
*
|
||||
* Note: Do not call this directly. Pass the block to {@link World::setBlock()} instead.
|
||||
*/
|
||||
public function writeStateToWorld() : void{
|
||||
$world = $this->position->getWorld();
|
||||
$world->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
|
||||
@@ -168,7 +203,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type ID that identifies this type of block. This does not include information like facing, colour,
|
||||
* Returns a type ID that identifies this type of block. This does not include information like facing, open/closed,
|
||||
* powered/unpowered, etc.
|
||||
*/
|
||||
public function getTypeId() : int{
|
||||
@@ -199,22 +234,36 @@ class Block{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this block can be replaced by another block placed in the same position.
|
||||
*/
|
||||
public function canBeReplaced() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this block can replace the given block in the given placement conditions.
|
||||
* This is used to allow slabs of the same type to combine into double slabs.
|
||||
*/
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||
return $blockReplace->canBeReplaced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the Block, using block space and block target, and side. Returns if the block has been placed.
|
||||
* Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block
|
||||
* itself, but may be multiple blocks in some cases (such as doors).
|
||||
*
|
||||
* @return bool whether the placement should go ahead
|
||||
*/
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$tx->addBlock($blockReplace->position, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called immediately after the block has been placed in the world. Since placement uses a block transaction, some
|
||||
* things may not be possible until after the transaction has been executed.
|
||||
*/
|
||||
public function onPostPlace() : void{
|
||||
|
||||
}
|
||||
@@ -254,7 +303,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* Called when this block is randomly updated due to chunk ticking.
|
||||
* WARNING: This will not be called if ticksRandomly() does not return true!
|
||||
* WARNING: This will not be called if {@link Block::ticksRandomly()} does not return true!
|
||||
*/
|
||||
public function onRandomTick() : void{
|
||||
|
||||
@@ -275,8 +324,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this block is attacked (left-clicked). This is called when a player left-clicks the block to try and
|
||||
* start to break it in survival mode.
|
||||
* Called when this block is attacked (left-clicked) by a player attempting to start breaking it in survival.
|
||||
*
|
||||
* @return bool if an action took place, prevents starting to break the block if true.
|
||||
*/
|
||||
@@ -284,11 +332,19 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a multiplier applied to the velocity of entities moving on top of this block. A higher value will make
|
||||
* the block more slippery (like ice).
|
||||
*
|
||||
* @return float 0.0-1.0
|
||||
*/
|
||||
public function getFrictionFactor() : float{
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of light emitted by this block.
|
||||
*
|
||||
* @return int 0-15
|
||||
*/
|
||||
public function getLightLevel() : int{
|
||||
@@ -331,10 +387,6 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasEntityCollision() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether entities can climb up this block.
|
||||
*/
|
||||
@@ -342,10 +394,6 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
||||
return null;
|
||||
}
|
||||
|
||||
final public function getPosition() : Position{
|
||||
return $this->position;
|
||||
}
|
||||
@@ -428,6 +476,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* Returns the item that players will equip when middle-clicking on this block.
|
||||
* If addUserData is true, additional data may be added, such as banner patterns, chest contents, etc.
|
||||
*/
|
||||
public function getPickedItem(bool $addUserData = false) : Item{
|
||||
$item = $this->asItem();
|
||||
@@ -551,7 +600,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for collision against an AxisAlignedBB
|
||||
* Returns whether any of the block's collision boxes intersect with the given AxisAlignedBB.
|
||||
*/
|
||||
public function collidesWithBB(AxisAlignedBB $bb) : bool{
|
||||
foreach($this->getCollisionBoxes() as $bb2){
|
||||
@@ -563,10 +612,21 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the block has actions to be executed when an entity enters its cell (full cube space).
|
||||
*
|
||||
* @see Block::onEntityInside()
|
||||
*/
|
||||
public function hasEntityCollision() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting
|
||||
* with the collision box or bounding box.
|
||||
*
|
||||
* WARNING: This will not be called if {@link Block::hasEntityCollision()} returns false.
|
||||
*
|
||||
* @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive
|
||||
* being ignited), this should return false.
|
||||
*/
|
||||
@@ -574,6 +634,19 @@ class Block{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a direction vector describing which way an entity intersecting this block should be pushed.
|
||||
* This is used by liquids to push entities in liquid currents.
|
||||
*
|
||||
* The returned vector is summed with vectors from every other block the entity is intersecting, and normalized to
|
||||
* produce a final direction vector.
|
||||
*
|
||||
* WARNING: This will not be called if {@link Block::hasEntityCollision()} does not return true!
|
||||
*/
|
||||
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an entity lands on this block (usually due to falling).
|
||||
* @return float|null The new vertical velocity of the entity, or null if unchanged.
|
||||
@@ -583,6 +656,13 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of collision bounding boxes for this block.
|
||||
* These are used for:
|
||||
* - entity movement collision checks (to ensure entities can't clip through blocks)
|
||||
* - projectile flight paths
|
||||
* - block placement (to ensure the player can't place blocks inside itself or another entity)
|
||||
* - anti-cheat checks in plugins
|
||||
*
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
final public function getCollisionBoxes() : array{
|
||||
@@ -613,6 +693,10 @@ class Block{
|
||||
return [AxisAlignedBB::one()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of support that the block can provide on the given face. This is used to determine whether
|
||||
* blocks placed on the given face can be supported by this block.
|
||||
*/
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::FULL();
|
||||
}
|
||||
@@ -623,6 +707,10 @@ class Block{
|
||||
return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a ray trace along the line between the two positions using the block's collision boxes.
|
||||
* Returns the intersection point closest to pos1, or null if no intersection occurred.
|
||||
*/
|
||||
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
|
||||
$bbs = $this->getCollisionBoxes();
|
||||
if(count($bbs) === 0){
|
||||
|
@@ -523,12 +523,6 @@ class BlockFactory{
|
||||
$this->registerAllMeta(...$leaves);
|
||||
$this->registerAllMeta(...$allSidedLogs);
|
||||
|
||||
static $sandstoneTypes = [
|
||||
Meta::SANDSTONE_NORMAL => "",
|
||||
Meta::SANDSTONE_CHISELED => "Chiseled ",
|
||||
Meta::SANDSTONE_CUT => "Cut ",
|
||||
Meta::SANDSTONE_SMOOTH => "Smooth "
|
||||
];
|
||||
$sandstoneBreakInfo = new BreakInfo(0.8, ToolType::PICKAXE, ToolTier::WOOD()->getHarvestLevel());
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::RED_SANDSTONE_STAIRS, 0), "Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_RED_SANDSTONE_STAIRS, 0), "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
@@ -536,7 +530,12 @@ class BlockFactory{
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS, 0), "Smooth Sandstone Stairs", $sandstoneBreakInfo));
|
||||
$sandstones = [];
|
||||
$redSandstones = [];
|
||||
foreach($sandstoneTypes as $variant => $prefix){
|
||||
foreach([
|
||||
Meta::SANDSTONE_NORMAL => "",
|
||||
Meta::SANDSTONE_CHISELED => "Chiseled ",
|
||||
Meta::SANDSTONE_CUT => "Cut ",
|
||||
Meta::SANDSTONE_SMOOTH => "Smooth "
|
||||
] as $variant => $prefix){
|
||||
$sandstones[] = new Opaque(new BID(Ids::SANDSTONE, $variant), $prefix . "Sandstone", $sandstoneBreakInfo);
|
||||
$redSandstones[] = new Opaque(new BID(Ids::RED_SANDSTONE, $variant), $prefix . "Red Sandstone", $sandstoneBreakInfo);
|
||||
}
|
||||
|
@@ -72,7 +72,7 @@ class Cactus extends Transparent{
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
static $shrinkSize = 1 / 16;
|
||||
$shrinkSize = 1 / 16;
|
||||
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
|
||||
}
|
||||
|
||||
|
@@ -34,7 +34,7 @@ use function mt_rand;
|
||||
class DeadBush extends Flowable{
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->getSide(Facing::DOWN)->isTransparent()){
|
||||
if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class DeadBush extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Facing::DOWN)->isTransparent()){
|
||||
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
@@ -64,4 +64,14 @@ class DeadBush extends Flowable{
|
||||
public function getFlammability() : int{
|
||||
return 100;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
$blockId = $block->getId();
|
||||
return $blockId === BlockLegacyIds::SAND
|
||||
|| $blockId === BlockLegacyIds::PODZOL
|
||||
|| $blockId === BlockLegacyIds::MYCELIUM
|
||||
|| $blockId === BlockLegacyIds::DIRT
|
||||
|| $blockId === BlockLegacyIds::HARDENED_CLAY
|
||||
|| $blockId === BlockLegacyIds::STAINED_HARDENED_CLAY;
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,10 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
|
||||
/**
|
||||
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
|
||||
* collision boxes, and can't provide support for other blocks.
|
||||
*/
|
||||
abstract class Flowable extends Transparent{
|
||||
|
||||
public function canBeFlowedInto() : bool{
|
||||
|
@@ -69,7 +69,6 @@ class Lever extends Flowable{
|
||||
5 => LeverFacing::UP_AXIS_Z(),
|
||||
6 => LeverFacing::UP_AXIS_X(),
|
||||
7 => LeverFacing::DOWN_AXIS_Z(),
|
||||
default => throw new AssumptionFailedError("0x07 mask should make this impossible"), //phpstan doesn't understand :(
|
||||
};
|
||||
|
||||
$this->activated = ($stateMeta & BlockLegacyMetadata::LEVER_FLAG_POWERED) !== 0;
|
||||
|
@@ -27,7 +27,7 @@ use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use function mt_rand;
|
||||
|
||||
class Melon extends Transparent{
|
||||
class Melon extends Opaque{
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [
|
||||
|
@@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* Opaque blocks do not allow light to pass through. They are usually collidable full-cube blocks.
|
||||
* Most blocks in Minecraft fall into this category.
|
||||
*/
|
||||
class Opaque extends Block{
|
||||
|
||||
public function isSolid() : bool{
|
||||
|
@@ -43,7 +43,11 @@ class RedMushroom extends Flowable{
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
if(!$down->isTransparent()){
|
||||
$position = $this->getPosition();
|
||||
$lightLevel = $position->getWorld()->getFullLightAt($position->x, $position->y, $position->z);
|
||||
$downId = $down->getId();
|
||||
//TODO: nylium support
|
||||
if(($lightLevel <= 12 && !$down->isTransparent()) || $downId === BlockLegacyIds::MYCELIUM || $downId === BlockLegacyIds::PODZOL){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,7 @@ use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
class Sugarcane extends Flowable{
|
||||
public const MAX_AGE = 15;
|
||||
@@ -49,14 +50,23 @@ class Sugarcane extends Flowable{
|
||||
return 0b1111;
|
||||
}
|
||||
|
||||
private function grow() : bool{
|
||||
$grew = false;
|
||||
private function seekToBottom() : Position{
|
||||
$world = $this->position->getWorld();
|
||||
$bottom = $this->position;
|
||||
while(($next = $world->getBlock($bottom->down()))->isSameType($this)){
|
||||
$bottom = $next->position;
|
||||
}
|
||||
return $bottom;
|
||||
}
|
||||
|
||||
private function grow(Position $pos) : bool{
|
||||
$grew = false;
|
||||
$world = $pos->getWorld();
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
if(!$world->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
||||
if(!$world->isInWorld($pos->x, $pos->y + $y, $pos->z)){
|
||||
break;
|
||||
}
|
||||
$b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
||||
$b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z);
|
||||
if($b->getId() === BlockLegacyIds::AIR){
|
||||
$ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE());
|
||||
$ev->call();
|
||||
@@ -65,12 +75,12 @@ class Sugarcane extends Flowable{
|
||||
}
|
||||
$world->setBlock($b->position, $ev->getNewState());
|
||||
$grew = true;
|
||||
}else{
|
||||
}elseif(!$b->isSameType($this)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->age = 0;
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->setBlock($pos, $this);
|
||||
return $grew;
|
||||
}
|
||||
|
||||
@@ -87,7 +97,7 @@ class Sugarcane extends Flowable{
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($item instanceof Fertilizer){
|
||||
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
|
||||
if($this->grow($this->seekToBottom())){
|
||||
$item->pop();
|
||||
}
|
||||
|
||||
@@ -111,7 +121,7 @@ class Sugarcane extends Flowable{
|
||||
public function onRandomTick() : void{
|
||||
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
||||
if($this->age === self::MAX_AGE){
|
||||
$this->grow();
|
||||
$this->grow($this->position);
|
||||
}else{
|
||||
++$this->age;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
|
@@ -29,6 +29,9 @@ use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Thin blocks behave like glass panes. They connect to full-cube blocks horizontally adjacent to them if possible.
|
||||
*/
|
||||
class Thin extends Transparent{
|
||||
/** @var bool[] facing => dummy */
|
||||
protected array $connections = [];
|
||||
|
@@ -23,6 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* Transparent blocks do not block any light from propagating through them.
|
||||
*
|
||||
* Note: This does **not** imply that the block is **visually** transparent. For example, chests allow light to pass
|
||||
* through, but the player cannot see through them except at the edges.
|
||||
*/
|
||||
class Transparent extends Block{
|
||||
|
||||
public function isTransparent() : bool{
|
||||
|
@@ -25,6 +25,9 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* Represents a block which is unrecognized or not implemented.
|
||||
*/
|
||||
class UnknownBlock extends Transparent{
|
||||
|
||||
public function __construct(BlockIdentifier $idInfo, BlockBreakInfo $breakInfo){
|
||||
|
@@ -585,6 +585,7 @@ final class VanillaBlocks{
|
||||
|
||||
/**
|
||||
* @return Block[]
|
||||
* @phpstan-return array<string, Block>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@@ -116,7 +116,7 @@ class Vine extends Flowable{
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$blockClicked->isSolid() || Facing::axis($face) === Axis::Y){
|
||||
if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ use pocketmine\block\utils\SignText;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\World;
|
||||
use function array_pad;
|
||||
use function array_slice;
|
||||
@@ -42,6 +43,13 @@ use function sprintf;
|
||||
class Sign extends Spawnable{
|
||||
public const TAG_TEXT_BLOB = "Text";
|
||||
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able
|
||||
public const TAG_TEXT_COLOR = "SignTextColor";
|
||||
public const TAG_GLOWING_TEXT = "IgnoreLighting";
|
||||
/**
|
||||
* This tag is set to indicate that MCPE-117835 has been addressed in whatever version this sign was created.
|
||||
* @see https://bugs.mojang.com/browse/MCPE-117835
|
||||
*/
|
||||
public const TAG_LEGACY_BUG_RESOLVE = "TextIgnoreLegacyBugResolved";
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
@@ -111,5 +119,11 @@ class Sign extends Spawnable{
|
||||
|
||||
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
|
||||
$nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
|
||||
|
||||
//the following are not yet used by the server, but needed to roll back any changes to glowing state or colour
|
||||
//if the client uses dye on the sign
|
||||
$nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00));
|
||||
$nbt->setByte(self::TAG_GLOWING_TEXT, 0);
|
||||
$nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1);
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ class SignText{
|
||||
private array $lines;
|
||||
|
||||
/**
|
||||
* @param string[]|null $lines index-sensitive; omitting an index will leave it unchanged
|
||||
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
|
||||
*
|
||||
* @throws \InvalidArgumentException if the array size is greater than 4
|
||||
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
|
||||
|
@@ -27,13 +27,13 @@ declare(strict_types=1);
|
||||
namespace pocketmine\command;
|
||||
|
||||
use pocketmine\command\utils\CommandException;
|
||||
use pocketmine\console\ConsoleCommandSender;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function explode;
|
||||
use function str_replace;
|
||||
@@ -227,12 +227,12 @@ abstract class Command{
|
||||
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
||||
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
|
||||
if($sendToSource && !($source instanceof ConsoleCommandSender)){
|
||||
if($sendToSource){
|
||||
$source->sendMessage($message);
|
||||
}
|
||||
|
||||
foreach($users as $user){
|
||||
if($user instanceof ConsoleCommandSender){
|
||||
if($user instanceof BroadcastLoggerForwarder){
|
||||
$user->sendMessage($result);
|
||||
}elseif($user !== $source){
|
||||
$user->sendMessage($colored);
|
||||
|
@@ -272,10 +272,11 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
//These registered commands have absolute priority
|
||||
$lowerAlias = strtolower($alias);
|
||||
if(count($targets) > 0){
|
||||
$this->knownCommands[strtolower($alias)] = new FormattedCommandAlias(strtolower($alias), $targets);
|
||||
$this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets);
|
||||
}else{
|
||||
unset($this->knownCommands[strtolower($alias)]);
|
||||
unset($this->knownCommands[$lowerAlias]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function date;
|
||||
|
||||
class DumpMemoryCommand extends VanillaCommand{
|
||||
|
@@ -63,7 +63,7 @@ class GarbageCollectorCommand extends VanillaCommand{
|
||||
|
||||
$cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector();
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::WHITE, TextFormat::GREEN . " ----" . TextFormat::WHITE));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::RESET, TextFormat::GREEN . " ----" . TextFormat::RESET));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_chunks(TextFormat::RED . number_format($chunksCollected))->prefix(TextFormat::GOLD));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD));
|
||||
|
||||
|
@@ -75,7 +75,11 @@ class GiveCommand extends VanillaCommand{
|
||||
if(!isset($args[2])){
|
||||
$item->setCount($item->getMaxStackSize());
|
||||
}else{
|
||||
$item->setCount((int) $args[2]);
|
||||
$count = $this->getBoundedInt($sender, $args[2], 1, 32767);
|
||||
if($count === null){
|
||||
return true;
|
||||
}
|
||||
$item->setCount($count);
|
||||
}
|
||||
|
||||
if(isset($args[3])){
|
||||
|
@@ -95,7 +95,7 @@ class HelpCommand extends VanillaCommand{
|
||||
foreach($commands[$pageNumber - 1] as $command){
|
||||
$description = $command->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::WHITE . $descriptionString);
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::RESET . $descriptionString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,18 +107,18 @@ class HelpCommand extends VanillaCommand{
|
||||
$description = $cmd->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getName())
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::WHITE, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::WHITE . $descriptionString)
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$usage = $cmd->getUsage();
|
||||
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $usageString)))
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$aliases = $cmd->getAliases();
|
||||
sort($aliases, SORT_NATURAL);
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::WHITE . implode(", ", $aliases))
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
return true;
|
||||
|
@@ -52,7 +52,7 @@ class MeCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::WHITE . implode(" ", $args)));
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ class PluginsCommand extends VanillaCommand{
|
||||
}, $sender->getServer()->getPluginManager()->getPlugins());
|
||||
sort($list, SORT_STRING);
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::WHITE . ", ", $list)));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::RESET . ", ", $list)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ class StatusCommand extends VanillaCommand{
|
||||
$mUsage = Process::getAdvancedMemoryUsage();
|
||||
|
||||
$server = $sender->getServer();
|
||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
|
||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::RESET . "Server status" . TextFormat::GREEN . " ----");
|
||||
|
||||
$time = (int) (microtime(true) - $server->getStartTime());
|
||||
|
||||
|
@@ -35,7 +35,7 @@ use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\InternetException;
|
||||
use pocketmine\utils\InternetRequestResult;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function count;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
|
@@ -32,9 +32,7 @@ use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function implode;
|
||||
use function opcache_get_status;
|
||||
use function sprintf;
|
||||
use function stripos;
|
||||
use function strtolower;
|
||||
@@ -59,36 +57,31 @@ class VersionCommand extends VanillaCommand{
|
||||
|
||||
if(count($args) === 0){
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareName(
|
||||
VersionInfo::NAME
|
||||
TextFormat::GREEN . VersionInfo::NAME . TextFormat::RESET
|
||||
));
|
||||
$versionColor = VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : TextFormat::GREEN;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareVersion(
|
||||
VersionInfo::VERSION()->getFullVersion(),
|
||||
VersionInfo::GIT_HASH()
|
||||
$versionColor . VersionInfo::VERSION()->getFullVersion() . TextFormat::RESET,
|
||||
TextFormat::GREEN . VersionInfo::GIT_HASH() . TextFormat::RESET
|
||||
));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_minecraftVersion(
|
||||
ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
(string) ProtocolInfo::CURRENT_PROTOCOL
|
||||
TextFormat::GREEN . ProtocolInfo::MINECRAFT_VERSION_NETWORK . TextFormat::RESET,
|
||||
TextFormat::GREEN . ProtocolInfo::CURRENT_PROTOCOL . TextFormat::RESET
|
||||
));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(PHP_VERSION));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(TextFormat::GREEN . PHP_VERSION . TextFormat::RESET));
|
||||
|
||||
if(
|
||||
function_exists('opcache_get_status') &&
|
||||
($opcacheStatus = opcache_get_status(false)) !== false &&
|
||||
isset($opcacheStatus["jit"]["on"])
|
||||
){
|
||||
$jit = $opcacheStatus["jit"];
|
||||
if($jit["on"] === true){
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(
|
||||
sprintf("CRTO: %s%s%s%s", $jit["opt_flags"] >> 2, $jit["opt_flags"] & 0x03, $jit["kind"], $jit["opt_level"])
|
||||
);
|
||||
$jitMode = Utils::getOpcacheJitMode();
|
||||
if($jitMode !== null){
|
||||
if($jitMode !== 0){
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(sprintf("CRTO: %d", $jitMode));
|
||||
}else{
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitDisabled();
|
||||
}
|
||||
}else{
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitNotSupported();
|
||||
}
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(Utils::getOS()));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus->format(TextFormat::GREEN, TextFormat::RESET)));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(TextFormat::GREEN . Utils::getOS() . TextFormat::RESET));
|
||||
}else{
|
||||
$pluginName = implode(" ", $args);
|
||||
$exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName);
|
||||
@@ -118,7 +111,7 @@ class VersionCommand extends VanillaCommand{
|
||||
|
||||
private function describeToSender(Plugin $plugin, CommandSender $sender) : void{
|
||||
$desc = $plugin->getDescription();
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::WHITE . " version " . TextFormat::DARK_GREEN . $desc->getVersion());
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::RESET . " version " . TextFormat::DARK_GREEN . $desc->getVersion());
|
||||
|
||||
if($desc->getDescription() !== ""){
|
||||
$sender->sendMessage($desc->getDescription());
|
||||
|
@@ -30,6 +30,8 @@ use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\permission\PermissibleBase;
|
||||
use pocketmine\permission\PermissibleDelegateTrait;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function explode;
|
||||
use function trim;
|
||||
use const PHP_INT_MAX;
|
||||
@@ -59,13 +61,12 @@ class ConsoleCommandSender implements CommandSender{
|
||||
}
|
||||
|
||||
public function sendMessage(Translatable|string $message) : void{
|
||||
$server = $this->getServer();
|
||||
if($message instanceof Translatable){
|
||||
$message = $this->getLanguage()->translate($message);
|
||||
}
|
||||
|
||||
foreach(explode("\n", trim($message)) as $line){
|
||||
$server->getLogger()->info($line);
|
||||
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,12 +23,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\utils\Process;
|
||||
use function cli_set_process_title;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function feof;
|
||||
use function fwrite;
|
||||
use function stream_socket_client;
|
||||
use const PTHREADS_INHERIT_NONE;
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
@@ -43,9 +45,40 @@ $socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
|
||||
if($socket === false){
|
||||
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
|
||||
}
|
||||
$consoleReader = new ConsoleReader();
|
||||
|
||||
$channel = new \Threaded();
|
||||
$thread = new class($channel) extends \Thread{
|
||||
public function __construct(
|
||||
private \Threaded $channel,
|
||||
){}
|
||||
|
||||
public function run(){
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
$channel = $this->channel;
|
||||
$reader = new ConsoleReader();
|
||||
while(true){ // @phpstan-ignore-line
|
||||
$line = $reader->readLine();
|
||||
if($line !== null){
|
||||
$channel->synchronized(function() use ($channel, $line) : void{
|
||||
$channel[] = $line;
|
||||
$channel->notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$thread->start(PTHREADS_INHERIT_NONE);
|
||||
while(!feof($socket)){
|
||||
$line = $consoleReader->readLine();
|
||||
$line = $channel->synchronized(function() use ($channel) : ?string{
|
||||
if(count($channel) === 0){
|
||||
$channel->wait(1_000_000);
|
||||
}
|
||||
/** @var string|null $line */
|
||||
$line = $channel->shift();
|
||||
return $line;
|
||||
});
|
||||
if(@fwrite($socket, ($line ?? "") . "\n") === false){
|
||||
//Always send even if there's no line, to check if the parent is alive
|
||||
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
|
||||
@@ -53,3 +86,8 @@ while(!feof($socket)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//For simplicity's sake, we don't bother with a graceful shutdown here.
|
||||
//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the
|
||||
//parent process was terminated forcibly and didn't clean up after itself.
|
||||
Process::kill(Process::pid(), false);
|
||||
|
@@ -23,11 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\thread\Thread;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function base64_encode;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
@@ -45,36 +43,35 @@ use function trim;
|
||||
use const PHP_BINARY;
|
||||
use const STREAM_SHUT_RDWR;
|
||||
|
||||
final class ConsoleReaderThread extends Thread{
|
||||
/**
|
||||
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
|
||||
* properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets()
|
||||
* waiting for a line that might never come (and Windows doesn't support character-based reading either), and
|
||||
* pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless.
|
||||
*
|
||||
* This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill
|
||||
* the entire server process to make it go away.
|
||||
*
|
||||
* To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can
|
||||
* then brutally murder when the server shuts down, without killing the entire server process.
|
||||
* Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process
|
||||
* communication.
|
||||
*/
|
||||
final class ConsoleReaderChildProcessDaemon{
|
||||
private \PrefixedLogger $logger;
|
||||
/** @var resource */
|
||||
private $subprocess;
|
||||
/** @var resource */
|
||||
private $socket;
|
||||
|
||||
public function __construct(
|
||||
private \Threaded $buffer,
|
||||
private ?SleeperNotifier $notifier = null
|
||||
){}
|
||||
|
||||
protected function onRun() : void{
|
||||
$buffer = $this->buffer;
|
||||
$notifier = $this->notifier;
|
||||
|
||||
while(!$this->isKilled){
|
||||
$this->runSubprocess($buffer, $notifier);
|
||||
}
|
||||
\Logger $logger
|
||||
){
|
||||
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
|
||||
$this->prepareSubprocess();
|
||||
}
|
||||
|
||||
/**
|
||||
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
|
||||
* properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets()
|
||||
* waiting for a line that might never come (and Windows doesn't support character-based reading either), and
|
||||
* pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless.
|
||||
*
|
||||
* This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill
|
||||
* the entire server process to make it go away.
|
||||
*
|
||||
* To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can
|
||||
* then brutally murder when the server shuts down, without killing the entire server process.
|
||||
* Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process
|
||||
* communication.
|
||||
*/
|
||||
private function runSubprocess(\Threaded $buffer, ?SleeperNotifier $notifier) : void{
|
||||
private function prepareSubprocess() : void{
|
||||
$server = stream_socket_server("tcp://127.0.0.1:0");
|
||||
if($server === false){
|
||||
throw new \RuntimeException("Failed to open console reader socket server");
|
||||
@@ -96,41 +93,43 @@ final class ConsoleReaderThread extends Thread{
|
||||
throw new AssumptionFailedError("stream_socket_accept() returned false");
|
||||
}
|
||||
stream_socket_shutdown($server, STREAM_SHUT_RDWR);
|
||||
while(!$this->isKilled){
|
||||
$r = [$client];
|
||||
$w = null;
|
||||
$e = null;
|
||||
if(stream_select($r, $w, $e, 0, 200000) === 1){
|
||||
$command = fgets($client);
|
||||
if($command === false){
|
||||
//subprocess died for some reason; this could be someone killed it manually from outside (e.g.
|
||||
//mistyped PID) or it might be a ctrl+c signal to this process that the child is handling
|
||||
//differently (different signal handlers).
|
||||
//since we have no way to know the difference, we just kill the sub and start a new one.
|
||||
break;
|
||||
}
|
||||
|
||||
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
if($command === ""){
|
||||
continue;
|
||||
}
|
||||
$buffer[] = $command;
|
||||
if($notifier !== null){
|
||||
$notifier->wakeupSleeper();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->subprocess = $sub;
|
||||
$this->socket = $client;
|
||||
}
|
||||
|
||||
private function shutdownSubprocess() : void{
|
||||
//we have no way to signal to the subprocess to shut down gracefully; besides, Windows sucks, and the subprocess
|
||||
//gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in
|
||||
//the first place).
|
||||
proc_terminate($sub);
|
||||
proc_close($sub);
|
||||
stream_socket_shutdown($client, STREAM_SHUT_RDWR);
|
||||
proc_terminate($this->subprocess);
|
||||
proc_close($this->subprocess);
|
||||
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
|
||||
}
|
||||
|
||||
public function getThreadName() : string{
|
||||
return "Console";
|
||||
public function readLine() : ?string{
|
||||
$r = [$this->socket];
|
||||
$w = null;
|
||||
$e = null;
|
||||
if(stream_select($r, $w, $e, 0, 0) === 1){
|
||||
$command = fgets($this->socket);
|
||||
if($command === false){
|
||||
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
|
||||
$this->shutdownSubprocess();
|
||||
$this->prepareSubprocess();
|
||||
return null;
|
||||
}
|
||||
|
||||
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
|
||||
return $command !== "" ? $command : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function quit() : void{
|
||||
$this->shutdownSubprocess();
|
||||
}
|
||||
}
|
@@ -33,7 +33,7 @@ use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function base64_encode;
|
||||
use function error_get_last;
|
||||
use function file;
|
||||
@@ -164,6 +164,8 @@ class CrashDump{
|
||||
}
|
||||
$this->data->extensions = $extensions;
|
||||
|
||||
$this->data->jit_mode = Utils::getOpcacheJitMode();
|
||||
|
||||
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
|
||||
ob_start();
|
||||
phpinfo();
|
||||
|
@@ -66,6 +66,8 @@ final class CrashDumpData implements \JsonSerializable{
|
||||
*/
|
||||
public array $extensions = [];
|
||||
|
||||
public ?int $jit_mode = null;
|
||||
|
||||
public string $phpinfo = "";
|
||||
|
||||
public CrashDumpDataGeneral $general;
|
||||
|
@@ -74,7 +74,7 @@ final class DyeColorIdMap{
|
||||
}
|
||||
|
||||
public function fromId(int $id) : ?DyeColor{
|
||||
return $this->idToEnum[$id];
|
||||
return $this->idToEnum[$id] ?? null;
|
||||
}
|
||||
|
||||
public function fromInvertedId(int $id) : ?DyeColor{
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyBiomeIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@@ -249,10 +249,8 @@ abstract class Entity{
|
||||
$this->getWorld()->addEntity($this);
|
||||
|
||||
$this->lastUpdate = $this->server->getTick();
|
||||
(new EntitySpawnEvent($this))->call();
|
||||
|
||||
$this->scheduleUpdate();
|
||||
|
||||
}
|
||||
|
||||
abstract protected function getInitialSizeInfo() : EntitySizeInfo;
|
||||
@@ -937,6 +935,14 @@ abstract class Entity{
|
||||
return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from onUpdate() on the first tick of a new entity. This is called before any movement processing or
|
||||
* main ticking logic. Use this to fire any events related to spawning the entity.
|
||||
*/
|
||||
protected function onFirstUpdate(int $currentTick) : void{
|
||||
(new EntitySpawnEvent($this))->call();
|
||||
}
|
||||
|
||||
public function onUpdate(int $currentTick) : bool{
|
||||
if($this->closed){
|
||||
return false;
|
||||
@@ -953,6 +959,10 @@ abstract class Entity{
|
||||
|
||||
$this->lastUpdate = $currentTick;
|
||||
|
||||
if($this->justCreated){
|
||||
$this->onFirstUpdate($currentTick);
|
||||
}
|
||||
|
||||
if(!$this->isAlive()){
|
||||
if($this->onDeathUpdate($tickDiff)){
|
||||
$this->flagForDespawn();
|
||||
|
@@ -175,20 +175,6 @@ final class EntityFactory{
|
||||
}, ['Human']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(World, CompoundTag) : Entity $creationFunc
|
||||
*/
|
||||
private static function validateCreationFunc(\Closure $creationFunc) : void{
|
||||
$sig = new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
);
|
||||
if(!$sig->isSatisfiedBy($creationFunc)){
|
||||
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($creationFunc) . "` must be compatible with `" . $sig . "`");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity type into the index.
|
||||
*
|
||||
@@ -207,7 +193,11 @@ final class EntityFactory{
|
||||
throw new \InvalidArgumentException("At least one save name must be provided");
|
||||
}
|
||||
Utils::testValidInstance($className, Entity::class);
|
||||
self::validateCreationFunc($creationFunc);
|
||||
Utils::validateCallableSignature(new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
), $creationFunc);
|
||||
|
||||
foreach($saveNames as $name){
|
||||
$this->creationFuncs[$name] = $creationFunc;
|
||||
@@ -227,7 +217,7 @@ final class EntityFactory{
|
||||
*/
|
||||
public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
|
||||
try{
|
||||
$saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier");
|
||||
$saveId = $nbt->getTag("identifier") ?? $nbt->getTag("id");
|
||||
$func = null;
|
||||
if($saveId instanceof StringTag){
|
||||
$func = $this->creationFuncs[$saveId->getValue()] ?? null;
|
||||
@@ -248,7 +238,7 @@ final class EntityFactory{
|
||||
|
||||
public function injectSaveId(string $class, CompoundTag $saveData) : void{
|
||||
if(isset($this->saveNames[$class])){
|
||||
$saveData->setTag("id", new StringTag($this->saveNames[$class]));
|
||||
$saveData->setTag("identifier", new StringTag($this->saveNames[$class]));
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Entity $class is not registered");
|
||||
}
|
||||
|
@@ -237,11 +237,10 @@ class ExperienceManager{
|
||||
}
|
||||
|
||||
public function onPickupXp(int $xpValue) : void{
|
||||
static $mainHandIndex = -1;
|
||||
static $offHandIndex = -2;
|
||||
$mainHandIndex = -1;
|
||||
$offHandIndex = -2;
|
||||
|
||||
//TODO: replace this with a more generic equipment getting/setting interface
|
||||
/** @var Durable[] $equipment */
|
||||
$equipment = [];
|
||||
|
||||
if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
|
||||
|
@@ -56,6 +56,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\sound\BurpSound;
|
||||
use pocketmine\world\sound\EntityLandSound;
|
||||
use pocketmine\world\sound\EntityLongFallSound;
|
||||
use pocketmine\world\sound\EntityShortFallSound;
|
||||
@@ -255,8 +256,7 @@ abstract class Living extends Entity{
|
||||
$size = $this->getInitialSizeInfo();
|
||||
if($this->isSwimming() || $this->isGliding()){
|
||||
$width = $size->getWidth();
|
||||
//we don't actually know an appropriate eye height for a swimming mob, but 2/3 should be good enough.
|
||||
$this->setSize((new EntitySizeInfo($width, $width, $width * 2 / 3))->scale($this->getScale()));
|
||||
$this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale()));
|
||||
}else{
|
||||
$this->setSize($size->scale($this->getScale()));
|
||||
}
|
||||
@@ -293,6 +293,10 @@ abstract class Living extends Entity{
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function always returns true, no matter whether the target is in the line of sight or not.
|
||||
* @see VoxelRayTrace::inDirection() for a more generalized method of ray-tracing to a target.
|
||||
*/
|
||||
public function hasLineOfSight(Entity $entity) : bool{
|
||||
//TODO: head height
|
||||
return true;
|
||||
@@ -320,6 +324,9 @@ abstract class Living extends Entity{
|
||||
foreach($consumable->getAdditionalEffects() as $effect){
|
||||
$this->effectManager->add($effect);
|
||||
}
|
||||
if($consumable instanceof FoodSource){
|
||||
$this->broadcastSound(new BurpSound());
|
||||
}
|
||||
|
||||
$consumable->onConsume($this);
|
||||
}
|
||||
|
@@ -38,7 +38,6 @@ class Effect{
|
||||
* @param Translatable|string $name Translation key used for effect name
|
||||
* @param Color $color Color of bubbles given by this effect
|
||||
* @param bool $bad Whether the effect is harmful
|
||||
* @param int $defaultDuration
|
||||
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
|
||||
*/
|
||||
public function __construct(
|
||||
|
@@ -33,5 +33,8 @@ class HealthBoostEffect extends Effect{
|
||||
|
||||
public function remove(Living $entity, EffectInstance $instance) : void{
|
||||
$entity->setMaxHealth($entity->getMaxHealth() - 4 * $instance->getEffectLevel());
|
||||
if($entity->getHealth() > $entity->getMaxHealth()){
|
||||
$entity->setHealth($entity->getMaxHealth());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ class InstantDamageEffect extends InstantEffect{
|
||||
|
||||
public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null) : void{
|
||||
//TODO: add particles (witch spell)
|
||||
$damage = (4 << $instance->getAmplifier()) * $potency;
|
||||
$damage = (6 << $instance->getAmplifier()) * $potency;
|
||||
if($source !== null){
|
||||
$sourceOwner = $source->getOwningEntity();
|
||||
if($sourceOwner !== null){
|
||||
|
@@ -30,7 +30,7 @@ use pocketmine\event\entity\EntityRegainHealthEvent;
|
||||
class RegenerationEffect extends Effect{
|
||||
|
||||
public function canTick(EffectInstance $instance) : bool{
|
||||
if(($interval = (40 >> $instance->getAmplifier())) > 0){
|
||||
if(($interval = (50 >> $instance->getAmplifier())) > 0){
|
||||
return ($instance->getDuration() % $interval) === 0;
|
||||
}
|
||||
return true;
|
||||
|
@@ -101,6 +101,7 @@ final class VanillaEffects{
|
||||
|
||||
/**
|
||||
* @return Effect[]
|
||||
* @phpstan-return array<string, Effect>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@@ -117,7 +117,6 @@ class FallingBlock extends Entity{
|
||||
|
||||
$block = $world->getBlock($pos);
|
||||
if(!$block->canBeReplaced() || !$world->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ()) || ($this->onGround && abs($this->location->y - $this->location->getFloorY()) > 0.001)){
|
||||
//FIXME: anvils are supposed to destroy torches
|
||||
$world->dropItem($this->location, $this->block->asItem());
|
||||
}else{
|
||||
$ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block);
|
||||
|
@@ -92,8 +92,11 @@ class ItemEntity extends Entity{
|
||||
$this->pickupDelay = $nbt->getShort("PickupDelay", $this->pickupDelay);
|
||||
$this->owner = $nbt->getString("Owner", $this->owner);
|
||||
$this->thrower = $nbt->getString("Thrower", $this->thrower);
|
||||
}
|
||||
|
||||
(new ItemSpawnEvent($this))->call();
|
||||
protected function onFirstUpdate(int $currentTick) : void{
|
||||
(new ItemSpawnEvent($this))->call(); //this must be called before EntitySpawnEvent, to maintain backwards compatibility
|
||||
parent::onFirstUpdate($currentTick);
|
||||
}
|
||||
|
||||
protected function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
@@ -186,6 +189,10 @@ class ItemEntity extends Entity{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canSaveWithChunk() : bool{
|
||||
return !$this->item->isNull() && parent::canSaveWithChunk();
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setTag("Item", $this->item->nbtSerialize());
|
||||
|
@@ -98,8 +98,8 @@ class SplashPotion extends Throwable{
|
||||
|
||||
if($hasEffects){
|
||||
if(!$this->willLinger()){
|
||||
foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living && $entity->isAlive()){
|
||||
foreach($this->getWorld()->getCollidingEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living){
|
||||
$distanceSquared = $entity->getEyePos()->distanceSquared($this->location);
|
||||
if($distanceSquared > 16){ //4 blocks
|
||||
continue;
|
||||
|
@@ -72,7 +72,7 @@ class BlockBreakEvent extends BlockEvent implements Cancellable{
|
||||
* Returns the item used to destroy the block.
|
||||
*/
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
return clone $this->item;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -65,7 +65,7 @@ class BlockPlaceEvent extends BlockEvent implements Cancellable{
|
||||
* Gets the item in hand
|
||||
*/
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
return clone $this->item;
|
||||
}
|
||||
|
||||
public function getBlockReplaced() : Block{
|
||||
|
@@ -69,7 +69,7 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
|
||||
}
|
||||
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
return clone $this->item;
|
||||
}
|
||||
|
||||
public function getBlock() : Block{
|
||||
|
@@ -60,6 +60,6 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
|
||||
* Returns the item in the slot that the player is trying to equip.
|
||||
*/
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
return clone $this->item;
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ class PlayerItemUseEvent extends PlayerEvent implements Cancellable{
|
||||
* Returns the item used.
|
||||
*/
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
return clone $this->item;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -76,11 +76,13 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
abstract protected function internalSetContents(array $items) : void;
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void{
|
||||
if(count($items) > $this->getSize()){
|
||||
@@ -132,6 +134,7 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
public function first(Item $item, bool $exact = false) : int{
|
||||
$count = $exact ? $item->getCount() : max(1, $item->getCount());
|
||||
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
||||
|
@@ -26,7 +26,7 @@ namespace pocketmine\inventory;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function json_decode;
|
||||
|
||||
|
@@ -24,8 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* An inventory which is backed by another inventory, and acts as a proxy to that inventory.
|
||||
@@ -37,16 +35,25 @@ class DelegateInventory extends BaseInventory{
|
||||
private Inventory $backingInventory
|
||||
){
|
||||
parent::__construct();
|
||||
$weakThis = \WeakReference::create($this);
|
||||
$this->backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener(
|
||||
function(Inventory $unused, int $slot, Item $oldItem) : void{
|
||||
$this->onSlotChange($slot, $oldItem);
|
||||
static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onSlotChange($slot, $oldItem);
|
||||
}
|
||||
},
|
||||
function(Inventory $unused, array $oldContents) : void{
|
||||
$this->onContentChange($oldContents);
|
||||
static function(Inventory $unused, array $oldContents) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onContentChange($oldContents);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
$this->backingInventory->getListeners()->remove($this->inventoryListener);
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
return $this->backingInventory->getSize();
|
||||
}
|
||||
@@ -66,12 +73,4 @@ class DelegateInventory extends BaseInventory{
|
||||
protected function internalSetContents(array $items) : void{
|
||||
$this->backingInventory->setContents($items);
|
||||
}
|
||||
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
if(count($this->getViewers()) === 0 && count($this->getListeners()->toArray()) === 1){
|
||||
$this->backingInventory->getListeners()->remove($this->inventoryListener);
|
||||
$this->inventoryListener = CallbackInventoryListener::onAnyChange(static function() : void{}); //break cyclic reference
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -47,12 +47,20 @@ interface Inventory{
|
||||
public function setItem(int $index, Item $item) : void;
|
||||
|
||||
/**
|
||||
* Returns an array of all the itemstacks in the inventory, indexed by their slot number.
|
||||
* Empty slots are not included unless includeEmpty is true.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getContents(bool $includeEmpty = false) : array;
|
||||
|
||||
/**
|
||||
* Sets the contents of the inventory. Non-numeric offsets or offsets larger than the size of the inventory are
|
||||
* ignored.
|
||||
*
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void;
|
||||
|
||||
@@ -85,8 +93,10 @@ interface Inventory{
|
||||
/**
|
||||
* Will return all the Items that has the same id and metadata (if not null).
|
||||
* Won't check amount
|
||||
* The returned array is indexed by slot number.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function all(Item $item) : array;
|
||||
|
||||
|
@@ -58,6 +58,7 @@ class SimpleInventory extends BaseInventory{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getContents(bool $includeEmpty = false) : array{
|
||||
$contents = [];
|
||||
|
@@ -42,6 +42,9 @@ class DropItemAction extends InventoryAction{
|
||||
if($this->targetItem->isNull()){
|
||||
throw new TransactionValidationException("Cannot drop an empty itemstack");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds item type max stack size");
|
||||
}
|
||||
}
|
||||
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
|
@@ -70,6 +70,12 @@ class SlotChangeAction extends InventoryAction{
|
||||
if(!$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){
|
||||
throw new TransactionValidationException("Slot does not contain expected original item");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds item type max stack size");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds inventory max stack size");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,7 +26,7 @@ namespace pocketmine\item;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
@@ -108,8 +108,9 @@ final class LegacyStringToItemParser{
|
||||
throw new LegacyStringToItemParserException("Unable to parse \"" . $b[1] . "\" from \"" . $input . "\" as a valid meta value");
|
||||
}
|
||||
|
||||
if(isset($this->map[strtolower($b[0])])){
|
||||
$item = $this->itemFactory->get($this->map[strtolower($b[0])], $meta);
|
||||
$id = strtolower($b[0]);
|
||||
if(isset($this->map[$id])){
|
||||
$item = $this->itemFactory->get($this->map[$id], $meta);
|
||||
}else{
|
||||
throw new LegacyStringToItemParserException("Unable to resolve \"" . $input . "\" to a valid item");
|
||||
}
|
||||
|
@@ -384,6 +384,7 @@ final class VanillaItems{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return array<string, Item>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@@ -24,9 +24,10 @@ declare(strict_types=1);
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function is_dir;
|
||||
@@ -67,10 +68,14 @@ class Language{
|
||||
$result = [];
|
||||
|
||||
foreach($files as $file){
|
||||
$code = explode(".", $file)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings["language.name"])){
|
||||
$result[$code] = $strings["language.name"];
|
||||
try{
|
||||
$code = explode(".", $file)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings["language.name"])){
|
||||
$result[$code] = $strings["language.name"];
|
||||
}
|
||||
}catch(LanguageNotFoundException $e){
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +129,10 @@ class Language{
|
||||
protected static function loadLang(string $path, string $languageCode) : array{
|
||||
$file = Path::join($path, $languageCode . ".ini");
|
||||
if(file_exists($file)){
|
||||
return array_map('\stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files"));
|
||||
$strings = array_map('stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files"));
|
||||
if(count($strings) > 0){
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
|
||||
throw new LanguageNotFoundException("Language \"$languageCode\" not found");
|
||||
|
@@ -345,8 +345,10 @@ class InventoryManager{
|
||||
|
||||
public function onClientRemoveWindow(int $id) : void{
|
||||
if($id === $this->lastInventoryNetworkId){
|
||||
$this->remove($id);
|
||||
$this->player->removeCurrentWindow();
|
||||
if(isset($this->windowMap[$id]) && $id !== $this->pendingCloseWindowId){
|
||||
$this->remove($id);
|
||||
$this->player->removeCurrentWindow();
|
||||
}
|
||||
}else{
|
||||
$this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId");
|
||||
}
|
||||
|
@@ -56,6 +56,7 @@ use pocketmine\network\mcpe\handler\LoginPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\PacketHandler;
|
||||
use pocketmine\network\mcpe\handler\PreSpawnPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\SessionStartPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler;
|
||||
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
|
||||
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
@@ -87,6 +88,7 @@ use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTitlePacket;
|
||||
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\TextPacket;
|
||||
use pocketmine\network\mcpe\protocol\ToastRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\TransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
||||
@@ -164,6 +166,7 @@ class NetworkSession{
|
||||
*/
|
||||
private \SplQueue $compressedQueue;
|
||||
private bool $forceAsyncCompression = true;
|
||||
private bool $enableCompression = false; //disabled until handshake completed
|
||||
|
||||
private PacketSerializerContext $packetSerializerContext;
|
||||
|
||||
@@ -196,17 +199,10 @@ class NetworkSession{
|
||||
|
||||
$this->connectTime = time();
|
||||
|
||||
$this->setHandler(new LoginPacketHandler(
|
||||
$this->setHandler(new SessionStartPacketHandler(
|
||||
$this->server,
|
||||
$this,
|
||||
function(PlayerInfo $info) : void{
|
||||
$this->info = $info;
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
}
|
||||
fn() => $this->onSessionStartSuccess()
|
||||
));
|
||||
|
||||
$this->manager->add($this);
|
||||
@@ -221,6 +217,24 @@ class NetworkSession{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
private function onSessionStartSuccess() : void{
|
||||
$this->logger->debug("Session start handshake completed, awaiting login packet");
|
||||
$this->flushSendBuffer(true);
|
||||
$this->enableCompression = true;
|
||||
$this->setHandler(new LoginPacketHandler(
|
||||
$this->server,
|
||||
$this,
|
||||
function(PlayerInfo $info) : void{
|
||||
$this->info = $info;
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
protected function createPlayer() : void{
|
||||
$this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion(
|
||||
\Closure::fromCallable([$this, 'onPlayerCreated']),
|
||||
@@ -335,18 +349,22 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
Timings::$playerNetworkReceiveDecompress->startTiming();
|
||||
try{
|
||||
$stream = new PacketBatch($this->compressor->decompress($payload));
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
if($this->enableCompression){
|
||||
Timings::$playerNetworkReceiveDecompress->startTiming();
|
||||
try{
|
||||
$decompressed = $this->compressor->decompress($payload);
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
}
|
||||
}else{
|
||||
$decompressed = $payload;
|
||||
}
|
||||
|
||||
try{
|
||||
foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
||||
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
||||
if($packet === null){
|
||||
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
|
||||
throw new PacketHandlingException("Unknown packet received");
|
||||
@@ -451,7 +469,14 @@ class NetworkSession{
|
||||
}elseif($this->forceAsyncCompression){
|
||||
$syncMode = false;
|
||||
}
|
||||
$promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode);
|
||||
|
||||
$batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer);
|
||||
if($this->enableCompression){
|
||||
$promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode);
|
||||
}else{
|
||||
$promise = new CompressBatchPromise();
|
||||
$promise->resolve($batch->getBuffer());
|
||||
}
|
||||
$this->sendBuffer = [];
|
||||
$this->queueCompressedNoBufferFlush($promise, $immediate);
|
||||
}
|
||||
@@ -1076,6 +1101,10 @@ class NetworkSession{
|
||||
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
|
||||
}
|
||||
|
||||
public function onToastNotification(string $title, string $body) : void{
|
||||
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
if($this->info === null){
|
||||
if(time() >= $this->connectTime + 10){
|
||||
|
2
src/network/mcpe/cache/ChunkCache.php
vendored
2
src/network/mcpe/cache/ChunkCache.php
vendored
@@ -43,8 +43,6 @@ class ChunkCache implements ChunkListener{
|
||||
|
||||
/**
|
||||
* Fetches the ChunkCache instance for the given world. This lazily creates cache systems as needed.
|
||||
*
|
||||
* @return ChunkCache
|
||||
*/
|
||||
public static function getInstance(World $world, Compressor $compressor) : self{
|
||||
$worldId = spl_object_id($world);
|
||||
|
2
src/network/mcpe/cache/StaticPacketCache.php
vendored
2
src/network/mcpe/cache/StaticPacketCache.php
vendored
@@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
class StaticPacketCache{
|
||||
|
@@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
|
@@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_key_exists;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
|
@@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
/**
|
||||
|
@@ -43,7 +43,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
@@ -110,7 +112,7 @@ class TypeConverter{
|
||||
|
||||
public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{
|
||||
if($itemStack->isNull()){
|
||||
return new RecipeIngredient(0, 0, 0);
|
||||
return new RecipeIngredient(null, 0);
|
||||
}
|
||||
if($itemStack->hasAnyDamageValue()){
|
||||
[$id, ] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), 0);
|
||||
@@ -118,15 +120,25 @@ class TypeConverter{
|
||||
}else{
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta());
|
||||
}
|
||||
return new RecipeIngredient($id, $meta, $itemStack->getCount());
|
||||
return new RecipeIngredient(new IntIdMetaItemDescriptor($id, $meta), $itemStack->getCount());
|
||||
}
|
||||
|
||||
public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{
|
||||
if($ingredient->getId() === 0){
|
||||
$descriptor = $ingredient->getDescriptor();
|
||||
if($descriptor === null){
|
||||
return VanillaItems::AIR();
|
||||
}
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($ingredient->getId(), $ingredient->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
if($descriptor instanceof IntIdMetaItemDescriptor){
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($descriptor->getId(), $descriptor->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
}
|
||||
if($descriptor instanceof StringIdMetaItemDescriptor){
|
||||
$intId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($descriptor->getId());
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($intId, $descriptor->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
}
|
||||
|
||||
throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack");
|
||||
}
|
||||
|
||||
public function coreItemStackToNet(Item $itemStack) : ItemStack{
|
||||
|
@@ -51,7 +51,6 @@ use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
@@ -687,10 +686,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return true; //this is a broken useless packet, so we don't use it
|
||||
}
|
||||
|
||||
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
|
||||
return true; //no longer used, but the client still sends it for flight changes
|
||||
}
|
||||
|
||||
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
|
||||
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
|
||||
if($pos->distanceSquared($this->player->getLocation()) > 10000){
|
||||
|
@@ -30,6 +30,7 @@ use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||
use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
@@ -62,6 +63,7 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
$location = $this->player->getLocation();
|
||||
$world = $location->getWorld();
|
||||
|
||||
$this->session->getLogger()->debug("Preparing StartGamePacket");
|
||||
$levelSettings = new LevelSettings();
|
||||
$levelSettings->seed = -1;
|
||||
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
@@ -105,22 +107,41 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
|
||||
$this->session->getLogger()->debug("Sending biome definitions");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
|
||||
$this->session->getLogger()->debug("Sending attributes");
|
||||
$this->session->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
|
||||
|
||||
$this->session->getLogger()->debug("Sending available commands");
|
||||
$this->session->syncAvailableCommands();
|
||||
|
||||
$this->session->getLogger()->debug("Sending abilities");
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
|
||||
$this->session->getLogger()->debug("Sending effects");
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->onEntityEffectAdded($this->player, $effect, false);
|
||||
}
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor metadata");
|
||||
$this->player->sendData([$this->player]);
|
||||
|
||||
$this->session->getLogger()->debug("Sending inventory");
|
||||
$this->inventoryManager->syncAll();
|
||||
$this->inventoryManager->syncCreative();
|
||||
$this->inventoryManager->syncSelectedHotbarSlot();
|
||||
|
||||
$this->session->getLogger()->debug("Sending creative inventory data");
|
||||
$this->inventoryManager->syncCreative();
|
||||
|
||||
$this->session->getLogger()->debug("Sending crafting data");
|
||||
$this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending player list");
|
||||
$this->session->syncPlayerList($this->server->getOnlinePlayers());
|
||||
}
|
||||
|
||||
@@ -129,4 +150,10 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
|
||||
//the client will send this every tick once we start sending chunks, but we don't handle it in this stage
|
||||
//this is very spammy so we filter it out
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -65,9 +65,19 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
){}
|
||||
|
||||
public function setUp() : void{
|
||||
$resourcePackEntries = array_map(static function(ResourcePack $pack) : ResourcePackInfoEntry{
|
||||
$resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{
|
||||
//TODO: more stuff
|
||||
return new ResourcePackInfoEntry($pack->getPackId(), $pack->getPackVersion(), $pack->getPackSize(), "", "", "", false);
|
||||
$encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId());
|
||||
|
||||
return new ResourcePackInfoEntry(
|
||||
$pack->getPackId(),
|
||||
$pack->getPackVersion(),
|
||||
$pack->getPackSize(),
|
||||
$encryptionKey ?? "",
|
||||
"",
|
||||
$pack->getPackId(),
|
||||
false
|
||||
);
|
||||
}, $this->resourcePackManager->getResourceStack());
|
||||
//TODO: support forcing server packs
|
||||
$this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false));
|
||||
|
76
src/network/mcpe/handler/SessionStartPacketHandler.php
Normal file
76
src/network/mcpe/handler/SessionStartPacketHandler.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\handler;
|
||||
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\NetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\RequestNetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
use pocketmine\Server;
|
||||
|
||||
final class SessionStartPacketHandler extends PacketHandler{
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure() : void $onSuccess
|
||||
*/
|
||||
public function __construct(
|
||||
private Server $server,
|
||||
private NetworkSession $session,
|
||||
private \Closure $onSuccess
|
||||
){}
|
||||
|
||||
public function handleRequestNetworkSettings(RequestNetworkSettingsPacket $packet) : bool{
|
||||
$protocolVersion = $packet->getProtocolVersion();
|
||||
if(!$this->isCompatibleProtocol($protocolVersion)){
|
||||
$this->session->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
|
||||
|
||||
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
|
||||
$this->session->disconnect(
|
||||
$this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion)),
|
||||
false
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future
|
||||
$this->session->sendDataPacket(NetworkSettingsPacket::create(
|
||||
NetworkSettingsPacket::COMPRESS_EVERYTHING,
|
||||
CompressionAlgorithm::ZLIB,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
));
|
||||
($this->onSuccess)();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isCompatibleProtocol(int $protocolVersion) : bool{
|
||||
return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL;
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
* 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 = 10;
|
||||
private const MCPE_RAKNET_PROTOCOL_VERSION = 11;
|
||||
|
||||
private const MCPE_RAKNET_PACKET_ID = "\xfe";
|
||||
|
||||
|
@@ -47,7 +47,6 @@ final class RakLibThreadCrashInfo{
|
||||
return new self(null, $info["message"], $info["file"], $info["line"]);
|
||||
}
|
||||
|
||||
/** @return string|null */
|
||||
public function getClass() : ?string{ return $this->class; }
|
||||
|
||||
public function getMessage() : string{ return $this->message; }
|
||||
|
@@ -418,6 +418,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return $this->lastPlayed - $this->firstPlayed > 1; // microtime(true) - microtime(true) may have less than one millisecond difference
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player is allowed to toggle flight mode.
|
||||
*
|
||||
* If set to false, the player will be locked in its current flight mode (flying/not flying), and attempts by the
|
||||
* player to enter or exit flight mode will be prevented.
|
||||
*
|
||||
* Note: Setting this to false DOES NOT change whether the player is currently flying. Use
|
||||
* {@link Player::setFlying()} for that purpose.
|
||||
*/
|
||||
public function setAllowFlight(bool $value) : void{
|
||||
if($this->allowFlight !== $value){
|
||||
$this->allowFlight = $value;
|
||||
@@ -425,10 +434,24 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the player is allowed to toggle its flight state.
|
||||
*
|
||||
* If false, the player is locked in its current flight mode (flying/not flying), and attempts by the player to
|
||||
* enter or exit flight mode will be prevented.
|
||||
*/
|
||||
public function getAllowFlight() : bool{
|
||||
return $this->allowFlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player's movement may be obstructed by blocks with collision boxes.
|
||||
* If set to false, the player can move through any block unobstructed.
|
||||
*
|
||||
* Note: Enabling flight mode in conjunction with this is recommended. A non-flying player will simply fall through
|
||||
* the ground into the void.
|
||||
* @see Player::setFlying()
|
||||
*/
|
||||
public function setHasBlockCollision(bool $value) : void{
|
||||
if($this->blockCollision !== $value){
|
||||
$this->blockCollision = $value;
|
||||
@@ -436,6 +459,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether blocks may obstruct the player's movement.
|
||||
* If false, the player can move through any block unobstructed.
|
||||
*/
|
||||
public function hasBlockCollision() : bool{
|
||||
return $this->blockCollision;
|
||||
}
|
||||
@@ -1021,7 +1048,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
protected function internalSetGameMode(GameMode $gameMode) : void{
|
||||
$this->gamemode = $gameMode;
|
||||
|
||||
$this->allowFlight = $this->isCreative();
|
||||
$this->allowFlight = $this->gamemode->equals(GameMode::CREATIVE());
|
||||
$this->hungerManager->setEnabled($this->isSurvival());
|
||||
|
||||
if($this->isSpectator()){
|
||||
@@ -1316,6 +1343,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$this->lastUpdate = $currentTick;
|
||||
|
||||
if($this->justCreated){
|
||||
$this->onFirstUpdate($currentTick);
|
||||
}
|
||||
|
||||
if(!$this->isAlive() && $this->spawned){
|
||||
$this->onDeathUpdate($tickDiff);
|
||||
return true;
|
||||
@@ -1986,6 +2017,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->getNetworkSession()->onTip($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a toast message to the player, or queue to send it if a toast message is already shown.
|
||||
*/
|
||||
public function sendToastNotification(string $title, string $body) : void{
|
||||
$this->getNetworkSession()->onToastNotification($title, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Form to the player, or queue to send it if a form is already open.
|
||||
*
|
||||
|
@@ -33,7 +33,7 @@ use pocketmine\Server;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function fclose;
|
||||
|
@@ -40,7 +40,7 @@ use pocketmine\Server;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_diff_key;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
@@ -75,6 +75,9 @@ class PluginManager{
|
||||
/** @var Plugin[] */
|
||||
protected $enabledPlugins = [];
|
||||
|
||||
/** @var array<string, array<string, true>> */
|
||||
private array $pluginDependents = [];
|
||||
|
||||
private bool $loadPluginsGuard = false;
|
||||
|
||||
/**
|
||||
@@ -453,6 +456,15 @@ class PluginManager{
|
||||
if($plugin->isEnabled()){ //the plugin may have disabled itself during onEnable()
|
||||
$this->enabledPlugins[$plugin->getDescription()->getName()] = $plugin;
|
||||
|
||||
foreach($plugin->getDescription()->getDepend() as $dependency){
|
||||
$this->pluginDependents[$dependency][$plugin->getDescription()->getName()] = true;
|
||||
}
|
||||
foreach($plugin->getDescription()->getSoftDepend() as $dependency){
|
||||
if(isset($this->plugins[$dependency])){
|
||||
$this->pluginDependents[$dependency][$plugin->getDescription()->getName()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
(new PluginEnableEvent($plugin))->call();
|
||||
|
||||
return true;
|
||||
@@ -472,8 +484,19 @@ class PluginManager{
|
||||
}
|
||||
|
||||
public function disablePlugins() : void{
|
||||
foreach($this->getPlugins() as $plugin){
|
||||
$this->disablePlugin($plugin);
|
||||
while(count($this->enabledPlugins) > 0){
|
||||
foreach($this->enabledPlugins as $plugin){
|
||||
if(!$plugin->isEnabled()){
|
||||
continue; //in case a plugin disabled another plugin
|
||||
}
|
||||
$name = $plugin->getDescription()->getName();
|
||||
if(isset($this->pluginDependents[$name]) && count($this->pluginDependents[$name]) > 0){
|
||||
$this->server->getLogger()->debug("Deferring disable of plugin $name due to dependent plugins still enabled: " . implode(", ", array_keys($this->pluginDependents[$name])));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->disablePlugin($plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +506,12 @@ class PluginManager{
|
||||
(new PluginDisableEvent($plugin))->call();
|
||||
|
||||
unset($this->enabledPlugins[$plugin->getDescription()->getName()]);
|
||||
foreach(Utils::stringifyKeys($this->pluginDependents) as $dependency => $dependentList){
|
||||
unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]);
|
||||
if(count($this->pluginDependents[$dependency]) === 0){
|
||||
unset($this->pluginDependents[$dependency]);
|
||||
}
|
||||
}
|
||||
|
||||
$plugin->onEnableStateChange(false);
|
||||
$plugin->getScheduler()->shutdown();
|
||||
|
@@ -23,12 +23,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\resourcepacks;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\utils\Config;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_keys;
|
||||
use function copy;
|
||||
use function count;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function gettype;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
@@ -49,6 +51,12 @@ class ResourcePackManager{
|
||||
/** @var ResourcePack[] */
|
||||
private array $uuidList = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<string, string>
|
||||
*/
|
||||
private array $encryptionKeys = [];
|
||||
|
||||
/**
|
||||
* @param string $path Path to resource-packs directory.
|
||||
*/
|
||||
@@ -105,7 +113,19 @@ class ResourcePackManager{
|
||||
|
||||
if($newPack instanceof ResourcePack){
|
||||
$this->resourcePacks[] = $newPack;
|
||||
$this->uuidList[strtolower($newPack->getPackId())] = $newPack;
|
||||
$index = strtolower($newPack->getPackId());
|
||||
$this->uuidList[$index] = $newPack;
|
||||
|
||||
$keyPath = Path::join($this->path, $pack . ".key");
|
||||
if(file_exists($keyPath)){
|
||||
try{
|
||||
$this->encryptionKeys[$index] = ErrorToExceptionHandler::trapAndRemoveFalse(
|
||||
fn() => file_get_contents($keyPath)
|
||||
);
|
||||
}catch(\ErrorException $e){
|
||||
throw new ResourcePackException("Could not read encryption key file: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
throw new ResourcePackException("Format not recognized");
|
||||
}
|
||||
@@ -153,4 +173,11 @@ class ResourcePackManager{
|
||||
public function getPackIdList() : array{
|
||||
return array_keys($this->uuidList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key with which the pack was encrypted, or null if the pack has no key.
|
||||
*/
|
||||
public function getPackEncryptionKey(string $id) : ?string{
|
||||
return $this->encryptionKeys[strtolower($id)] ?? null;
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\MemoryManager;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
/**
|
||||
* Task used to dump memory from AsyncWorkers
|
||||
|
@@ -69,6 +69,9 @@ class TaskHandler{
|
||||
return $this->nextRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setNextRun(int $ticks) : void{
|
||||
$this->nextRun = $ticks;
|
||||
}
|
||||
@@ -103,11 +106,17 @@ class TaskHandler{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function remove() : void{
|
||||
$this->cancelled = true;
|
||||
$this->task->setHandler(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function run() : void{
|
||||
$this->timings->startTiming();
|
||||
try{
|
||||
|
@@ -56,7 +56,9 @@ abstract class Worker extends \Worker{
|
||||
$this->isKilled = true;
|
||||
|
||||
if(!$this->isShutdown()){
|
||||
while($this->unstack() !== null);
|
||||
$this->synchronized(function() : void{
|
||||
while($this->unstack() !== null);
|
||||
});
|
||||
$this->notify();
|
||||
$this->shutdown();
|
||||
}
|
||||
|
79
src/utils/BroadcastLoggerForwarder.php
Normal file
79
src/utils/BroadcastLoggerForwarder.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\Language;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\permission\PermissibleBase;
|
||||
use pocketmine\permission\PermissibleDelegateTrait;
|
||||
use pocketmine\Server;
|
||||
|
||||
/**
|
||||
* Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and
|
||||
* command audit log messages to the server log file.
|
||||
*
|
||||
* Unfortunately, broadcast subscribers are currently required to implement CommandSender, so this class has to include
|
||||
* a lot of useless methods.
|
||||
*/
|
||||
final class BroadcastLoggerForwarder implements CommandSender{
|
||||
use PermissibleDelegateTrait;
|
||||
|
||||
public function __construct(
|
||||
private Server $server, //annoying useless dependency
|
||||
private \Logger $logger,
|
||||
private Language $language
|
||||
){
|
||||
//this doesn't need any permissions
|
||||
$this->perm = new PermissibleBase([]);
|
||||
}
|
||||
|
||||
public function getLanguage() : Language{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function sendMessage(Translatable|string $message) : void{
|
||||
if($message instanceof Translatable){
|
||||
$this->logger->info($this->language->translate($message));
|
||||
}else{
|
||||
$this->logger->info($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function getServer() : Server{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Broadcast Logger Forwarder";
|
||||
}
|
||||
|
||||
public function getScreenLineHeight() : int{
|
||||
return PHP_INT_MAX;
|
||||
}
|
||||
|
||||
public function setScreenLineHeight(?int $height) : void{
|
||||
//NOOP
|
||||
}
|
||||
}
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_change_key_case;
|
||||
use function array_fill_keys;
|
||||
use function array_keys;
|
||||
|
@@ -48,6 +48,7 @@ trait EnumTrait{
|
||||
* This is overridden to change the return typehint.
|
||||
*
|
||||
* @return self[]
|
||||
* @phpstan-return array<string, self>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function copy;
|
||||
use function dirname;
|
||||
use function fclose;
|
||||
|
@@ -191,7 +191,7 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
|
||||
$threadName = (new \ReflectionClass($thread))->getShortName() . " thread";
|
||||
}
|
||||
|
||||
$message = sprintf($this->format, $time->format("H:i:s.v"), $color, $threadName, $prefix, TextFormat::clean($message, false));
|
||||
$message = sprintf($this->format, $time->format("H:i:s.v"), $color, $threadName, $prefix, TextFormat::addBase($color, TextFormat::clean($message, false)));
|
||||
|
||||
if(!Terminal::isInit()){
|
||||
Terminal::init($this->useFormattingCodes); //lazy-init colour codes because we don't know if they've been registered on this thread
|
||||
|
@@ -29,7 +29,10 @@ use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
|
||||
trait RegistryTrait{
|
||||
/** @var object[] */
|
||||
/**
|
||||
* @var object[]
|
||||
* @phpstan-var array<string, object>
|
||||
*/
|
||||
private static $members = null;
|
||||
|
||||
private static function verifyName(string $name) : void{
|
||||
@@ -107,6 +110,7 @@ trait RegistryTrait{
|
||||
|
||||
/**
|
||||
* @return object[]
|
||||
* @phpstan-return array<string, object>
|
||||
*/
|
||||
private static function _registryGetAll() : array{
|
||||
self::checkInit();
|
||||
|
@@ -158,6 +158,31 @@ abstract class TextFormat{
|
||||
return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-gk-or])/u', TextFormat::ESCAPE . '$1', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds base formatting to the string. The given format codes will be inserted directly after any RESET (§r) codes.
|
||||
*
|
||||
* This is useful for log messages, where a RESET code should return to the log message's original colour (e.g.
|
||||
* blue for NOTICE), rather than whatever the terminal's base text colour is (usually some off-white colour).
|
||||
*
|
||||
* Example behaviour:
|
||||
* - Base format "§c" (red) + "Hello" (no format) = "§r§cHello"
|
||||
* - Base format "§c" + "Hello §rWorld" = "§r§cHello §r§cWorld"
|
||||
*
|
||||
* Note: Adding base formatting to the output string a second time will result in a combination of formats from both
|
||||
* calls. This is not by design, but simply a consequence of the way the function is implemented.
|
||||
*/
|
||||
public static function addBase(string $baseFormat, string $string) : string{
|
||||
$baseFormatParts = self::tokenize($baseFormat);
|
||||
foreach($baseFormatParts as $part){
|
||||
if(!isset(self::FORMATS[$part]) && !isset(self::COLORS[$part])){
|
||||
throw new \InvalidArgumentException("Unexpected base format token \"$part\", expected only color and format tokens");
|
||||
}
|
||||
}
|
||||
$baseFormat = self::RESET . $baseFormat;
|
||||
|
||||
return $baseFormat . str_replace(TextFormat::RESET, $baseFormat, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML-formatted string with colors/markup
|
||||
*/
|
||||
|
@@ -69,6 +69,7 @@ use function mb_check_encoding;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_contents;
|
||||
use function ob_start;
|
||||
use function opcache_get_status;
|
||||
use function ord;
|
||||
use function php_uname;
|
||||
use function phpversion;
|
||||
@@ -108,6 +109,7 @@ final class Utils{
|
||||
|
||||
private static ?string $os = null;
|
||||
private static ?UuidInterface $serverUniqueId = null;
|
||||
private static ?int $cpuCores = null;
|
||||
|
||||
/**
|
||||
* Returns a readable identifier for the given Closure, including file and line.
|
||||
@@ -295,14 +297,11 @@ final class Utils{
|
||||
}
|
||||
|
||||
public static function getCoreCount(bool $recalculate = false) : int{
|
||||
static $processors = 0;
|
||||
|
||||
if($processors > 0 && !$recalculate){
|
||||
return $processors;
|
||||
}else{
|
||||
$processors = 0;
|
||||
if(self::$cpuCores !== null && !$recalculate){
|
||||
return self::$cpuCores;
|
||||
}
|
||||
|
||||
$processors = 0;
|
||||
switch(Utils::getOS()){
|
||||
case Utils::OS_LINUX:
|
||||
case Utils::OS_ANDROID:
|
||||
@@ -326,7 +325,7 @@ final class Utils{
|
||||
$processors = (int) getenv("NUMBER_OF_PROCESSORS");
|
||||
break;
|
||||
}
|
||||
return $processors;
|
||||
return self::$cpuCores = $processors;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,12 +364,6 @@ final class Utils{
|
||||
$ord -= 0x100;
|
||||
}
|
||||
$hash = 31 * $hash + $ord;
|
||||
while($hash > 0x7FFFFFFF){
|
||||
$hash -= 0x100000000;
|
||||
}
|
||||
while($hash < -0x80000000){
|
||||
$hash += 0x100000000;
|
||||
}
|
||||
$hash &= 0xFFFFFFFF;
|
||||
}
|
||||
return $hash;
|
||||
@@ -486,8 +479,8 @@ final class Utils{
|
||||
*/
|
||||
public static function currentTrace(int $skipFrames = 0) : array{
|
||||
++$skipFrames; //omit this frame from trace, in addition to other skipped frames
|
||||
if(function_exists("xdebug_get_function_stack")){
|
||||
$trace = array_reverse(xdebug_get_function_stack());
|
||||
if(function_exists("xdebug_get_function_stack") && count($trace = @xdebug_get_function_stack()) !== 0){
|
||||
$trace = array_reverse($trace);
|
||||
}else{
|
||||
$e = new \Exception();
|
||||
$trace = $e->getTrace();
|
||||
@@ -634,4 +627,30 @@ final class Utils{
|
||||
public static function checkLocationNotInfOrNaN(Location $location) : void{
|
||||
self::checkVector3NotInfOrNaN($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an integer describing the current OPcache JIT setting.
|
||||
* @see https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
|
||||
*/
|
||||
public static function getOpcacheJitMode() : ?int{
|
||||
if(
|
||||
function_exists('opcache_get_status') &&
|
||||
($opcacheStatus = opcache_get_status(false)) !== false &&
|
||||
isset($opcacheStatus["jit"]["on"])
|
||||
){
|
||||
$jit = $opcacheStatus["jit"];
|
||||
if($jit["on"] === true){
|
||||
return (($jit["opt_flags"] >> 2) * 1000) +
|
||||
(($jit["opt_flags"] & 0x03) * 100) +
|
||||
($jit["kind"] * 10) +
|
||||
$jit["opt_level"];
|
||||
}
|
||||
|
||||
//jit available, but disabled
|
||||
return 0;
|
||||
}
|
||||
|
||||
//jit not available
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\InternetException;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function fgets;
|
||||
use function sleep;
|
||||
use function strtolower;
|
||||
|
@@ -195,7 +195,7 @@ class Explosion{
|
||||
}
|
||||
|
||||
$entity->attack($ev);
|
||||
$entity->setMotion($motion->multiply($impact));
|
||||
$entity->setMotion($entity->getMotion()->addVector($motion->multiply($impact)));
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user