PocketMine-MP/src/pocketmine/inventory/PlayerInventory.php
2017-03-18 16:29:38 +00:00

545 lines
15 KiB
PHP

<?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/
*
*
*/
namespace pocketmine\inventory;
use pocketmine\entity\Human;
use pocketmine\event\entity\EntityArmorChangeEvent;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\event\player\PlayerItemHeldEvent;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\Player;
use pocketmine\Server;
class PlayerInventory extends BaseInventory{
protected $itemInHandIndex = 0;
/** @var int[] */
protected $hotbar;
public function __construct(Human $player){
$this->resetHotbar(false);
parent::__construct($player, InventoryType::get(InventoryType::PLAYER));
}
public function getSize(){
return parent::getSize() - 4; //Remove armor slots
}
public function setSize($size){
parent::setSize($size + 4);
$this->sendContents($this->getViewers());
}
/**
* Called when a client equips a hotbar slot. This method should not be used by plugins.
* This method will call PlayerItemHeldEvent.
*
* @param int $hotbarSlot Number of the hotbar slot to equip.
* @param int|null $inventorySlot Inventory slot to map to the specified hotbar slot. Supply null to make no change to the link.
*
* @return bool if the equipment change was successful, false if not.
*/
public function equipItem(int $hotbarSlot, $inventorySlot = null) : bool{
if($inventorySlot === null){
$inventorySlot = $this->getHotbarSlotIndex($this->getHeldItemIndex());
}
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){
$this->sendContents($this->getHolder());
return false;
}
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($inventorySlot), $inventorySlot, $hotbarSlot));
if($ev->isCancelled()){
$this->sendContents($this->getHolder());
return false;
}
$this->setHotbarSlotIndex($hotbarSlot, $inventorySlot);
$this->setHeldItemIndex($hotbarSlot, false);
return true;
}
/**
* Returns the index of the inventory slot mapped to the specified hotbar slot, or -1 if the hotbar slot does not exist.
* @param int $index
*
* @return int
*/
public function getHotbarSlotIndex($index){
return $this->hotbar[$index] ?? -1;
}
/**
* Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot.
* This method is intended for use in network interaction with clients only.
*
* NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
*
* @param int $hotbarSlot
* @param int $inventorySlot
*/
public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){
if($hotbarSlot >= 0 and $hotbarSlot < $this->getHotbarSize() and $inventorySlot >= -1 and $inventorySlot < $this->getSize()){
if($inventorySlot !== -1 and ($alreadyEquippedIndex = array_search($inventorySlot, $this->hotbar)) !== false){
/* Swap the slots
* This assumes that the equipped slot can only be equipped in one other slot
* it will not account for ancient bugs where the same slot ended up linked to several hotbar slots.
* Such bugs will require a hotbar reset to default.
*/
$this->hotbar[$alreadyEquippedIndex] = $this->hotbar[$hotbarSlot];
}
$this->hotbar[$hotbarSlot] = $inventorySlot;
}
}
/**
* Resets hotbar links to their original defaults.
* @param bool $send Whether to send changes to the holder.
*/
public function resetHotbar(bool $send = true){
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
if($send){
$this->sendContents($this->getHolder());
}
}
/**
* Returns the hotbar slot number the holder is currently holding.
* @return int
*/
public function getHeldItemIndex(){
return $this->itemInHandIndex;
}
/**
* Sets which hotbar slot the player is currently loading.
*
* @param int $index 0-8 index of the hotbar slot to hold
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
* It should only be false to prevent feedback loops of equipment packets between client and server.
*/
public function setHeldItemIndex($index, $send = true){
if($index >= 0 and $index < $this->getHotbarSize()){
$this->itemInHandIndex = $index;
if($this->getHolder() instanceof Player and $send){
$this->sendHeldItem($this->getHolder());
}
$this->sendHeldItem($this->getHolder()->getViewers());
}
}
/**
* Returns the currently-held item.
*
* @return Item
*/
public function getItemInHand(){
$item = $this->getItem($this->getHeldItemSlot());
if($item instanceof Item){
return $item;
}else{
return Item::get(Item::AIR, 0, 0);
}
}
/**
* Sets the item in the currently-held slot to the specified item.
* @param Item $item
*
* @return bool
*/
public function setItemInHand(Item $item){
return $this->setItem($this->getHeldItemSlot(), $item);
}
/**
* Returns the hotbar slot number currently held.
*
* @return int
*/
public function getHeldItemSlot(){
return $this->getHotbarSlotIndex($this->itemInHandIndex);
}
/**
* Sets the hotbar slot link of the currently-held hotbar slot.
* @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
*
* @param int $slot
*/
public function setHeldItemSlot($slot){
if($slot >= -1 and $slot < $this->getSize()){
$this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot);
}
}
/**
* Sends the currently-held item to specified targets.
* @param Player|Player[] $target
*/
public function sendHeldItem($target){
$item = $this->getItemInHand();
$pk = new MobEquipmentPacket();
$pk->eid = $this->getHolder()->getId();
$pk->item = $item;
$pk->inventorySlot = $this->getHeldItemSlot();
$pk->hotbarSlot = $this->getHeldItemIndex();
if(!is_array($target)){
$target->dataPacket($pk);
if($target === $this->getHolder()){
$this->sendSlot($this->getHeldItemSlot(), $target);
}
}else{
$this->getHolder()->getLevel()->getServer()->broadcastPacket($target, $pk);
foreach($target as $player){
if($player === $this->getHolder()){
$this->sendSlot($this->getHeldItemSlot(), $player);
break;
}
}
}
}
public function onSlotChange($index, $before){
$holder = $this->getHolder();
if($holder instanceof Player and !$holder->spawned){
return;
}
parent::onSlotChange($index, $before);
if($index >= $this->getSize()){
$this->sendArmorSlot($index, $this->getViewers());
$this->sendArmorSlot($index, $this->getHolder()->getViewers());
}
}
/**
* Returns the number of slots in the hotbar.
* @return int
*/
public function getHotbarSize(){
return 9;
}
public function getArmorItem($index){
return $this->getItem($this->getSize() + $index);
}
public function setArmorItem($index, Item $item){
return $this->setItem($this->getSize() + $index, $item);
}
public function getHelmet(){
return $this->getItem($this->getSize());
}
public function getChestplate(){
return $this->getItem($this->getSize() + 1);
}
public function getLeggings(){
return $this->getItem($this->getSize() + 2);
}
public function getBoots(){
return $this->getItem($this->getSize() + 3);
}
public function setHelmet(Item $helmet){
return $this->setItem($this->getSize(), $helmet);
}
public function setChestplate(Item $chestplate){
return $this->setItem($this->getSize() + 1, $chestplate);
}
public function setLeggings(Item $leggings){
return $this->setItem($this->getSize() + 2, $leggings);
}
public function setBoots(Item $boots){
return $this->setItem($this->getSize() + 3, $boots);
}
public function setItem($index, Item $item){
if($index < 0 or $index >= $this->size){
return false;
}elseif($item->getId() === 0 or $item->getCount() <= 0){
return $this->clear($index);
}
if($index >= $this->getSize()){ //Armor change
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
if($ev->isCancelled() and $this->getHolder() instanceof Human){
$this->sendArmorSlot($index, $this->getViewers());
return false;
}
$item = $ev->getNewItem();
}else{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
if($ev->isCancelled()){
$this->sendSlot($index, $this->getViewers());
return false;
}
$item = $ev->getNewItem();
}
$old = $this->getItem($index);
$this->slots[$index] = clone $item;
$this->onSlotChange($index, $old);
return true;
}
public function clear($index){
if(isset($this->slots[$index])){
$item = Item::get(Item::AIR, 0, 0);
$old = $this->slots[$index];
if($index >= $this->getSize() and $index < $this->size){ //Armor change
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $old, $item, $index));
if($ev->isCancelled()){
if($index >= $this->size){
$this->sendArmorSlot($index, $this->getViewers());
}else{
$this->sendSlot($index, $this->getViewers());
}
return false;
}
$item = $ev->getNewItem();
}else{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $old, $item, $index));
if($ev->isCancelled()){
if($index >= $this->size){
$this->sendArmorSlot($index, $this->getViewers());
}else{
$this->sendSlot($index, $this->getViewers());
}
return false;
}
$item = $ev->getNewItem();
}
if($item->getId() !== Item::AIR){
$this->slots[$index] = clone $item;
}else{
unset($this->slots[$index]);
}
$this->onSlotChange($index, $old);
}
return true;
}
/**
* @return Item[]
*/
public function getArmorContents(){
$armor = [];
for($i = 0; $i < 4; ++$i){
$armor[$i] = $this->getItem($this->getSize() + $i);
}
return $armor;
}
public function clearAll(){
$limit = $this->getSize() + 4;
for($index = 0; $index < $limit; ++$index){
$this->clear($index);
}
}
/**
* @param Player|Player[] $target
*/
public function sendArmorContents($target){
if($target instanceof Player){
$target = [$target];
}
$armor = $this->getArmorContents();
$pk = new MobArmorEquipmentPacket();
$pk->eid = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->encode();
$pk->isEncoded = true;
foreach($target as $player){
if($player === $this->getHolder()){
$pk2 = new ContainerSetContentPacket();
$pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR;
$pk2->slots = $armor;
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);
}
}
}
/**
* @param Item[] $items
*/
public function setArmorContents(array $items){
for($i = 0; $i < 4; ++$i){
if(!isset($items[$i]) or !($items[$i] instanceof Item)){
$items[$i] = Item::get(Item::AIR, 0, 0);
}
if($items[$i]->getId() === Item::AIR){
$this->clear($this->getSize() + $i);
}else{
$this->setItem($this->getSize() + $i, $items[$i]);
}
}
}
/**
* @param int $index
* @param Player|Player[] $target
*/
public function sendArmorSlot($index, $target){
if($target instanceof Player){
$target = [$target];
}
$armor = $this->getArmorContents();
$pk = new MobArmorEquipmentPacket();
$pk->eid = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->encode();
$pk->isEncoded = true;
foreach($target as $player){
if($player === $this->getHolder()){
/** @var Player $player */
$pk2 = new ContainerSetSlotPacket();
$pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR;
$pk2->slot = $index - $this->getSize();
$pk2->item = $this->getItem($index);
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);
}
}
}
/**
* @param Player|Player[] $target
*/
public function sendContents($target){
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetContentPacket();
$pk->slots = [];
for($i = 0; $i < $this->getSize(); ++$i){ //Do not send armor by error here
$pk->slots[$i] = $this->getItem($i);
}
//Because PE is stupid and shows 9 less slots than you send it, give it 9 dummy slots so it shows all the REAL slots.
for($i = $this->getSize(); $i < $this->getSize() + $this->getHotbarSize(); ++$i){
$pk->slots[$i] = Item::get(Item::AIR, 0, 0);
}
foreach($target as $player){
$pk->hotbar = [];
if($player === $this->getHolder()){
for($i = 0; $i < $this->getHotbarSize(); ++$i){
$index = $this->getHotbarSlotIndex($i);
$pk->hotbar[] = $index <= -1 ? -1 : $index + $this->getHotbarSize();
}
}
if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){
$this->close($player);
continue;
}
$pk->windowid = $id;
$player->dataPacket(clone $pk);
}
}
public function sendCreativeContents(){
$pk = new ContainerSetContentPacket();
$pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE;
if($this->getHolder()->getGamemode() === Player::CREATIVE){
foreach(Item::getCreativeItems() as $i => $item){
$pk->slots[$i] = clone $item;
}
}
$this->getHolder()->dataPacket($pk);
}
/**
* @param int $index
* @param Player|Player[] $target
*/
public function sendSlot($index, $target){
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetSlotPacket();
$pk->slot = $index;
$pk->item = clone $this->getItem($index);
foreach($target as $player){
if($player === $this->getHolder()){
/** @var Player $player */
$pk->windowid = 0;
$player->dataPacket(clone $pk);
}else{
if(($id = $player->getWindowId($this)) === -1){
$this->close($player);
continue;
}
$pk->windowid = $id;
$player->dataPacket(clone $pk);
}
}
}
/**
* @return Human|Player
*/
public function getHolder(){
return parent::getHolder();
}
}