Allow offering different resource packs to different players (#6249)

closes #6248
This commit is contained in:
Jason Wynn 2024-03-01 09:53:59 -05:00 committed by GitHub
parent 98042f844f
commit 90409b50d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 160 additions and 15 deletions

View File

@ -0,0 +1,106 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Event;
use pocketmine\player\PlayerInfo;
use pocketmine\resourcepacks\ResourcePack;
use function array_unshift;
/**
* Called after a player authenticates and is being offered resource packs to download.
*
* This event should be used to decide which resource packs to offer the player and whether to require the player to
* download the packs before they can join the server.
*/
class PlayerResourcePackOfferEvent extends Event{
/**
* @param ResourcePack[] $resourcePacks
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
*
* @phpstan-param list<ResourcePack> $resourcePacks
* @phpstan-param array<string, string> $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<ResourcePack> $resourcePacks
* @phpstan-param array<string, string> $encryptionKeys
*/
public function setResourcePacks(array $resourcePacks, array $encryptionKeys) : void{
$this->resourcePacks = $resourcePacks;
$this->encryptionKeys = $encryptionKeys;
}
/**
* @return ResourcePack[]
* @phpstan-return list<ResourcePack>
*/
public function getResourcePacks() : array{
return $this->resourcePacks;
}
/**
* @return string[]
* @phpstan-return array<string, string>
*/
public function getEncryptionKeys() : array{
return $this->encryptionKeys;
}
public function setMustAccept(bool $mustAccept) : void{
$this->mustAccept = $mustAccept;
}
public function mustAccept() : bool{
return $this->mustAccept;
}
}

View File

@ -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();
}));
}

View File

@ -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<string, ResourcePack>
*/
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<ResourcePack> $resourcePackStack
* @phpstan-param array<string, string> $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;
}

View File

@ -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