Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
523 changes: 520 additions & 3 deletions src/block/Hopper.php

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions src/block/Jukebox.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,22 @@ public function getRecord() : ?Record{
}

public function ejectRecord() : void{
if($this->record !== null){
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $this->record);
$record = $this->extractRecord();
if($record !== null){
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $record);
}
}

/**
* Removes the record from the jukebox without dropping it and returns it, or null if there was no record inside.
*/
public function extractRecord() : ?Record{
$record = $this->record;
if($record !== null){
$this->record = null;
$this->stopSound();
}
return $record;
}

public function insertRecord(Record $record) : void{
Expand Down
32 changes: 30 additions & 2 deletions src/block/inventory/ShulkerBoxInventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,41 @@ protected function getCloseSound() : Sound{
}

public function canAddItem(Item $item) : bool{
$blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId());
if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){
if($this->isNestedShulkerBox($item)){
return false;
}
return parent::canAddItem($item);
}

public function getAddableItemQuantity(Item $item) : int{
if($this->isNestedShulkerBox($item)){
return 0;
}
return parent::getAddableItemQuantity($item);
}

public function addItem(Item ...$slots) : array{
$accepted = [];
$rejected = [];
foreach($slots as $slot){
if($this->isNestedShulkerBox($slot)){
$rejected[] = clone $slot;
continue;
}
$accepted[] = $slot;
}
$leftover = $accepted === [] ? [] : parent::addItem(...$accepted);
foreach($rejected as $item){
$leftover[] = $item;
}
return $leftover;
}

private function isNestedShulkerBox(Item $item) : bool{
$blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId());
return $blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX;
}

protected function animateBlock(bool $isOpen) : void{
$holder = $this->getHolder();

Expand Down
28 changes: 27 additions & 1 deletion src/block/tile/Hopper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,34 @@
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\world\World;
use function max;

class Hopper extends Spawnable implements Container, Nameable{

use ContainerTrait;
use NameableTrait;

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

private HopperInventory $inventory;
private int $transferCooldown = 0;
private int $lastScheduledUpdateTick = -1;

public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
$this->inventory = new HopperInventory($this->position);
// Hopper::onScheduledUpdate() keeps rescheduling itself, but something has to start that chain off - both for
// newly placed hoppers and for the ones read back from disk.
$world->scheduleDelayedBlockUpdate($pos, 1);
}

public function readSaveData(CompoundTag $nbt) : void{
$this->loadItems($nbt);
$this->loadName($nbt);

$this->transferCooldown = $nbt->getInt(self::TAG_TRANSFER_COOLDOWN, 0);
// Only negative values are rejected here, to stay consistent with what setTransferCooldown() accepts.
$this->transferCooldown = max(0, $nbt->getInt(self::TAG_TRANSFER_COOLDOWN, 0));
}

protected function writeSaveData(CompoundTag $nbt) : void{
Expand Down Expand Up @@ -78,4 +85,23 @@ public function getInventory() : HopperInventory{
public function getRealInventory() : HopperInventory{
return $this->inventory;
}

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

public function setTransferCooldown(int $transferCooldown) : void{
if($transferCooldown < 0){
throw new \InvalidArgumentException("Transfer cooldown must not be negative");
}
$this->transferCooldown = $transferCooldown;
}

public function getLastScheduledUpdateTick() : int{
return $this->lastScheduledUpdateTick;
}

public function setLastScheduledUpdateTick(int $tick) : void{
$this->lastScheduledUpdateTick = $tick;
}
}
76 changes: 76 additions & 0 deletions src/event/inventory/InventoryMoveItemEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
*
* _ _ _
* / \ | | |_ __ _ _ _
* / _ \ | | __/ _` | | | |
* / ___ \| | || (_| | |_| |
* /_/ \_\_|\__\__,_|\__, |
* |___/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Original work by the PocketMine Team.
* https://www.pocketmine.net/
*
* @author Altay Team
* @link https://github.com/altayofficial
*/

declare(strict_types=1);

namespace pocketmine\event\inventory;

use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\event\Event;
use pocketmine\inventory\Inventory;
use pocketmine\item\Item;

/**
* Called when an item is moved from one inventory to another by a block, such as a hopper.
*
* Some sources and destinations, such as jukeboxes, don't have an inventory at all, in which case the respective side
* of the move is null.
*/
class InventoryMoveItemEvent extends Event implements Cancellable{
use CancellableTrait;

public function __construct(
private ?Inventory $source,
private ?Inventory $destination,
private Item $item
){}

/**
* Returns the inventory the item is taken from, or null if the item doesn't come from an inventory.
*/
public function getSource() : ?Inventory{
return $this->source;
}

/**
* Returns the inventory the item is moved into, or null if the item isn't moved into an inventory.
*/
public function getDestination() : ?Inventory{
return $this->destination;
}

/**
* Returns the item which is being moved.
*/
public function getItem() : Item{
return clone $this->item;
}

/**
* Changes the item which is moved to the destination inventory.
*/
public function setItem(Item $item) : void{
$this->item = clone $item;
}
}
61 changes: 61 additions & 0 deletions tests/phpunit/block/inventory/ShulkerBoxInventoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
*
* _ _ _
* / \ | | |_ __ _ _ _
* / _ \ | | __/ _` | | | |
* / ___ \| | || (_| | |_| |
* /_/ \_\_|\__\__,_|\__, |
* |___/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Original work by the PocketMine Team.
* https://www.pocketmine.net/
*
* @author Altay Team
* @link https://github.com/altayofficial
*/

declare(strict_types=1);

namespace pocketmine\block\inventory;

use PHPUnit\Framework\TestCase;
use pocketmine\block\VanillaBlocks;
use pocketmine\world\Position;

class ShulkerBoxInventoryTest extends TestCase{

private function createInventory() : ShulkerBoxInventory{
return new ShulkerBoxInventory(new Position(0, 0, 0, null));
}

public function testRejectsNestedShulkerBoxes() : void{
$inventory = $this->createInventory();
$shulker = VanillaBlocks::SHULKER_BOX()->asItem();
$dyed = VanillaBlocks::DYED_SHULKER_BOX()->asItem();

self::assertFalse($inventory->canAddItem($shulker));
self::assertFalse($inventory->canAddItem($dyed));
self::assertSame(0, $inventory->getAddableItemQuantity($shulker));
self::assertSame(0, $inventory->getAddableItemQuantity($dyed));
self::assertNotEmpty($inventory->addItem($shulker));
self::assertNotEmpty($inventory->addItem($dyed));
self::assertTrue($inventory->isSlotEmpty(0));
}

public function testStillAcceptsNormalItems() : void{
$inventory = $this->createInventory();
$dirt = VanillaBlocks::DIRT()->asItem()->setCount(16);

self::assertTrue($inventory->canAddItem($dirt));
self::assertSame(16, $inventory->getAddableItemQuantity($dirt));
self::assertEmpty($inventory->addItem($dirt));
self::assertTrue($inventory->getItem(0)->equalsExact($dirt));
}
}
22 changes: 21 additions & 1 deletion tests/plugins/TesterPlugin/src/Main.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@

namespace pmmp\TesterPlugin;

use pmmp\TesterPlugin\hopper\HopperChainStressTest;
use pmmp\TesterPlugin\hopper\HopperCooldownTest;
use pmmp\TesterPlugin\hopper\HopperInventoryListenerDupeTest;
use pmmp\TesterPlugin\hopper\HopperJukeboxDupeTest;
use pmmp\TesterPlugin\hopper\HopperMoveEventDupeTest;
use pmmp\TesterPlugin\hopper\HopperPickupBoundsTest;
use pmmp\TesterPlugin\hopper\HopperPickupDupeTest;
use pmmp\TesterPlugin\hopper\HopperPickupRangeTest;
use pmmp\TesterPlugin\hopper\HopperPickupShulkerNestTest;
use pmmp\TesterPlugin\hopper\HopperStaleUpdateTest;
use pocketmine\event\Listener;
use pocketmine\event\server\CommandEvent;
use pocketmine\plugin\PluginBase;
Expand Down Expand Up @@ -74,7 +84,17 @@ function() : void{
throw new TestFailedException();
}
}
)
),
new HopperChainStressTest($this->getLogger(), $this),
new HopperCooldownTest($this->getLogger(), $this),
new HopperMoveEventDupeTest($this->getLogger(), $this),
new HopperInventoryListenerDupeTest($this->getLogger(), $this),
new HopperPickupDupeTest($this->getLogger(), $this),
new HopperPickupRangeTest($this->getLogger(), $this),
new HopperPickupBoundsTest($this->getLogger(), $this),
new HopperPickupShulkerNestTest($this->getLogger(), $this),
new HopperStaleUpdateTest($this->getLogger(), $this),
new HopperJukeboxDupeTest($this->getLogger(), $this)
];
}

Expand Down
Loading
Loading