Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ba01569
added getter and setter for the transfer cooldown
ColinHDev Aug 27, 2021
79da692
implement hopper logic for pushing, pulling and picking up items
ColinHDev Aug 29, 2021
c640a5a
added initiation of hopper block scheduling
ColinHDev Aug 29, 2021
30e295a
added import of count function
ColinHDev Aug 29, 2021
2fb995d
added comment about a hopper's decision to pull or pick up items
ColinHDev Aug 29, 2021
f9a0f98
hoppers now get their records removed if they push into a jukebox
ColinHDev Aug 29, 2021
7fb541d
the slot variable of the hopper inventory gets no longer overwritten …
ColinHDev Aug 29, 2021
463b946
removed redundant Item::isNull() check
ColinHDev Aug 29, 2021
c72c860
removed redundant import of ItemFactory
ColinHDev Aug 29, 2021
7e10f89
removed redundant check if the item is a shulkerbox while a hopper is…
ColinHDev Aug 29, 2021
fb3d2df
removed redundant comments and combined two similar if statements
ColinHDev Sep 3, 2021
e3c2d07
TODO comments now match PMMP's coding style
ColinHDev Sep 3, 2021
05e3b22
added DEFAULT_TRANSFER_COOLDOWN constant
ColinHDev Sep 3, 2021
616b0d5
Merge remote-tracking branch 'origin/master' into hoppers
ColinHDev Sep 3, 2021
15e6fc3
added braces around NOT operator
ColinHDev Sep 3, 2021
5e094d7
removed dependency on hopper tiles in logic functions
ColinHDev Sep 3, 2021
bdce93b
if a hopper couldn't push to a jukebox because the tile had no corres…
ColinHDev Sep 3, 2021
bfa8a1d
removed dependency on container tiles in pull logic function
ColinHDev Sep 3, 2021
274453d
fixed the provided parameter types for logic functions
ColinHDev Sep 3, 2021
c3113c1
added TODO about removing dependency on container tiles in pull logic…
ColinHDev Sep 3, 2021
af336e5
used new inventory slot constants instead of hardcoded values
ColinHDev Sep 3, 2021
76cb9d2
renamed smelting slot to input slot in comment
ColinHDev Sep 3, 2021
1ac9c44
removed usage of bitwise operators
ColinHDev Sep 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 235 additions & 2 deletions src/block/Hopper.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@

namespace pocketmine\block;

use pocketmine\block\inventory\FurnaceInventory;
use pocketmine\block\inventory\HopperInventory;
use pocketmine\block\tile\Container;
use pocketmine\block\tile\Furnace as TileFurnace;
use pocketmine\block\tile\Hopper as TileHopper;
use pocketmine\block\tile\Jukebox as TileJukebox;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\block\utils\PoweredByRedstoneTrait;
use pocketmine\entity\object\ItemEntity;
use pocketmine\inventory\Inventory;
use pocketmine\item\Bucket;
use pocketmine\item\Item;
use pocketmine\item\Record;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function count;

class Hopper extends Transparent{
use PoweredByRedstoneTrait;
Expand Down Expand Up @@ -96,8 +106,231 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
}

public function onScheduledUpdate() : void{
//TODO
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);

$tile = $this->position->getWorld()->getTile($this->position);
if(!$tile instanceof TileHopper){
return;
}

$transferCooldown = $tile->getTransferCooldown();
if($transferCooldown > 0){
$transferCooldown--;
$tile->setTransferCooldown($transferCooldown);
}

if($this->isPowered() || $transferCooldown > 0){
return;
}

$inventory = $tile->getInventory();
$success = $this->push($inventory);
// Hoppers that have a container above them, won't try to pick up items.
$origin = $this->position->getWorld()->getTile($this->position->getSide(Facing::UP));
//TODO: Not all blocks a hopper can pull from have an inventory (for example: Jukebox).
if($origin instanceof Container){
$success = $this->pull($inventory, $origin->getInventory()) || $success;
}else{
$success = $this->pickup($inventory) || $success;
}
// The cooldown is only set back to the default amount of ticks if the hopper has done anything.
if($success){
$tile->setTransferCooldown(TileHopper::DEFAULT_TRANSFER_COOLDOWN);
}
}

/**
* This function handles pushing items from the hopper to a tile in the direction the hopper is facing.
* Returns true if an item was successfully pushed or false on failure.
*/
private function push(HopperInventory $inventory) : bool{
if(count($inventory->getContents()) === 0){
return false;
}
$destination = $this->position->getWorld()->getTile($this->position->getSide($this->facing));
if($destination === null){
return false;
}

for($slot = 0; $slot < $inventory->getSize(); $slot++){
$item = $inventory->getItem($slot);
if($item->isNull()){
continue;
}

// Hoppers interact differently when pushing into different kinds of tiles.
//TODO: Composter
//TODO: Brewing Stand
//TODO: Jukebox (improve)
if($destination instanceof TileFurnace){
// If the hopper is facing down, it will push every item to the furnace's input slot, even items that aren't smeltable.
// If the hopper is facing in any other direction, it will only push items that can be used as fuel to the furnace's fuel slot.
if($this->facing === Facing::DOWN){
$slotInFurnace = FurnaceInventory::SLOT_INPUT;
$itemInFurnace = $destination->getInventory()->getSmelting();
}else{
if($item->getFuelTime() === 0){
continue;
}
$slotInFurnace = FurnaceInventory::SLOT_FUEL;
$itemInFurnace = $destination->getInventory()->getFuel();
}
if(!$itemInFurnace->isNull()){
if($itemInFurnace->getCount() >= $itemInFurnace->getMaxStackSize()){
return false;
}
if(!$itemInFurnace->canStackWith($item)){
continue;
}
$item->pop();
$itemInFurnace->setCount($itemInFurnace->getCount() + 1);
}else{
$itemInFurnace = $item->pop();
}

//TODO: event on item inventory switch

$destination->getInventory()->setItem($slotInFurnace, $itemInFurnace);
$inventory->setItem($slot, $item);
return true;

}elseif($destination instanceof TileHopper){
$itemToPush = $item->pop();
if(!$destination->getInventory()->canAddItem($itemToPush)){
continue;
}
// Hoppers pushing into empty hoppers set the empty hoppers transfer cooldown back to the default amount of ticks.
if(count($destination->getInventory()->getContents()) === 0){
$destination->setTransferCooldown(TileHopper::DEFAULT_TRANSFER_COOLDOWN);
}

}elseif($destination instanceof TileJukebox){
if(!($item instanceof Record)){
continue;
}
//TODO:
// Jukeboxes actually emit a redstone signal when playing a record so nearby hoppers are blocked and
// prevented from inserting another disk. Because neither does redstone work properly nor can we check if
// a jukebox is still playing a record or has already finished it, we can just check if it has already a
// record inserted.
if($destination->getRecord() !== null){
return false;
}

// The Jukebox block is handling the playing of records, so we need to get it here and can't use TileJukebox::setRecord().
$jukeboxBlock = $destination->getBlock();
if($jukeboxBlock instanceof Jukebox){
$jukeboxBlock->insertRecord($item->pop());
$jukeboxBlock->getPosition()->getWorld()->setBlock($jukeboxBlock->getPosition(), $jukeboxBlock);
$inventory->setItem($slot, $item);
return true;
}
return false;

}elseif($destination instanceof Container){
$itemToPush = $item->pop();
if(!$destination->getInventory()->canAddItem($itemToPush)){
continue;
}

}else{
return false;
}

//TODO: event on item inventory switch

$inventory->setItem($slot, $item);
$destination->getInventory()->addItem($itemToPush);
return true;
}
return false;
}

//TODO: redstone logic, sucking logic
/**
* This function handles pulling items by the hopper from a container above.
* Returns true if an item was successfully pulled or false on failure.
*/
private function pull(HopperInventory $inventory, Inventory $origin) : bool{
// Hoppers interact differently when pulling from different kinds of tiles.
//TODO: Composter
//TODO: Brewing Stand
//TODO: Jukebox
if($origin instanceof FurnaceInventory){
// Hoppers either pull empty buckets from the furnace's fuel slot or pull from its result slot.
// They prioritise pulling from the fuel slot over the result slot.
$item = $origin->getFuel();
if($item instanceof Bucket){
$slot = FurnaceInventory::SLOT_FUEL;
}else{
$slot = FurnaceInventory::SLOT_RESULT;
$item = $origin->getResult();
if($item->isNull()){
return false;
}
}
$itemToPull = $item->pop();

//TODO: event on item inventory switch

$origin->setItem($slot, $item);
$inventory->addItem($itemToPull);
return true;

}else{
for($slot = 0; $slot < $origin->getSize(); $slot++){
$item = $origin->getItem($slot);
if($item->isNull()){
continue;
}
$itemToPull = $item->pop();
if(!$inventory->canAddItem($itemToPull)){
continue;
}

//TODO: event on item inventory switch

$origin->setItem($slot, $item);
$inventory->addItem($itemToPull);
return true;
}
}
return false;
}

/**
* This function handles picking up items by the hopper.
* Returns true if an item was successfully picked up or false on failure.
*/
private function pickup(HopperInventory $inventory) : bool{
// In Bedrock Edition hoppers collect from the lower 3/4 of the block space above them.
$pickupCollisionBox = new AxisAlignedBB(
$this->position->getX(),
$this->position->getY() + 1,
$this->position->getZ(),
$this->position->getX() + 1,
$this->position->getY() + 1.75,
$this->position->getZ() + 1
);

foreach($this->position->getWorld()->getNearbyEntities($pickupCollisionBox) as $entity){
if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity instanceof ItemEntity){
continue;
}
// Unlike Java Edition, Bedrock Edition's hoppers don't save in which order item entities landed on top of them to collect them in that order.
// In Bedrock Edition hoppers collect item entities in the order in which they entered the chunk.
// Because of how entities are saved by PocketMine-MP the first entities of this loop are also the first ones who were saved.
// That's why we don't need to implement any sorting mechanism.
$item = $entity->getItem();
if(!$inventory->canAddItem($item)){
continue;
}

//TODO: event on block picking up an item

$inventory->addItem($item);
$entity->flagForDespawn();
return true;
}
return false;
}
}
10 changes: 10 additions & 0 deletions src/block/tile/Hopper.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Hopper extends Spawnable implements Container, Nameable{
use NameableTrait;

private const TAG_TRANSFER_COOLDOWN = "TransferCooldown";
public const DEFAULT_TRANSFER_COOLDOWN = 8;

/** @var HopperInventory */
private $inventory;
Expand All @@ -44,6 +45,7 @@ class Hopper extends Spawnable implements Container, Nameable{
public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
$this->inventory = new HopperInventory($this->position);
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
}

public function readSaveData(CompoundTag $nbt) : void{
Expand Down Expand Up @@ -85,4 +87,12 @@ public function getInventory(){
public function getRealInventory(){
return $this->inventory;
}

public function getTransferCooldown() : int{
return $this->transferCooldown;
}

public function setTransferCooldown(int $transferCooldown) : void{
$this->transferCooldown = $transferCooldown;
}
}