From 90409b50d130fc24edc895a1a5ce6b868089d0a7 Mon Sep 17 00:00:00 2001 From: Jason Wynn Date: Fri, 1 Mar 2024 09:53:59 -0500 Subject: [PATCH] Allow offering different resource packs to different players (#6249) closes #6248 --- .../player/PlayerResourcePackOfferEvent.php | 106 ++++++++++++++++++ src/network/mcpe/NetworkSession.php | 15 ++- .../handler/ResourcePacksPacketHandler.php | 49 +++++--- tests/phpstan/configs/actual-problems.neon | 5 + 4 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 src/event/player/PlayerResourcePackOfferEvent.php diff --git a/src/event/player/PlayerResourcePackOfferEvent.php b/src/event/player/PlayerResourcePackOfferEvent.php new file mode 100644 index 000000000..df00cf474 --- /dev/null +++ b/src/event/player/PlayerResourcePackOfferEvent.php @@ -0,0 +1,106 @@ + key, leave unset for any packs that are not encrypted + * + * @phpstan-param list $resourcePacks + * @phpstan-param array $encryptionKeys + */ + public function __construct( + private readonly PlayerInfo $playerInfo, + private array $resourcePacks, + private array $encryptionKeys, + private bool $mustAccept + ){} + + public function getPlayerInfo() : PlayerInfo{ + return $this->playerInfo; + } + + /** + * Adds a resource pack to the top of the stack. + * The resources in this pack will be applied over the top of any existing packs. + */ + public function addResourcePack(ResourcePack $entry, ?string $encryptionKey = null) : void{ + array_unshift($this->resourcePacks, $entry); + if($encryptionKey !== null){ + $this->encryptionKeys[$entry->getPackId()] = $encryptionKey; + } + } + + /** + * Sets the resource packs to offer. Packs are applied from the highest key to the lowest, with each pack + * overwriting any resources from the previous pack. This means that the pack at index 0 gets the final say on which + * resources are used. + * + * @param ResourcePack[] $resourcePacks + * @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted + * + * @phpstan-param list $resourcePacks + * @phpstan-param array $encryptionKeys + */ + public function setResourcePacks(array $resourcePacks, array $encryptionKeys) : void{ + $this->resourcePacks = $resourcePacks; + $this->encryptionKeys = $encryptionKeys; + } + + /** + * @return ResourcePack[] + * @phpstan-return list + */ + public function getResourcePacks() : array{ + return $this->resourcePacks; + } + + /** + * @return string[] + * @phpstan-return array + */ + public function getEncryptionKeys() : array{ + return $this->encryptionKeys; + } + + public function setMustAccept(bool $mustAccept) : void{ + $this->mustAccept = $mustAccept; + } + + public function mustAccept() : bool{ + return $this->mustAccept; + } +} diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 63fab278e..5a369ba33 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe; use pocketmine\entity\effect\EffectInstance; use pocketmine\event\player\PlayerDuplicateLoginEvent; +use pocketmine\event\player\PlayerResourcePackOfferEvent; use pocketmine\event\server\DataPacketDecodeEvent; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\event\server\DataPacketSendEvent; @@ -844,7 +845,19 @@ class NetworkSession{ $this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::LOGIN_SUCCESS)); $this->logger->debug("Initiating resource packs phase"); - $this->setHandler(new ResourcePacksPacketHandler($this, $this->server->getResourcePackManager(), function() : void{ + + $packManager = $this->server->getResourcePackManager(); + $resourcePacks = $packManager->getResourceStack(); + $keys = []; + foreach($resourcePacks as $resourcePack){ + $key = $packManager->getPackEncryptionKey($resourcePack->getPackId()); + if($key !== null){ + $keys[$resourcePack->getPackId()] = $key; + } + } + $event = new PlayerResourcePackOfferEvent($this->info, $resourcePacks, $keys, $packManager->resourcePacksRequired()); + $event->call(); + $this->setHandler(new ResourcePacksPacketHandler($this, $event->getResourcePacks(), $event->getEncryptionKeys(), $event->mustAccept(), function() : void{ $this->createPlayer(); })); } diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index 3d413ee5a..c5a459613 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -37,12 +37,13 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; use pocketmine\resourcepacks\ResourcePack; -use pocketmine\resourcepacks\ResourcePackManager; +use function array_keys; use function array_map; use function ceil; use function count; use function implode; use function strpos; +use function strtolower; use function substr; /** @@ -52,35 +53,55 @@ use function substr; class ResourcePacksPacketHandler extends PacketHandler{ private const PACK_CHUNK_SIZE = 128 * 1024; //128KB + /** + * @var ResourcePack[] + * @phpstan-var array + */ + private array $resourcePacksById = []; + /** @var bool[][] uuid => [chunk index => hasSent] */ private array $downloadedChunks = []; /** - * @phpstan-param \Closure() : void $completionCallback + * @param ResourcePack[] $resourcePackStack + * @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted + * + * @phpstan-param list $resourcePackStack + * @phpstan-param array $encryptionKeys + * @phpstan-param \Closure() : void $completionCallback */ public function __construct( private NetworkSession $session, - private ResourcePackManager $resourcePackManager, + private array $resourcePackStack, + private array $encryptionKeys, + private bool $mustAccept, private \Closure $completionCallback - ){} + ){ + foreach($resourcePackStack as $pack){ + $this->resourcePacksById[$pack->getPackId()] = $pack; + } + } + + private function getPackById(string $id) : ?ResourcePack{ + return $this->resourcePacksById[strtolower($id)] ?? null; + } public function setUp() : void{ $resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{ //TODO: more stuff - $encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId()); return new ResourcePackInfoEntry( $pack->getPackId(), $pack->getPackVersion(), $pack->getPackSize(), - $encryptionKey ?? "", + $this->encryptionKeys[$pack->getPackId()] ?? "", "", $pack->getPackId(), false ); - }, $this->resourcePackManager->getResourceStack()); - //TODO: support forcing server packs - $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false, [])); + }, $this->resourcePackStack); + // TODO: support forcing server packs + $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->mustAccept, false, false, [])); $this->session->getLogger()->debug("Waiting for client to accept resource packs"); } @@ -104,11 +125,11 @@ class ResourcePacksPacketHandler extends PacketHandler{ if($splitPos !== false){ $uuid = substr($uuid, 0, $splitPos); } - $pack = $this->resourcePackManager->getPackById($uuid); + $pack = $this->getPackById($uuid); if(!($pack instanceof ResourcePack)){ //Client requested a resource pack but we don't have it available on the server - $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList())); + $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", array_keys($this->resourcePacksById))); return false; } @@ -128,7 +149,7 @@ class ResourcePacksPacketHandler extends PacketHandler{ case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS: $stack = array_map(static function(ResourcePack $pack) : ResourcePackStackEntry{ return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks - }, $this->resourcePackManager->getResourceStack()); + }, $this->resourcePackStack); //we support chemistry blocks by default, the client should already have this installed $stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", ""); @@ -151,9 +172,9 @@ class ResourcePacksPacketHandler extends PacketHandler{ } public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{ - $pack = $this->resourcePackManager->getPackById($packet->packId); + $pack = $this->getPackById($packet->packId); if(!($pack instanceof ResourcePack)){ - $this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList())); + $this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", array_keys($this->resourcePacksById))); return false; } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 9fea3803d..cc647da80 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -650,6 +650,11 @@ parameters: count: 2 path: ../../../src/network/mcpe/NetworkSession.php + - + message: "#^Parameter \\#1 \\$playerInfo of class pocketmine\\\\event\\\\player\\\\PlayerResourcePackOfferEvent constructor expects pocketmine\\\\player\\\\PlayerInfo, pocketmine\\\\player\\\\PlayerInfo\\|null given\\.$#" + count: 1 + path: ../../../src/network/mcpe/NetworkSession.php + - message: "#^Parameter \\#1 \\$target of method pocketmine\\\\command\\\\Command\\:\\:testPermissionSilent\\(\\) expects pocketmine\\\\command\\\\CommandSender, pocketmine\\\\player\\\\Player\\|null given\\.$#" count: 1