diff --git a/resources/resource_packs.yml b/resources/resource_packs.yml index 39677852d..f236117d5 100644 --- a/resources/resource_packs.yml +++ b/resources/resource_packs.yml @@ -10,3 +10,4 @@ resource_stack: # - natural.zip # - vanilla.zip #If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here. + #To specify a resource encryption key, put the key in the .key file alongside the resource pack. Example: vanilla.zip.key diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index 2b7dfd0b6..69951dce4 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -32,6 +32,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; @@ -44,14 +45,23 @@ class Sugarcane extends Flowable{ $w->boundedInt(4, 0, self::MAX_AGE, $this->age); } - 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->getTypeId() === BlockTypeIds::AIR){ $ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE()); $ev->call(); @@ -60,12 +70,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; } @@ -82,7 +92,7 @@ class Sugarcane extends Flowable{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($item instanceof Fertilizer){ - if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){ + if($this->grow($this->seekToBottom())){ $item->pop(); } @@ -113,7 +123,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); diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 89a9d6465..89824aa32 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -274,10 +274,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]); } } diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 5799b339a..b4e2ca8de 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -205,10 +205,8 @@ abstract class Entity{ $this->getWorld()->addEntity($this); $this->lastUpdate = $this->server->getTick(); - (new EntitySpawnEvent($this))->call(); $this->scheduleUpdate(); - } abstract protected function getInitialSizeInfo() : EntitySizeInfo; @@ -908,6 +906,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; @@ -924,6 +930,10 @@ abstract class Entity{ $this->lastUpdate = $currentTick; + if($this->justCreated){ + $this->onFirstUpdate($currentTick); + } + if(!$this->isAlive()){ if($this->onDeathUpdate($tickDiff)){ $this->flagForDespawn(); diff --git a/src/entity/Living.php b/src/entity/Living.php index 30a0b68d4..7485af2bb 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -278,6 +278,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; diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 95825bfcd..f20ec8445 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -85,8 +85,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{ diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index a7c603ceb..d1ba85724 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -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)); diff --git a/src/player/Player.php b/src/player/Player.php index d51329986..126951811 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1345,6 +1345,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; diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 4aaa9afc7..e6b37543a 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -23,12 +23,14 @@ declare(strict_types=1); namespace pocketmine\resourcepacks; +use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\utils\Config; use Webmozart\PathUtil\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 + */ + 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; + } }