PocketMine-MP 5.33.2 git-919492bdcad8510eb6606439eb77e1c604f1d1ea
Loading...
Searching...
No Matches
Chest.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\block;
25
28use pocketmine\block\tile\Chest as TileChest;
30use pocketmine\block\utils\AnimatedContainerLikeTrait;
31use pocketmine\block\utils\ChestPairHalf;
33use pocketmine\block\utils\ContainerTrait;
34use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
36use pocketmine\block\utils\HorizontalFacingOption;
37use pocketmine\block\utils\SupportType;
51use function assert;
52
54 use AnimatedContainerLikeTrait;
55 use ContainerTrait;
56 use FacesOppositePlacingPlayerTrait;
57
58 protected ?ChestPairHalf $pairHalf = null;
59
60 public function getPairHalf() : ?ChestPairHalf{ return $this->pairHalf; }
61
62 public function setPairHalf(?ChestPairHalf $pairHalf) : self{
63 $this->pairHalf = $pairHalf;
64 return $this;
65 }
66
67 public function readStateFromWorld() : Block{
68 parent::readStateFromWorld();
69 $tile = $this->position->getWorld()->getTile($this->position);
70
71 $this->pairHalf = null;
72 if($tile instanceof TileChest && ($pairXZ = $tile->getPairXZ()) !== null){
73 [$pairX, $pairZ] = $pairXZ;
74 foreach(ChestPairHalf::cases() as $pairSide){
75 $pairDirection = $pairSide->getOtherHalfSide($this->facing);
76 $pairPosition = $this->position->getSide($pairDirection);
77 if($pairPosition->getFloorX() === $pairX && $pairPosition->getFloorZ() === $pairZ){
78 $this->pairHalf = $pairSide;
79 break;
80 }
81 }
82 }
83
84 return $this;
85 }
86
87 public function writeStateToWorld() : void{
88 parent::writeStateToWorld();
89 $tile = $this->position->getWorld()->getTile($this->position);
90 assert($tile instanceof TileChest);
91
92 //TODO: this should probably use relative coordinates instead of absolute, for portability
93 if($this->pairHalf !== null){
94 $pairDirection = $this->pairHalf->getOtherHalfSide($this->facing);
95 $pairPosition = $this->position->getSide($pairDirection);
96 $pairXZ = [$pairPosition->getFloorX(), $pairPosition->getFloorZ()];
97 }else{
98 $pairXZ = null;
99 }
100 $tile->setPairXZ($pairXZ);
101 }
102
103 protected function recalculateCollisionBoxes() : array{
104 //these are slightly bigger than in PC
105 $facing = $this->facing->toFacing();
106 $box = AxisAlignedBB::one()
107 ->squashedCopy(Facing::axis($facing), 0.025)
108 ->trimmedCopy(Facing::UP, 0.05);
109 $pairSide = $this->pairHalf?->getOtherHalfSide($this->facing);
110 return [$pairSide !== null ?
111 $box->trimmedCopy(Facing::opposite($pairSide), 0.025) :
112 $box->squashedCopy(Facing::axis(Facing::rotateY($facing, true)), 0.025)
113 ];
114 }
115
116 public function getSupportType(Facing $facing) : SupportType{
117 return SupportType::NONE;
118 }
119
120 private function getPossiblePair(ChestPairHalf $pairSide) : ?Chest{
121 $pair = $this->getSide($pairSide->getOtherHalfSide($this->facing));
122 return $pair->hasSameTypeId($this) && $pair instanceof Chest && $pair->getFacing() === $this->facing ? $pair : null;
123 }
124
125 public function getOtherHalf() : ?Chest{
126 return $this->pairHalf !== null && ($pair = $this->getPossiblePair($this->pairHalf)) !== null && $pair->pairHalf === $this->pairHalf->opposite() ? $pair : null;
127 }
128
129 public function onPostPlace() : void{
130 //Not sure if this vanilla behaviour is intended, but a chest facing north or west will try to pair on the left
131 //side first, while a chest facing south or east will try the right side first.
132 $order = match($this->facing){
133 HorizontalFacingOption::NORTH, HorizontalFacingOption::WEST => [ChestPairHalf::LEFT, ChestPairHalf::RIGHT],
134 HorizontalFacingOption::SOUTH, HorizontalFacingOption::EAST => [ChestPairHalf::RIGHT, ChestPairHalf::LEFT]
135 };
136 $world = $this->position->getWorld();
137 foreach($order as $pairSide){
138 $possiblePair = $this->getPossiblePair($pairSide);
139 if($possiblePair !== null && $possiblePair->pairHalf === null){
140 [$left, $right] = $pairSide === ChestPairHalf::LEFT ? [$this, $possiblePair] : [$possiblePair, $this];
141 $ev = new ChestPairEvent($left, $right);
142 if(!$ev->isCancelled() && $world->getBlock($this->position)->isSameState($this) && $world->getBlock($possiblePair->position)->isSameState($possiblePair)){
143 $world->setBlock($this->position, $this->setPairHalf($pairSide));
144 $world->setBlock($possiblePair->position, $possiblePair->setPairHalf($pairSide->opposite()));
145 break;
146 }
147 }
148 }
149 }
150
151 public function onNearbyBlockChange() : void{
152 //TODO: If the pair chunk isn't loaded, a block update of an adjacent block in loaded terrain could cause the
153 //chest to become unpaired. However, this is not unique to chests (think wall connections). Probably we
154 //should defer updates in chunks whose neighbours are not loaded?
155 if($this->pairHalf !== null && $this->getOtherHalf() === null){
156 $this->position->getWorld()->setBlock($this->position, $this->setPairHalf(null));
157 }
158 }
159
160 public function isOpeningObstructed() : bool{
161 foreach([$this, $this->getOtherHalf()] as $chest){
162 if($chest !== null && !$chest->getSide(Facing::UP)->isTransparent()){
163 return true;
164 }
165 }
166 return false;
167 }
168
169 protected function getTile() : ?TileChest{
170 $tile = $this->position->getWorld()->getTile($this->position);
171 return $tile instanceof TileChest ? $tile : null;
172 }
173
174 public function getInventory() : ?Inventory{
175 $thisInventory = $this->getTile()?->getRealInventory();
176 if($thisInventory === null){
177 return null;
178 }
179 $pairInventory = $this->getOtherHalf()?->getTile()?->getRealInventory();
180 if($pairInventory === null){
181 return $thisInventory;
182 }
183
184 [$left, $right] = $this->pairHalf === ChestPairHalf::LEFT ? [$thisInventory, $pairInventory] : [$pairInventory, $thisInventory];
185 return new CombinedInventoryProxy([$left, $right]);
186 }
187
188 protected function newMenu(Player $player, Inventory $inventory, Position $position) : InventoryWindow{
189 $pair = $this->getOtherHalf();
190 if($pair === null){
191 return new BlockInventoryWindow($player, $inventory, $position);
192 }
193 [$left, $right] = $this->pairHalf === ChestPairHalf::LEFT ? [$this, $pair] : [$pair, $this];
194 return new DoubleChestInventoryWindow($player, $inventory, $left->position, $right->position);
195 }
196
197 public function getFuelTime() : int{
198 return 300;
199 }
200
201 protected function getOpenSound() : Sound{
202 return new ChestOpenSound();
203 }
204
205 protected function getCloseSound() : Sound{
206 return new ChestCloseSound();
207 }
208
209 protected function playAnimationVisual(Position $position, bool $isOpen) : void{
210 //event ID is always 1 for a chest
211 //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems
212 $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0));
213 }
214
215 protected function doAnimationEffects(bool $isOpen) : void{
216 $this->playAnimationVisual($this->position, $isOpen);
217 $this->playAnimationSound($this->position, $isOpen);
218
219 $pair = $this->getOtherHalf();
220 if($pair !== null){
221 $this->playAnimationVisual($pair->position, $isOpen);
222 $this->playAnimationSound($pair->position, $isOpen);
223 }
224 }
225}
hasSameTypeId(Block $other)
Definition Block.php:187
getSupportType(Facing $facing)
Definition Chest.php:116