resetHotbar(false); parent::__construct($player, InventoryType::get(InventoryType::PLAYER)); } public function getSize(){ return parent::getSize() - 4; //Remove armor slots } public function setSize($size){ parent::setSize($size + 4); $this->sendContents($this->getViewers()); } /** * Called when a client equips a hotbar slot. This method should not be used by plugins. * This method will call PlayerItemHeldEvent. * * @param int $hotbarSlot Number of the hotbar slot to equip. * @param int|null $inventorySlot Inventory slot to map to the specified hotbar slot. Supply null to make no change to the link. * * @return bool if the equipment change was successful, false if not. */ public function equipItem(int $hotbarSlot, $inventorySlot = null) : bool{ if($inventorySlot === null){ $inventorySlot = $this->getHotbarSlotIndex($this->getHeldItemIndex()); } if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){ $this->sendContents($this->getHolder()); return false; } $this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($inventorySlot), $inventorySlot, $hotbarSlot)); if($ev->isCancelled()){ $this->sendContents($this->getHolder()); return false; } $this->setHotbarSlotIndex($hotbarSlot, $inventorySlot); $this->setHeldItemIndex($hotbarSlot, false); return true; } /** * Returns the index of the inventory slot mapped to the specified hotbar slot, or -1 if the hotbar slot does not exist. * @param int $index * * @return int */ public function getHotbarSlotIndex($index){ return $this->hotbar[$index] ?? -1; } /** * Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot. * This method is intended for use in network interaction with clients only. * * NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients. * * @param int $hotbarSlot * @param int $inventorySlot */ public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){ if($hotbarSlot >= 0 and $hotbarSlot < $this->getHotbarSize() and $inventorySlot >= -1 and $inventorySlot < $this->getSize()){ if($inventorySlot !== -1 and ($alreadyEquippedIndex = array_search($inventorySlot, $this->hotbar)) !== false){ /* Swap the slots * This assumes that the equipped slot can only be equipped in one other slot * it will not account for ancient bugs where the same slot ended up linked to several hotbar slots. * Such bugs will require a hotbar reset to default. */ $this->hotbar[$alreadyEquippedIndex] = $this->hotbar[$hotbarSlot]; } $this->hotbar[$hotbarSlot] = $inventorySlot; } } /** * Resets hotbar links to their original defaults. * @param bool $send Whether to send changes to the holder. */ public function resetHotbar(bool $send = true){ $this->hotbar = range(0, $this->getHotbarSize() - 1, 1); if($send){ $this->sendContents($this->getHolder()); } } /** * Returns the hotbar slot number the holder is currently holding. * @return int */ public function getHeldItemIndex(){ return $this->itemInHandIndex; } /** * Sets which hotbar slot the player is currently loading. * * @param int $index 0-8 index of the hotbar slot to hold * @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls. * It should only be false to prevent feedback loops of equipment packets between client and server. */ public function setHeldItemIndex($index, $send = true){ if($index >= 0 and $index < $this->getHotbarSize()){ $this->itemInHandIndex = $index; if($this->getHolder() instanceof Player and $send){ $this->sendHeldItem($this->getHolder()); } $this->sendHeldItem($this->getHolder()->getViewers()); } } /** * Returns the currently-held item. * * @return Item */ public function getItemInHand(){ $item = $this->getItem($this->getHeldItemSlot()); if($item instanceof Item){ return $item; }else{ return Item::get(Item::AIR, 0, 0); } } /** * Sets the item in the currently-held slot to the specified item. * @param Item $item * * @return bool */ public function setItemInHand(Item $item){ return $this->setItem($this->getHeldItemSlot(), $item); } /** * Returns the hotbar slot number currently held. * * @return int */ public function getHeldItemSlot(){ return $this->getHotbarSlotIndex($this->itemInHandIndex); } /** * Sets the hotbar slot link of the currently-held hotbar slot. * @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients. * * @param int $slot */ public function setHeldItemSlot($slot){ if($slot >= -1 and $slot < $this->getSize()){ $this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot); } } /** * Sends the currently-held item to specified targets. * @param Player|Player[] $target */ public function sendHeldItem($target){ $item = $this->getItemInHand(); $pk = new MobEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->item = $item; $pk->inventorySlot = $this->getHeldItemSlot(); $pk->hotbarSlot = $this->getHeldItemIndex(); if(!is_array($target)){ $target->dataPacket($pk); if($target === $this->getHolder()){ $this->sendSlot($this->getHeldItemSlot(), $target); } }else{ $this->getHolder()->getLevel()->getServer()->broadcastPacket($target, $pk); foreach($target as $player){ if($player === $this->getHolder()){ $this->sendSlot($this->getHeldItemSlot(), $player); break; } } } } public function onSlotChange($index, $before){ $holder = $this->getHolder(); if($holder instanceof Player and !$holder->spawned){ return; } parent::onSlotChange($index, $before); if($index >= $this->getSize()){ $this->sendArmorSlot($index, $this->getViewers()); $this->sendArmorSlot($index, $this->getHolder()->getViewers()); } } /** * Returns the number of slots in the hotbar. * @return int */ public function getHotbarSize(){ return 9; } public function getArmorItem($index){ return $this->getItem($this->getSize() + $index); } public function setArmorItem($index, Item $item){ return $this->setItem($this->getSize() + $index, $item); } public function getHelmet(){ return $this->getItem($this->getSize()); } public function getChestplate(){ return $this->getItem($this->getSize() + 1); } public function getLeggings(){ return $this->getItem($this->getSize() + 2); } public function getBoots(){ return $this->getItem($this->getSize() + 3); } public function setHelmet(Item $helmet){ return $this->setItem($this->getSize(), $helmet); } public function setChestplate(Item $chestplate){ return $this->setItem($this->getSize() + 1, $chestplate); } public function setLeggings(Item $leggings){ return $this->setItem($this->getSize() + 2, $leggings); } public function setBoots(Item $boots){ return $this->setItem($this->getSize() + 3, $boots); } public function setItem($index, Item $item){ if($index < 0 or $index >= $this->size){ return false; }elseif($item->getId() === 0 or $item->getCount() <= 0){ return $this->clear($index); } if($index >= $this->getSize()){ //Armor change Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $item, $index)); if($ev->isCancelled() and $this->getHolder() instanceof Human){ $this->sendArmorSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); }else{ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $item, $index)); if($ev->isCancelled()){ $this->sendSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); } $old = $this->getItem($index); $this->slots[$index] = clone $item; $this->onSlotChange($index, $old); return true; } public function clear($index){ if(isset($this->slots[$index])){ $item = Item::get(Item::AIR, 0, 0); $old = $this->slots[$index]; if($index >= $this->getSize() and $index < $this->size){ //Armor change Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $old, $item, $index)); if($ev->isCancelled()){ if($index >= $this->size){ $this->sendArmorSlot($index, $this->getViewers()); }else{ $this->sendSlot($index, $this->getViewers()); } return false; } $item = $ev->getNewItem(); }else{ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $old, $item, $index)); if($ev->isCancelled()){ if($index >= $this->size){ $this->sendArmorSlot($index, $this->getViewers()); }else{ $this->sendSlot($index, $this->getViewers()); } return false; } $item = $ev->getNewItem(); } if($item->getId() !== Item::AIR){ $this->slots[$index] = clone $item; }else{ unset($this->slots[$index]); } $this->onSlotChange($index, $old); } return true; } /** * @return Item[] */ public function getArmorContents(){ $armor = []; for($i = 0; $i < 4; ++$i){ $armor[$i] = $this->getItem($this->getSize() + $i); } return $armor; } public function clearAll(){ $limit = $this->getSize() + 4; for($index = 0; $index < $limit; ++$index){ $this->clear($index); } } /** * @param Player|Player[] $target */ public function sendArmorContents($target){ if($target instanceof Player){ $target = [$target]; } $armor = $this->getArmorContents(); $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $armor; $pk->encode(); $pk->isEncoded = true; foreach($target as $player){ if($player === $this->getHolder()){ $pk2 = new ContainerSetContentPacket(); $pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR; $pk2->slots = $armor; $player->dataPacket($pk2); }else{ $player->dataPacket($pk); } } } /** * @param Item[] $items */ public function setArmorContents(array $items){ for($i = 0; $i < 4; ++$i){ if(!isset($items[$i]) or !($items[$i] instanceof Item)){ $items[$i] = Item::get(Item::AIR, 0, 0); } if($items[$i]->getId() === Item::AIR){ $this->clear($this->getSize() + $i); }else{ $this->setItem($this->getSize() + $i, $items[$i]); } } } /** * @param int $index * @param Player|Player[] $target */ public function sendArmorSlot($index, $target){ if($target instanceof Player){ $target = [$target]; } $armor = $this->getArmorContents(); $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $armor; $pk->encode(); $pk->isEncoded = true; foreach($target as $player){ if($player === $this->getHolder()){ /** @var Player $player */ $pk2 = new ContainerSetSlotPacket(); $pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR; $pk2->slot = $index - $this->getSize(); $pk2->item = $this->getItem($index); $player->dataPacket($pk2); }else{ $player->dataPacket($pk); } } } /** * @param Player|Player[] $target */ public function sendContents($target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetContentPacket(); $pk->slots = []; for($i = 0; $i < $this->getSize(); ++$i){ //Do not send armor by error here $pk->slots[$i] = $this->getItem($i); } //Because PE is stupid and shows 9 less slots than you send it, give it 9 dummy slots so it shows all the REAL slots. for($i = $this->getSize(); $i < $this->getSize() + $this->getHotbarSize(); ++$i){ $pk->slots[$i] = Item::get(Item::AIR, 0, 0); } foreach($target as $player){ $pk->hotbar = []; if($player === $this->getHolder()){ for($i = 0; $i < $this->getHotbarSize(); ++$i){ $index = $this->getHotbarSlotIndex($i); $pk->hotbar[] = $index <= -1 ? -1 : $index + $this->getHotbarSize(); } } if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){ $this->close($player); continue; } $pk->windowid = $id; $player->dataPacket(clone $pk); } } public function sendCreativeContents(){ $pk = new ContainerSetContentPacket(); $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; if($this->getHolder()->getGamemode() === Player::CREATIVE){ foreach(Item::getCreativeItems() as $i => $item){ $pk->slots[$i] = clone $item; } } $this->getHolder()->dataPacket($pk); } /** * @param int $index * @param Player|Player[] $target */ public function sendSlot($index, $target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetSlotPacket(); $pk->slot = $index; $pk->item = clone $this->getItem($index); foreach($target as $player){ if($player === $this->getHolder()){ /** @var Player $player */ $pk->windowid = 0; $player->dataPacket(clone $pk); }else{ if(($id = $player->getWindowId($this)) === -1){ $this->close($player); continue; } $pk->windowid = $id; $player->dataPacket(clone $pk); } } } /** * @return Human|Player */ public function getHolder(){ return parent::getHolder(); } }