├── .poggit.yml ├── README.md ├── plugin.yml └── src └── muqsit └── customsizedinvmenu ├── CustomSizedActorInvMenuGraphic.php ├── CustomSizedInvMenu.php └── CustomSizedInvMenuType.php /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/Muqsit/CustomSizedInvMenu 2 | build-by-default: true 3 | branches: 4 | - master 5 | projects: 6 | CustomSizedInvMenu: 7 | path: "" 8 | libs: 9 | - src: muqsit/InvMenu/InvMenu 10 | branch: pm5 11 | version: ^4.6.4 12 | ... 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustomSizedInvMenu 2 | To be able to build custom-sized `InvMenu` instances, you must install the [tedo0627/InventoryUIResourcePack](https://github.com/tedo0627/InventoryUIResourcePack) resource pack on your server. 3 | 4 | A custom-sized InvMenu provides several advantages over traditional ways of displaying menus (`InvMenu::TYPE_CHEST`, `InvMenu::TYPE_DOUBLE_CHEST`): 5 | 1. **Dynamic:** To create a menu that can hold `n` items, you need not know the size of a chest or a double chest inventory. Instead, simply create a menu of the preferred size: `CustomSizedInvMenu::create(n)`. 6 | 2. **Block-less:** Backed by an invisible entity, this menu does not require rendering a chest block strategically placed behind a player. Besides, rendering a chest block opens the server to an exploit where players can 'levitate' in the air by standing onto a fake chest block. 7 | 3. **More Portable:** Backed by an invisible entity, this menu can be sent to players anywhere, regardless of their Y-axis position. Block-backed menus can only be sent when the player's Y value falls within the world's minimum and maximum block height (because blocks cannot be set outside world bounds). 8 | 4. **Low Latency:** CustomSizedInvMenu renders right away, no delay necessary. On the other hand, `InvMenu::TYPE_DOUBLE_CHEST` is notoriously slow due to a 'delay' that is necessary to let the game render 'two individual chests transforming into a double chest'. 9 | 10 | ### Example API Usage 11 | To build a 5-slot InvMenu, use `CustomSizedInvMenu::create(5)`: 12 | ```php 13 | /** @var Player $player */ 14 | $menu = CustomSizedInvMenu::create(5); 15 | $menu->setName("This is a 5-slot Inventory"); 16 | $menu->send($player); 17 | ``` 18 | 19 | ### Example Command Usage 20 | Run `/cinvmenu [title]` to open an InvMenu of `numSlots` number of slots: 21 | - `/cinvmenu 36 "36-slot Inventory"` 22 | 23 | ![image](https://github.com/Muqsit/CustomSizedInvMenu/assets/15074389/721ee351-3247-4b37-8a42-da04851d66cb) 24 | 25 | - `/cinvmenu 59 "59-slot Inventory"` 26 | 27 | ![image](https://github.com/Muqsit/CustomSizedInvMenu/assets/15074389/602e2fdc-b675-4b7f-9e2e-b76c700c64a3) 28 | 29 | 30 | ### Resources 31 | - [tedo0627/InventoryUI](https://github.com/tedo0627/InventoryUI) 32 | - [tedo0627/InventoryUIResourcePack](https://github.com/tedo0627/InventoryUIResourcePack) 33 | -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | name: CustomSizedInvMenu 2 | main: muqsit\customsizedinvmenu\CustomSizedInvMenu 3 | api: 5.0.0 4 | version: 0.0.2 5 | 6 | commands: 7 | cinvmenu: 8 | usage: /cinvmenu [title] 9 | description: Opens a custom-sized inventory menu 10 | permission: customsizedinvmenu.command 11 | 12 | permissions: 13 | customsizedinvmenu.command: 14 | description: Allows using the /cinvmenu command 15 | default: true 16 | -------------------------------------------------------------------------------- /src/muqsit/customsizedinvmenu/CustomSizedActorInvMenuGraphic.php: -------------------------------------------------------------------------------- 1 | scrollbar; 23 | $this->size_data = "§{$this->length}§{$scroll}§r§r§r§r§r§r§r§r§r§r"; 24 | } 25 | 26 | public function send(Player $player, ?string $name) : void{ 27 | $this->inner->send($player, $this->size_data . ($name ?? $this->name ?? "Inventory")); 28 | } 29 | 30 | public function sendInventory(Player $player, Inventory $inventory) : bool{ 31 | return $this->inner->sendInventory($player, $inventory); 32 | } 33 | 34 | public function remove(Player $player) : void{ 35 | $this->inner->remove($player); 36 | } 37 | 38 | public function getNetworkTranslator() : ?InvMenuGraphicNetworkTranslator{ 39 | return $this->inner->getNetworkTranslator(); 40 | } 41 | 42 | public function getAnimationDuration() : int{ 43 | return $this->inner->getAnimationDuration(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/muqsit/customsizedinvmenu/CustomSizedInvMenu.php: -------------------------------------------------------------------------------- 1 | register($id, CustomSizedInvMenuType::ofSize($size)); 32 | $ids_by_size[$size] = $id; 33 | } 34 | return InvMenu::create($ids_by_size[$size]); 35 | } 36 | 37 | protected function onEnable() : void{ 38 | if($this->getServer()->getResourcePackManager()->getPackById(self::RESOURCE_PACK_ID) === null){ 39 | $this->getLogger()->warning("Resource pack 'Inventory UI Resource Pack' could not be found."); 40 | $this->getLogger()->warning("This plugin cannot be loaded. Please download the resource pack from: https://github.com/tedo0627/InventoryUIResourcePack"); 41 | throw new RuntimeException("Resource pack 'Inventory UI Resource Pack' has not been loaded"); 42 | } 43 | 44 | if(!InvMenuHandler::isRegistered()){ 45 | InvMenuHandler::register($this); 46 | } 47 | 48 | $packet = StaticPacketCache::getInstance()->getAvailableActorIdentifiers(); 49 | $tag = $packet->identifiers->getRoot(); 50 | assert($tag instanceof CompoundTag); 51 | $id_list = $tag->getListTag("idlist"); 52 | assert($id_list !== null); 53 | $id_list->push(CompoundTag::create() 54 | ->setString("bid", "") 55 | ->setByte("hasspawnegg", 0) 56 | ->setString("id", CustomSizedInvMenuType::ACTOR_NETWORK_ID) 57 | ->setByte("summonable", 0) 58 | ); 59 | } 60 | 61 | public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool{ 62 | if(!($sender instanceof Player)){ 63 | $sender->sendMessage(TextFormat::RED . "Please use this command in-game."); 64 | return true; 65 | } 66 | 67 | if(!isset($args[0]) || !is_numeric($args[0]) || (int) $args[0] <= 0){ 68 | $sender->sendMessage(TextFormat::RED . "/" . $command . " [title : string]"); 69 | return true; 70 | } 71 | 72 | $menu = CustomSizedInvMenu::create((int) $args[0]); 73 | $menu->setName($args[1] ?? null); 74 | 75 | $items = VanillaItems::getAll(); 76 | for($i = 0, $max = $menu->getInventory()->getSize(); $i < $max; $i++){ 77 | $menu->getInventory()->setItem($i, $items[array_rand($items)]); 78 | } 79 | 80 | $menu->send($sender); 81 | return true; 82 | } 83 | } -------------------------------------------------------------------------------- /src/muqsit/customsizedinvmenu/CustomSizedInvMenuType.php: -------------------------------------------------------------------------------- 1 | setByte(EntityMetadataProperties::CONTAINER_TYPE, WindowTypes::INVENTORY); 43 | $properties->setInt(EntityMetadataProperties::CONTAINER_BASE_SIZE, $this->size); 44 | 45 | $this->inner_graphic = new ActorInvMenuGraphic( 46 | self::ACTOR_NETWORK_ID, 47 | $actor_runtime_identifier, 48 | $properties->getAll(), 49 | new ActorInvMenuGraphicNetworkTranslator($actor_runtime_identifier) 50 | ); 51 | } 52 | 53 | public function createGraphic(InvMenu $menu, Player $player) : ?InvMenuGraphic{ 54 | return new CustomSizedActorInvMenuGraphic($this->inner_graphic, $menu->getName(), $this->length, $this->scrollbar); 55 | } 56 | 57 | public function createInventory() : Inventory{ 58 | return new InvMenuInventory($this->size); 59 | } 60 | } --------------------------------------------------------------------------------