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; return true;
} }
$item = $player->getInventory()->getItemInHand(); $item = $player->getHotbar()->getHeldItem();
if($item->isNull()){ if($item->isNull()){
$sender->sendMessage(KnownTranslationFactory::commands_enchant_noItem()); $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 //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)]); $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())); self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName()));
return true; return true;

View File

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

View File

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

View File

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

View File

@ -132,7 +132,7 @@ class InventoryManager{
$this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory());
$this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); $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{ private function associateIdWithInventory(int $id, Inventory $inventory) : void{
@ -668,7 +668,7 @@ class InventoryManager{
public function syncSelectedHotbarSlot() : void{ public function syncSelectedHotbarSlot() : void{
$playerInventory = $this->player->getInventory(); $playerInventory = $this->player->getInventory();
$selected = $playerInventory->getHeldItemIndex(); $selected = $this->player->getHotbar()->getSelectedIndex();
if($selected !== $this->clientSelectedHotbarSlot){ if($selected !== $this->clientSelectedHotbarSlot){
$inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null; $inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null;
if($inventoryEntry === null){ if($inventoryEntry === null){
@ -681,7 +681,7 @@ class InventoryManager{
$this->session->sendDataPacket(MobEquipmentPacket::create( $this->session->sendDataPacket(MobEquipmentPacket::create(
$this->player->getId(), $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,
$selected, $selected,
ContainerIds::INVENTORY ContainerIds::INVENTORY

View File

@ -103,12 +103,12 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
public function onMobMainHandItemChange(array $recipients, Human $mob) : void{ 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 //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( $this->sendDataPacket($recipients, MobEquipmentPacket::create(
$mob->getId(), $mob->getId(),
ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getItemInHand())), ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getHeldItem())),
$inv->getHeldItemIndex(), $inv->getSelectedIndex(),
$inv->getHeldItemIndex(), $inv->getSelectedIndex(),
ContainerIds::INVENTORY ContainerIds::INVENTORY
)); ));
} }

View File

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

View File

@ -347,7 +347,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
private function callDummyItemHeldEvent() : void{ private function callDummyItemHeldEvent() : void{
$slot = $this->inventory->getHeldItemIndex(); $slot = $this->hotbar->getSelectedIndex();
$event = new PlayerItemHeldEvent($this, $this->inventory->getItem($slot), $slot); $event = new PlayerItemHeldEvent($this, $this->inventory->getItem($slot), $slot);
$event->call(); $event->call();
@ -362,7 +362,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->inventory->getListeners()->add(new CallbackInventoryListener( $this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot) : void{ function(Inventory $unused, int $slot) : void{
if($slot === $this->inventory->getHeldItemIndex()){ if($slot === $this->hotbar->getSelectedIndex()){
$this->setUsingItem(false); $this->setUsingItem(false);
$this->callDummyItemHeldEvent(); $this->callDummyItemHeldEvent();
@ -1540,10 +1540,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
public function selectHotbarSlot(int $hotbarSlot) : bool{ public function selectHotbarSlot(int $hotbarSlot) : bool{
if(!$this->inventory->isHotbarSlot($hotbarSlot)){ //TODO: exception here? if(!$this->hotbar->isHotbarSlot($hotbarSlot)){ //TODO: exception here?
return false; return false;
} }
if($hotbarSlot === $this->inventory->getHeldItemIndex()){ if($hotbarSlot === $this->hotbar->getSelectedIndex()){
return true; return true;
} }
@ -1553,7 +1553,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return false; return false;
} }
$this->inventory->setHeldItemIndex($hotbarSlot); $this->hotbar->setSelectedIndex($hotbarSlot);
$this->setUsingItem(false); $this->setUsingItem(false);
return true; return true;
@ -1565,7 +1565,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, array $extraReturnedItems) : void{ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, array $extraReturnedItems) : void{
$heldItemChanged = false; $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 //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 //if it was really changed we always need to set it, whether we have finite resources or not
$newReplica = clone $oldHeldItem; $newReplica = clone $oldHeldItem;
@ -1579,7 +1579,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($newHeldItem instanceof Durable && $newHeldItem->isBroken()){ if($newHeldItem instanceof Durable && $newHeldItem->isBroken()){
$this->broadcastSound(new ItemBreakSound()); $this->broadcastSound(new ItemBreakSound());
} }
$this->inventory->setItemInHand($newHeldItem); $this->hotbar->setHeldItem($newHeldItem);
$heldItemChanged = true; $heldItemChanged = true;
} }
} }
@ -1589,7 +1589,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
if($heldItemChanged && count($extraReturnedItems) > 0 && $newHeldItem->isNull()){ 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){ 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 :( //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{ public function useHeldItem() : bool{
$directionVector = $this->getDirectionVector(); $directionVector = $this->getDirectionVector();
$item = $this->inventory->getItemInHand(); $item = $this->hotbar->getHeldItem();
$oldItem = clone $item; $oldItem = clone $item;
$ev = new PlayerItemUseEvent($this, $item, $directionVector); $ev = new PlayerItemUseEvent($this, $item, $directionVector);
@ -1645,7 +1645,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @return bool if the consumption succeeded. * @return bool if the consumption succeeded.
*/ */
public function consumeHeldItem() : bool{ public function consumeHeldItem() : bool{
$slot = $this->inventory->getItemInHand(); $slot = $this->hotbar->getHeldItem();
if($slot instanceof ConsumableItem){ if($slot instanceof ConsumableItem){
$oldItem = clone $slot; $oldItem = clone $slot;
@ -1678,7 +1678,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
*/ */
public function releaseHeldItem() : bool{ public function releaseHeldItem() : bool{
try{ try{
$item = $this->inventory->getItemInHand(); $item = $this->hotbar->getHeldItem();
if(!$this->isUsingItem() || $this->hasItemCooldown($item)){ if(!$this->isUsingItem() || $this->hasItemCooldown($item)){
return false; return false;
} }
@ -1748,21 +1748,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{ private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{
if($existingSlot !== -1){ if($existingSlot !== -1){
if($existingSlot < $this->inventory->getHotbarSize()){ if($existingSlot < $this->hotbar->getSize()){
$this->inventory->setHeldItemIndex($existingSlot); $this->hotbar->setSelectedIndex($existingSlot);
}else{ }else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot); $this->inventory->swap($this->hotbar->getSelectedIndex(), $existingSlot);
} }
}else{ }else{
$firstEmpty = $this->inventory->firstEmpty(); $firstEmpty = $this->inventory->firstEmpty();
if($firstEmpty === -1){ //full inventory if($firstEmpty === -1){ //full inventory
$this->inventory->setItemInHand($item); $this->hotbar->setHeldItem($item);
}elseif($firstEmpty < $this->inventory->getHotbarSize()){ }elseif($firstEmpty < $this->hotbar->getSize()){
$this->inventory->setItem($firstEmpty, $item); $this->inventory->setItem($firstEmpty, $item);
$this->inventory->setHeldItemIndex($firstEmpty); $this->hotbar->setSelectedIndex($firstEmpty);
}else{ }else{
$this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty); $this->inventory->swap($this->hotbar->getSelectedIndex(), $firstEmpty);
$this->inventory->setItemInHand($item); $this->hotbar->setHeldItem($item);
} }
} }
} }
@ -1779,7 +1779,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$target = $this->getWorld()->getBlock($pos); $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()){ if($this->isSpectator()){
$ev->cancel(); $ev->cancel();
} }
@ -1788,7 +1788,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return false; return false;
} }
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); $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; 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)){ 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->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
$this->stopBreakBlock($pos); $this->stopBreakBlock($pos);
$item = $this->inventory->getItemInHand(); $item = $this->hotbar->getHeldItem();
$oldItem = clone $item; $oldItem = clone $item;
$returnedItems = []; $returnedItems = [];
if($this->getWorld()->useBreakOn($pos, $item, $this, true, $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)){ 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->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; $oldItem = clone $item;
$returnedItems = []; $returnedItems = [];
if($this->getWorld()->useItemOn($pos, $item, $face, $clickOffset, $this, true, $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; return false;
} }
$heldItem = $this->inventory->getItemInHand(); $heldItem = $this->hotbar->getHeldItem();
$oldItem = clone $heldItem; $oldItem = clone $heldItem;
$ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints()); $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(); $ev->call();
$item = $this->inventory->getItemInHand(); $item = $this->hotbar->getHeldItem();
$oldItem = clone $item; $oldItem = clone $item;
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
if($item->onInteractEntity($this, $entity, $clickPos)){ 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()){ if($item instanceof Durable && $item->isBroken()){
$this->broadcastSound(new ItemBreakSound()); $this->broadcastSound(new ItemBreakSound());
} }
$this->inventory->setItemInHand($item); $this->hotbar->setHeldItem($item);
} }
} }
return $entity->onInteract($this, $clickPos); return $entity->onInteract($this, $clickPos);
@ -2405,8 +2405,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->getWorld()->dropItem($this->location, $item); $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())); $clearInventory = fn(Inventory $inventory) => $inventory->setContents(array_filter($inventory->getContents(), fn(Item $item) => $item->keepOnDeath()));
$this->inventory->setHeldItemIndex(0);
$clearInventory($this->inventory); $clearInventory($this->inventory);
$clearInventory($this->armorInventory); $clearInventory($this->armorInventory);
$clearInventory($this->offHandInventory); $clearInventory($this->offHandInventory);

View File

@ -66,7 +66,7 @@ final class SurvivalBlockBreakHandler{
return 0.0; 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) //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){ if($breakTimePerTick > 0){
return 1 / $breakTimePerTick; return 1 / $breakTimePerTick;