Separate hotbar from player inventory

this allows this functionality to be used with any type of inventory, and also makes it a little nicer to use in many cases.
This commit is contained in:
Dylan K. Taylor 2024-11-24 17:58:58 +00:00
parent 3e9a96b43a
commit f98cebbd62
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
11 changed files with 185 additions and 141 deletions

View File

@ -56,7 +56,7 @@ class EnchantCommand extends VanillaCommand{
return true;
}
$item = $player->getInventory()->getItemInHand();
$item = $player->getHotbar()->getHeldItem();
if($item->isNull()){
$sender->sendMessage(KnownTranslationFactory::commands_enchant_noItem());
@ -79,7 +79,7 @@ class EnchantCommand extends VanillaCommand{
//this is necessary to deal with enchanted books, which are a different item type than regular books
$enchantedItem = EnchantingHelper::enchantItem($item, [new EnchantmentInstance($enchantment, $level)]);
$player->getInventory()->setItemInHand($enchantedItem);
$player->getHotbar()->setHeldItem($enchantedItem);
self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName()));
return true;

View File

@ -243,7 +243,7 @@ class ExperienceManager{
//TODO: replace this with a more generic equipment getting/setting interface
$equipment = [];
if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
if(($item = $this->entity->getHotbar()->getHeldItem()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
$equipment[$mainHandIndex] = $item;
}
if(($item = $this->entity->getOffHandInventory()->getItem(0)) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
@ -263,7 +263,7 @@ class ExperienceManager{
$xpValue -= (int) ceil($repairAmount / 2);
if($k === $mainHandIndex){
$this->entity->getInventory()->setItemInHand($repairItem);
$this->entity->getHotbar()->setHeldItem($repairItem);
}elseif($k === $offHandIndex){
$this->entity->getOffHandInventory()->setItem(0, $repairItem);
}else{

View File

@ -32,6 +32,7 @@ use pocketmine\entity\projectile\ProjectileSource;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Hotbar;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerEnderInventory;
@ -100,6 +101,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
public function getNetworkTypeId() : string{ return EntityIds::PLAYER; }
protected Hotbar $hotbar;
protected PlayerInventory $inventory;
protected PlayerOffHandInventory $offHandInventory;
protected PlayerEnderInventory $enderInventory;
@ -228,6 +230,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return min(100, 7 * $this->xpManager->getXpLevel());
}
public function getHotbar() : Hotbar{
return $this->hotbar;
}
public function getInventory() : PlayerInventory{
return $this->inventory;
}
@ -266,18 +272,20 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$this->xpManager = new ExperienceManager($this);
$this->inventory = new PlayerInventory($this);
$this->hotbar = new Hotbar($this->inventory);
$syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this)
);
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot, Item $unused2) use ($syncHeldItem) : void{
if($slot === $this->inventory->getHeldItemIndex()){
if($slot === $this->hotbar->getSelectedIndex()){
$syncHeldItem();
}
},
function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{
if(array_key_exists($this->inventory->getHeldItemIndex(), $oldItems)){
if(array_key_exists($this->hotbar->getSelectedIndex(), $oldItems)){
$syncHeldItem();
}
}
@ -326,8 +334,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
}
$this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0));
$this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->hotbar->setSelectedIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0));
$this->hotbar->getSelectedIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this)
));
@ -367,7 +375,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$type = $source->getCause();
if($type !== EntityDamageEvent::CAUSE_SUICIDE && $type !== EntityDamageEvent::CAUSE_VOID
&& ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){
&& ($this->hotbar->getHeldItem() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){
$compensation = $this->getHealth() - $source->getFinalDamage() - 1;
if($compensation <= -1){
@ -389,10 +397,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$this->broadcastAnimation(new TotemUseAnimation($this));
$this->broadcastSound(new TotemUseSound());
$hand = $this->inventory->getItemInHand();
$hand = $this->hotbar->getHeldItem();
if($hand instanceof Totem){
$hand->pop(); //Plugins could alter max stack size
$this->inventory->setItemInHand($hand);
$this->hotbar->setHeldItem($hand);
}elseif(($offHand = $this->offHandInventory->getItem(0)) instanceof Totem){
$offHand->pop();
$this->offHandInventory->setItem(0, $offHand);
@ -425,8 +433,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$nbt->setTag(self::TAG_INVENTORY, $inventoryTag);
//Normal inventory
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
$slotCount = $this->inventory->getSize() + $this->hotbar->getSize();
for($slot = $this->hotbar->getSize(); $slot < $slotCount; ++$slot){
$item = $this->inventory->getItem($slot - 9);
if(!$item->isNull()){
$inventoryTag->push($item->nbtSerialize($slot));
@ -441,7 +449,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
}
}
$nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->inventory->getHeldItemIndex());
$nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->hotbar->getSelectedIndex());
$offHandItem = $this->offHandInventory->getItem(0);
if(!$offHandItem->isNull()){
@ -495,7 +503,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$this->location->pitch,
$this->location->yaw,
$this->location->yaw, //TODO: head yaw
ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->getInventory()->getItemInHand())),
ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->hotbar->getHeldItem())),
GameMode::SURVIVAL,
$this->getAllNetworkData(),
new PropertySyncData([], []),
@ -529,8 +537,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
}
protected function onDispose() : void{
$this->hotbar->getSelectedIndexChangeListeners()->clear();
$this->inventory->removeAllViewers();
$this->inventory->getHeldItemIndexChangeListeners()->clear();
$this->offHandInventory->removeAllViewers();
$this->enderInventory->removeAllViewers();
parent::onDispose();

122
src/inventory/Hotbar.php Normal file
View File

@ -0,0 +1,122 @@
<?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.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\utils\ObjectSet;
final class Hotbar{
protected int $selectedIndex = 0;
/**
* @var \Closure[]|ObjectSet
* @phpstan-var ObjectSet<\Closure(int $oldIndex) : void>
*/
protected ObjectSet $selectedIndexChangeListeners;
public function __construct(
private Inventory $inventory,
private int $size = 9
){
if($this->inventory->getSize() < $this->size){
throw new \InvalidArgumentException("Inventory size must be at least $this->size");
}
$this->selectedIndexChangeListeners = new ObjectSet();
}
public function isHotbarSlot(int $slot) : bool{
return $slot >= 0 && $slot < $this->getSize();
}
/**
* @throws \InvalidArgumentException
*/
private function throwIfNotHotbarSlot(int $slot) : void{
if(!$this->isHotbarSlot($slot)){
throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getSize() - 1) . ")");
}
}
/**
* Returns the item in the specified hotbar slot.
*
* @throws \InvalidArgumentException if the hotbar slot index is out of range
*/
public function getHotbarSlotItem(int $hotbarSlot) : Item{
$this->throwIfNotHotbarSlot($hotbarSlot);
return $this->inventory->getItem($hotbarSlot);
}
/**
* Returns the hotbar slot number the holder is currently holding.
*/
public function getSelectedIndex() : int{
return $this->selectedIndex;
}
/**
* Sets which hotbar slot the player is currently loading.
*
* @param int $hotbarSlot 0-8 index of the hotbar slot to hold
*
* @throws \InvalidArgumentException if the hotbar slot is out of range
*/
public function setSelectedIndex(int $hotbarSlot) : void{
$this->throwIfNotHotbarSlot($hotbarSlot);
$oldIndex = $this->selectedIndex;
$this->selectedIndex = $hotbarSlot;
foreach($this->selectedIndexChangeListeners as $callback){
$callback($oldIndex);
}
}
/**
* @return \Closure[]|ObjectSet
* @phpstan-return ObjectSet<\Closure(int $oldIndex) : void>
*/
public function getSelectedIndexChangeListeners() : ObjectSet{ return $this->selectedIndexChangeListeners; }
/**
* Returns the currently-held item.
*/
public function getHeldItem() : Item{
return $this->getHotbarSlotItem($this->selectedIndex);
}
/**
* Sets the item in the currently-held slot to the specified item.
*/
public function setHeldItem(Item $item) : void{
$this->inventory->setItem($this->getSelectedIndex(), $item);
}
/**
* Returns the number of slots in the hotbar.
*/
public function getSize() : int{
return $this->size;
}
}

View File

@ -24,102 +24,16 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\entity\Human;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\ObjectSet;
class PlayerInventory extends SimpleInventory{
protected Human $holder;
protected int $itemInHandIndex = 0;
/**
* @var \Closure[]|ObjectSet
* @phpstan-var ObjectSet<\Closure(int $oldIndex) : void>
*/
protected ObjectSet $heldItemIndexChangeListeners;
public function __construct(Human $player){
$this->holder = $player;
$this->heldItemIndexChangeListeners = new ObjectSet();
parent::__construct(36);
}
public function isHotbarSlot(int $slot) : bool{
return $slot >= 0 && $slot < $this->getHotbarSize();
}
/**
* @throws \InvalidArgumentException
*/
private function throwIfNotHotbarSlot(int $slot) : void{
if(!$this->isHotbarSlot($slot)){
throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getHotbarSize() - 1) . ")");
}
}
/**
* Returns the item in the specified hotbar slot.
*
* @throws \InvalidArgumentException if the hotbar slot index is out of range
*/
public function getHotbarSlotItem(int $hotbarSlot) : Item{
$this->throwIfNotHotbarSlot($hotbarSlot);
return $this->getItem($hotbarSlot);
}
/**
* Returns the hotbar slot number the holder is currently holding.
*/
public function getHeldItemIndex() : int{
return $this->itemInHandIndex;
}
/**
* Sets which hotbar slot the player is currently loading.
*
* @param int $hotbarSlot 0-8 index of the hotbar slot to hold
*
* @throws \InvalidArgumentException if the hotbar slot is out of range
*/
public function setHeldItemIndex(int $hotbarSlot) : void{
$this->throwIfNotHotbarSlot($hotbarSlot);
$oldIndex = $this->itemInHandIndex;
$this->itemInHandIndex = $hotbarSlot;
foreach($this->heldItemIndexChangeListeners as $callback){
$callback($oldIndex);
}
}
/**
* @return \Closure[]|ObjectSet
* @phpstan-return ObjectSet<\Closure(int $oldIndex) : void>
*/
public function getHeldItemIndexChangeListeners() : ObjectSet{ return $this->heldItemIndexChangeListeners; }
/**
* Returns the currently-held item.
*/
public function getItemInHand() : Item{
return $this->getHotbarSlotItem($this->itemInHandIndex);
}
/**
* Sets the item in the currently-held slot to the specified item.
*/
public function setItemInHand(Item $item) : void{
$this->setItem($this->getHeldItemIndex(), $item);
}
/**
* Returns the number of slots in the hotbar.
*/
public function getHotbarSize() : int{
return 9;
}
public function getHolder() : Human{
return $this->holder;
}

View File

@ -145,7 +145,7 @@ class Armor extends Durable{
$thisCopy = clone $this;
$new = $thisCopy->pop();
$player->getArmorInventory()->setItem($this->getArmorSlot(), $new);
$player->getInventory()->setItemInHand($existing);
$player->getHotbar()->setHeldItem($existing);
$sound = $new->getMaterial()->getEquipSound();
if($sound !== null){
$player->broadcastSound($sound);

View File

@ -132,7 +132,7 @@ class InventoryManager{
$this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory());
$this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid());
$this->player->getInventory()->getHeldItemIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...));
$this->player->getHotbar()->getSelectedIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...));
}
private function associateIdWithInventory(int $id, Inventory $inventory) : void{
@ -668,7 +668,7 @@ class InventoryManager{
public function syncSelectedHotbarSlot() : void{
$playerInventory = $this->player->getInventory();
$selected = $playerInventory->getHeldItemIndex();
$selected = $this->player->getHotbar()->getSelectedIndex();
if($selected !== $this->clientSelectedHotbarSlot){
$inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null;
if($inventoryEntry === null){
@ -681,7 +681,7 @@ class InventoryManager{
$this->session->sendDataPacket(MobEquipmentPacket::create(
$this->player->getId(),
new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItemInHand())),
new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItem($selected))),
$selected,
$selected,
ContainerIds::INVENTORY

View File

@ -103,12 +103,12 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
public function onMobMainHandItemChange(array $recipients, Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$inv = $mob->getHotbar();
$this->sendDataPacket($recipients, MobEquipmentPacket::create(
$mob->getId(),
ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getItemInHand())),
$inv->getHeldItemIndex(),
$inv->getHeldItemIndex(),
ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getHeldItem())),
$inv->getSelectedIndex(),
$inv->getSelectedIndex(),
ContainerIds::INVENTORY
));
}

View File

@ -304,11 +304,11 @@ class InGamePacketHandler extends PacketHandler{
switch($packet->eventId){
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
$item = $this->player->getInventory()->getItemInHand();
$item = $this->player->getHotbar()->getHeldItem();
if($item->isNull()){
return false;
}
$this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $this->player->getInventory()->getItemInHand()));
$this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $item));
break;
default:
return false;

View File

@ -347,7 +347,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
private function callDummyItemHeldEvent() : void{
$slot = $this->inventory->getHeldItemIndex();
$slot = $this->hotbar->getSelectedIndex();
$event = new PlayerItemHeldEvent($this, $this->inventory->getItem($slot), $slot);
$event->call();
@ -362,7 +362,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot) : void{
if($slot === $this->inventory->getHeldItemIndex()){
if($slot === $this->hotbar->getSelectedIndex()){
$this->setUsingItem(false);
$this->callDummyItemHeldEvent();
@ -1540,10 +1540,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
public function selectHotbarSlot(int $hotbarSlot) : bool{
if(!$this->inventory->isHotbarSlot($hotbarSlot)){ //TODO: exception here?
if(!$this->hotbar->isHotbarSlot($hotbarSlot)){ //TODO: exception here?
return false;
}
if($hotbarSlot === $this->inventory->getHeldItemIndex()){
if($hotbarSlot === $this->hotbar->getSelectedIndex()){
return true;
}
@ -1553,7 +1553,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return false;
}
$this->inventory->setHeldItemIndex($hotbarSlot);
$this->hotbar->setSelectedIndex($hotbarSlot);
$this->setUsingItem(false);
return true;
@ -1565,7 +1565,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, array $extraReturnedItems) : void{
$heldItemChanged = false;
if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->inventory->getItemInHand())){
if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->hotbar->getHeldItem())){
//determine if the item was changed in some meaningful way, or just damaged/changed count
//if it was really changed we always need to set it, whether we have finite resources or not
$newReplica = clone $oldHeldItem;
@ -1579,7 +1579,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($newHeldItem instanceof Durable && $newHeldItem->isBroken()){
$this->broadcastSound(new ItemBreakSound());
}
$this->inventory->setItemInHand($newHeldItem);
$this->hotbar->setHeldItem($newHeldItem);
$heldItemChanged = true;
}
}
@ -1589,7 +1589,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
if($heldItemChanged && count($extraReturnedItems) > 0 && $newHeldItem->isNull()){
$this->inventory->setItemInHand(array_shift($extraReturnedItems));
$this->hotbar->setHeldItem(array_shift($extraReturnedItems));
}
foreach($this->inventory->addItem(...$extraReturnedItems) as $drop){
//TODO: we can't generate a transaction for this since the items aren't coming from an inventory :(
@ -1611,7 +1611,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
*/
public function useHeldItem() : bool{
$directionVector = $this->getDirectionVector();
$item = $this->inventory->getItemInHand();
$item = $this->hotbar->getHeldItem();
$oldItem = clone $item;
$ev = new PlayerItemUseEvent($this, $item, $directionVector);
@ -1645,7 +1645,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @return bool if the consumption succeeded.
*/
public function consumeHeldItem() : bool{
$slot = $this->inventory->getItemInHand();
$slot = $this->hotbar->getHeldItem();
if($slot instanceof ConsumableItem){
$oldItem = clone $slot;
@ -1678,7 +1678,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
*/
public function releaseHeldItem() : bool{
try{
$item = $this->inventory->getItemInHand();
$item = $this->hotbar->getHeldItem();
if(!$this->isUsingItem() || $this->hasItemCooldown($item)){
return false;
}
@ -1748,21 +1748,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{
if($existingSlot !== -1){
if($existingSlot < $this->inventory->getHotbarSize()){
$this->inventory->setHeldItemIndex($existingSlot);
if($existingSlot < $this->hotbar->getSize()){
$this->hotbar->setSelectedIndex($existingSlot);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot);
$this->inventory->swap($this->hotbar->getSelectedIndex(), $existingSlot);
}
}else{
$firstEmpty = $this->inventory->firstEmpty();
if($firstEmpty === -1){ //full inventory
$this->inventory->setItemInHand($item);
}elseif($firstEmpty < $this->inventory->getHotbarSize()){
$this->hotbar->setHeldItem($item);
}elseif($firstEmpty < $this->hotbar->getSize()){
$this->inventory->setItem($firstEmpty, $item);
$this->inventory->setHeldItemIndex($firstEmpty);
$this->hotbar->setSelectedIndex($firstEmpty);
}else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty);
$this->inventory->setItemInHand($item);
$this->inventory->swap($this->hotbar->getSelectedIndex(), $firstEmpty);
$this->hotbar->setHeldItem($item);
}
}
}
@ -1779,7 +1779,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$target = $this->getWorld()->getBlock($pos);
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
$ev = new PlayerInteractEvent($this, $this->hotbar->getHeldItem(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
if($this->isSpectator()){
$ev->cancel();
}
@ -1788,7 +1788,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return false;
}
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
if($target->onAttack($this->inventory->getItemInHand(), $face, $this)){
if($target->onAttack($this->hotbar->getHeldItem(), $face, $this)){
return true;
}
@ -1829,7 +1829,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
$this->stopBreakBlock($pos);
$item = $this->inventory->getItemInHand();
$item = $this->hotbar->getHeldItem();
$oldItem = clone $item;
$returnedItems = [];
if($this->getWorld()->useBreakOn($pos, $item, $this, true, $returnedItems)){
@ -1854,7 +1854,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
$item = $this->inventory->getItemInHand(); //this is a copy of the real item
$item = $this->hotbar->getHeldItem(); //this is a copy of the real item
$oldItem = clone $item;
$returnedItems = [];
if($this->getWorld()->useItemOn($pos, $item, $face, $clickOffset, $this, true, $returnedItems)){
@ -1883,7 +1883,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return false;
}
$heldItem = $this->inventory->getItemInHand();
$heldItem = $this->hotbar->getHeldItem();
$oldItem = clone $heldItem;
$ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints());
@ -1969,15 +1969,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$ev->call();
$item = $this->inventory->getItemInHand();
$item = $this->hotbar->getHeldItem();
$oldItem = clone $item;
if(!$ev->isCancelled()){
if($item->onInteractEntity($this, $entity, $clickPos)){
if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->inventory->getItemInHand())){
if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->hotbar->getHeldItem())){
if($item instanceof Durable && $item->isBroken()){
$this->broadcastSound(new ItemBreakSound());
}
$this->inventory->setItemInHand($item);
$this->hotbar->setHeldItem($item);
}
}
return $entity->onInteract($this, $clickPos);
@ -2405,8 +2405,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->getWorld()->dropItem($this->location, $item);
}
$this->hotbar->setSelectedIndex(0);
$clearInventory = fn(Inventory $inventory) => $inventory->setContents(array_filter($inventory->getContents(), fn(Item $item) => $item->keepOnDeath()));
$this->inventory->setHeldItemIndex(0);
$clearInventory($this->inventory);
$clearInventory($this->armorInventory);
$clearInventory($this->offHandInventory);

View File

@ -66,7 +66,7 @@ final class SurvivalBlockBreakHandler{
return 0.0;
}
//TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211)
$breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20;
$breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getHotbar()->getHeldItem()) * 20;
if($breakTimePerTick > 0){
return 1 / $breakTimePerTick;