Consumables refactor (#1796)

* Removed broken EntityEatEvents - these don't fit the pattern since they only apply to Human entities anyway. PlayerItemConsumeEvent and PlayerInteractEvent can be used for cancellation purposes, and plugins can do custom stuff without mess.

* Restrict item consuming to Living entities only

* Added FoodSource->requiresHunger()

* Only items implementing the Consumable interface can now be consumed.

* The effects from consuming items are now generic-ized by way of the Living->consume() function. This is overridden in Human to allow applying food and hunger.

* Fixed the hardcoded mess for buckets
This commit is contained in:
Dylan K. Taylor 2017-12-23 13:03:41 +00:00 committed by GitHub
parent 329fe7d844
commit 6e1df36188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 113 additions and 306 deletions

View File

@ -78,6 +78,7 @@ use pocketmine\inventory\PlayerInventory;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Consumable;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\WritableBook;
@ -2448,52 +2449,40 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->inventory->setItemInHand($item);
}
}else{
$this->inventory->sendContents($this);
break;
}
return true;
case InventoryTransactionPacket::RELEASE_ITEM_ACTION_CONSUME:
$slot = $this->inventory->getItemInHand();
if($slot->canBeConsumed()){
if($slot instanceof Consumable){
$ev = new PlayerItemConsumeEvent($this, $slot);
if(!$slot->canBeConsumedBy($this)){
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if(!$ev->isCancelled()){
$slot->onConsume($this);
}else{
$this->inventory->sendContents($this);
}
return true;
}elseif($slot->getId() === Item::BUCKET and $slot->getDamage() === 1){ //Milk!
$this->server->getPluginManager()->callEvent($ev = new PlayerItemConsumeEvent($this, $slot));
if($ev->isCancelled()){
if($ev->isCancelled() or !$this->consumeObject($slot)){
$this->inventory->sendContents($this);
return true;
}
if($this->isSurvival()){
--$slot->count;
$slot->pop();
$this->inventory->setItemInHand($slot);
$this->inventory->addItem(ItemFactory::get(Item::BUCKET, 0, 1));
$this->inventory->addItem($slot->getResidue());
}
$this->removeAllEffects();
return true;
}
return false;
break;
default:
break;
}
}finally{
$this->setUsingItem(false);
}
$this->inventory->sendContents($this);
break;
default:
$this->inventory->sendContents($this);

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Effect;
use pocketmine\event\entity\EntityEatBlockEvent;
use pocketmine\entity\Living;
use pocketmine\item\FoodSource;
use pocketmine\item\Item;
use pocketmine\level\Level;
@ -90,20 +90,9 @@ class Cake extends Transparent implements FoodSource{
}
public function onActivate(Item $item, Player $player = null) : bool{
//TODO: refactor this into generic food handling
if($player instanceof Player and $player->getFood() < $player->getMaxFood()){
$player->getServer()->getPluginManager()->callEvent($ev = new EntityEatBlockEvent($player, $this));
if(!$ev->isCancelled()){
$player->addFood($ev->getFoodRestore());
$player->addSaturation($ev->getSaturationRestore());
foreach($ev->getAdditionalEffects() as $effect){
$player->addEffect($effect);
}
$this->getLevel()->setBlock($this, $ev->getResidue());
return true;
}
if($player !== null){
$player->consumeObject($this);
return true;
}
return false;
@ -117,6 +106,13 @@ class Cake extends Transparent implements FoodSource{
return 0.4;
}
public function requiresHunger() : bool{
return true;
}
/**
* @return Block
*/
public function getResidue(){
$clone = clone $this;
$clone->meta++;
@ -132,4 +128,8 @@ class Cake extends Transparent implements FoodSource{
public function getAdditionalEffects() : array{
return [];
}
public function onConsume(Living $consumer) : void{
$this->level->setBlock($this, $this->getResidue());
}
}

View File

@ -30,7 +30,9 @@ use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\inventory\EnderChestInventory;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerInventory;
use pocketmine\item\Consumable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FoodSource;
use pocketmine\item\Item as ItemItem;
use pocketmine\level\Level;
use pocketmine\nbt\NBT;
@ -271,6 +273,19 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
return $ev->getAmount();
}
public function consumeObject(Consumable $consumable) : bool{
if($consumable instanceof FoodSource){
if($consumable->requiresHunger() and $this->getFood() >= $this->getMaxFood()){
return false;
}
$this->addFood($consumable->getFoodRestore());
$this->addSaturation($consumable->getSaturationRestore());
}
return parent::consumeObject($consumable);
}
public function getXpLevel() : int{
return (int) $this->attributeMap->getAttribute(Attribute::EXPERIENCE_LEVEL)->getValue();
}

View File

@ -32,6 +32,7 @@ use pocketmine\event\entity\EntityEffectAddEvent;
use pocketmine\event\entity\EntityEffectRemoveEvent;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\Timings;
use pocketmine\item\Consumable;
use pocketmine\item\Item as ItemItem;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
@ -322,6 +323,23 @@ abstract class Living extends Entity implements Damageable{
}
}
/**
* Causes the mob to consume the given Consumable object, applying applicable effects, health bonuses, food bonuses,
* etc.
*
* @param Consumable $consumable
*
* @return bool
*/
public function consumeObject(Consumable $consumable) : bool{
foreach($consumable->getAdditionalEffects() as $effect){
$this->addEffect($effect);
}
$consumable->onConsume($this);
return true;
}
/**
* Returns the initial upwards velocity of a jumping entity in blocks/tick, including additional velocity due to effects.

View File

@ -1,54 +0,0 @@
<?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\event\entity;
use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\item\FoodSource;
class EntityEatBlockEvent extends EntityEatEvent{
public function __construct(Entity $entity, FoodSource $foodSource){
if(!($foodSource instanceof Block)){
throw new \InvalidArgumentException("Food source must be a block");
}
parent::__construct($entity, $foodSource);
}
/**
* @return Block
*/
public function getResidue(){
return parent::getResidue();
}
/**
* @param Block $residue
*/
public function setResidue($residue){
if(!($residue instanceof Block)){
throw new \InvalidArgumentException("Eating a Block can only result in a Block residue");
}
parent::setResidue($residue);
}
}

View File

@ -1,109 +0,0 @@
<?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\event\entity;
use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\item\FoodSource;
class EntityEatEvent extends EntityEvent implements Cancellable{
public static $handlerList = null;
/** @var FoodSource */
private $foodSource;
/** @var int */
private $foodRestore;
/** @var float */
private $saturationRestore;
/** @var mixed */
private $residue;
/** @var Effect[] */
private $additionalEffects;
public function __construct(Entity $entity, FoodSource $foodSource){
$this->entity = $entity;
$this->foodSource = $foodSource;
$this->foodRestore = $foodSource->getFoodRestore();
$this->saturationRestore = $foodSource->getSaturationRestore();
$this->residue = $foodSource->getResidue();
$this->additionalEffects = $foodSource->getAdditionalEffects();
}
public function getFoodSource() : FoodSource{
return $this->foodSource;
}
public function getFoodRestore() : int{
return $this->foodRestore;
}
public function setFoodRestore(int $foodRestore){
$this->foodRestore = $foodRestore;
}
public function getSaturationRestore() : float{
return $this->saturationRestore;
}
public function setSaturationRestore(float $saturationRestore){
$this->saturationRestore = $saturationRestore;
}
/**
* Returns the result of eating the food source.
* @return mixed
*/
public function getResidue(){
return $this->residue;
}
/**
* @param mixed $residue
*/
public function setResidue($residue){
$this->residue = $residue;
}
/**
* @return Effect[]
*/
public function getAdditionalEffects() : array{
return $this->additionalEffects;
}
/**
* @param Effect[] $additionalEffects
*
* @throws \TypeError
*/
public function setAdditionalEffects(array $additionalEffects){
foreach($additionalEffects as $effect){
if(!($effect instanceof Effect)){
throw new \TypeError("Argument 1 passed to EntityEatEvent::setAdditionalEffects() must be an Effect array");
}
}
$this->additionalEffects = $additionalEffects;
}
}

View File

@ -1,51 +0,0 @@
<?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\event\entity;
use pocketmine\entity\Entity;
use pocketmine\item\Food;
use pocketmine\item\Item;
class EntityEatItemEvent extends EntityEatEvent{
public function __construct(Entity $entity, Food $foodSource){
parent::__construct($entity, $foodSource);
}
/**
* @return Item
*/
public function getResidue(){
return parent::getResidue();
}
/**
* @param Item $residue
*/
public function setResidue($residue){
if(!($residue instanceof Item)){
throw new \InvalidArgumentException("Eating an Item can only result in an Item residue");
}
parent::setResidue($residue);
}
}

View File

@ -27,13 +27,14 @@ use pocketmine\block\Air;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\block\Liquid;
use pocketmine\entity\Living;
use pocketmine\event\player\PlayerBucketEmptyEvent;
use pocketmine\event\player\PlayerBucketFillEvent;
use pocketmine\level\Level;
use pocketmine\math\Vector3;
use pocketmine\Player;
class Bucket extends Item{
class Bucket extends Item implements Consumable{
public function __construct(int $meta = 0){
parent::__construct(self::BUCKET, $meta, "Bucket");
}
@ -85,4 +86,20 @@ class Bucket extends Item{
return false;
}
public function getResidue(){
return ItemFactory::get(Item::BUCKET, 0, 1);
}
public function getAdditionalEffects() : array{
return [];
}
public function canBeConsumed() : bool{
return $this->meta === 1; //Milk
}
public function onConsume(Living $consumer){
$consumer->removeAllEffects();
}
}

View File

@ -23,14 +23,32 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\Block;
use pocketmine\entity\Effect;
use pocketmine\entity\Living;
/**
* Interface implemented by objects that can be consumed by mobs.
*/
interface Consumable{
/**
* Returns the leftover that this Consumable produces when it is consumed. For Items, this is usually air, but could
* be an Item to add to a Player's inventory afterwards (such as a bowl).
*
* @return Item|Block|mixed
*/
public function getResidue();
/**
* @return Effect[]
*/
public function getAdditionalEffects() : array;
/**
* Called when this Consumable is consumed by mob, after standard resulting effects have been applied.
*
* @param Living $consumer
*/
public function onConsume(Living $consumer);
}

View File

@ -23,42 +23,25 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\event\entity\EntityEatItemEvent;
use pocketmine\entity\Living;
abstract class Food extends Item implements FoodSource{
public function canBeConsumed() : bool{
public function requiresHunger() : bool{
return true;
}
public function canBeConsumedBy(Entity $entity) : bool{
return $entity instanceof Human and $entity->getFood() < $entity->getMaxFood();
}
/**
* @return Item
*/
public function getResidue(){
if($this->getCount() === 1){
return ItemFactory::get(0);
}else{
$new = clone $this;
$new->count--;
return $new;
}
return ItemFactory::get(Item::AIR, 0, 0);
}
public function getAdditionalEffects() : array{
return [];
}
public function onConsume(Entity $human){
$ev = new EntityEatItemEvent($human, $this);
public function onConsume(Living $consumer){
$human->addSaturation($ev->getSaturationRestore());
$human->addFood($ev->getFoodRestore());
foreach($ev->getAdditionalEffects() as $effect){
$human->addEffect($effect);
}
$human->getInventory()->setItemInHand($ev->getResidue());
}
}

View File

@ -23,9 +23,18 @@ declare(strict_types=1);
namespace pocketmine\item;
/**
* Interface implemented by objects that can be consumed by players, giving them food and saturation.
*/
interface FoodSource extends Consumable{
public function getFoodRestore() : int;
public function getSaturationRestore() : float;
/**
* Returns whether a Human eating this FoodSource must have a non-full hunger bar.
* @return bool
*/
public function requiresHunger() : bool;
}

View File

@ -24,8 +24,6 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
class GoldenApple extends Food{
@ -33,8 +31,8 @@ class GoldenApple extends Food{
parent::__construct(self::GOLDEN_APPLE, $meta, "Golden Apple");
}
public function canBeConsumedBy(Entity $entity) : bool{
return $entity instanceof Human;
public function requiresHunger() : bool{
return false;
}
public function getFoodRestore() : int{

View File

@ -621,32 +621,6 @@ class Item implements ItemIds, \JsonSerializable{
return $this->block !== null and $this->block->canBePlaced();
}
/**
* Returns whether an entity can eat or drink this item.
* @return bool
*/
public function canBeConsumed() : bool{
return false;
}
/**
* Returns whether this item can be consumed by the supplied Entity.
* @param Entity $entity
*
* @return bool
*/
public function canBeConsumedBy(Entity $entity) : bool{
return $this->canBeConsumed();
}
/**
* Called when the item is consumed by an Entity.
* @param Entity $entity
*/
public function onConsume(Entity $entity){
}
/**
* Returns the block corresponding to this Item.
* @return Block

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
class Potion extends Item{
public function __construct(int $meta = 0){
@ -38,7 +38,7 @@ class Potion extends Item{
return 1;
}
public function onConsume(Entity $entity){
public function onConsume(Living $consumer){
// TODO: Implement potions
}
}