*/ private array $slots = []; private ?ChiseledBookshelfSlot $lastInteractedSlot = null; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ $w->enum($this->facing); $w->enumSet($this->slots, ChiseledBookshelfSlot::cases()); } public function readStateFromWorld() : Block{ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileChiseledBookshelf){ $this->lastInteractedSlot = $tile->getLastInteractedSlot(); }else{ $this->lastInteractedSlot = null; } return $this; } public function writeStateToWorld() : void{ parent::writeStateToWorld(); $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileChiseledBookshelf){ $tile->setLastInteractedSlot($this->lastInteractedSlot); } } /** * Returns whether the given slot is displayed as occupied. * This doesn't guarantee that there is or isn't a book in the bookshelf's inventory. */ public function hasSlot(ChiseledBookshelfSlot $slot) : bool{ return isset($this->slots[spl_object_id($slot)]); } /** * Sets whether the given slot is displayed as occupied. * * This doesn't modify the bookshelf's inventory, so you can use this to make invisible * books or display books that aren't actually in the bookshelf. * * To modify the contents of the bookshelf inventory, access the tile inventory. * * @return $this */ public function setSlot(ChiseledBookshelfSlot $slot, bool $occupied) : self{ if($occupied){ $this->slots[spl_object_id($slot)] = $slot; }else{ unset($this->slots[spl_object_id($slot)]); } return $this; } /** * Returns which slots of the bookshelf are displayed as occupied. * As above, these values do not necessarily reflect the contents of the bookshelf inventory, * although they usually will unless modified by plugins. * * @return ChiseledBookshelfSlot[] * @phpstan-return array */ public function getSlots() : array{ return $this->slots; } /** * @param ChiseledBookshelfSlot[] $slots * @return $this */ public function setSlots(array $slots) : self{ $this->slots = []; foreach($slots as $slot){ $this->setSlot($slot, true); } return $this; } /** * Returns the last slot interacted by a player or null if no slot has been interacted with yet. */ public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ return $this->lastInteractedSlot; } /** * Sets the last slot interacted by a player. * * @return $this */ public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : self{ $this->lastInteractedSlot = $lastInteractedSlot; return $this; } public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($face !== $this->facing->toFacing()){ return false; } $x = Facing::axis($face) === Axis::X ? $clickVector->z : $clickVector->x; $slot = ChiseledBookshelfSlot::fromBlockFaceCoordinates( Facing::isPositive(Facing::rotateY($face, true)) ? 1 - $x : $x, $clickVector->y ); $tile = $this->position->getWorld()->getTile($this->position); if(!$tile instanceof TileChiseledBookshelf){ return false; } $inventory = $tile->getInventory(); if(!$inventory->isSlotEmpty($slot->value)){ $returnedItems[] = $inventory->getItem($slot->value); $inventory->clear($slot->value); $this->setSlot($slot, false); $this->lastInteractedSlot = $slot; }elseif($item instanceof WritableBookBase || $item instanceof Book || $item instanceof EnchantedBook){ //TODO: type tags like blocks would be better for this $inventory->setItem($slot->value, $item->pop()); $this->setSlot($slot, true); $this->lastInteractedSlot = $slot; }else{ return true; } $this->position->getWorld()->setBlock($this->position, $this); return true; } public function getDropsForCompatibleTool(Item $item) : array{ return []; } public function isAffectedBySilkTouch() : bool{ return true; } }