Merge branch 'minor-next' into major-next

This commit is contained in:
Dylan K. Taylor 2023-08-15 17:41:50 +01:00
commit 8c594fd126
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
40 changed files with 1947 additions and 152 deletions

@ -1 +1 @@
Subproject commit ed0bc4d2afafd00f9ee92823c6b1bd66789ce4f2
Subproject commit a053f65e1897e432478229071383fe1ba16032c3

39
composer.lock generated
View File

@ -1211,16 +1211,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.16.0",
"version": "v4.17.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"shasum": ""
},
"require": {
@ -1261,9 +1261,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
},
"time": "2023-06-25T14:52:30+00:00"
"time": "2023-08-13T19:53:39+00:00"
},
{
"name": "phar-io/manifest",
@ -1861,16 +1861,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.3.1",
"version": "10.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804"
"reference": "0dafb1175c366dd274eaa9a625e914451506bcd1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d442ce7c4104d5683c12e67e4dcb5058159e9804",
"reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0dafb1175c366dd274eaa9a625e914451506bcd1",
"reference": "0dafb1175c366dd274eaa9a625e914451506bcd1",
"shasum": ""
},
"require": {
@ -1942,7 +1942,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.1"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.2"
},
"funding": [
{
@ -1958,7 +1958,7 @@
"type": "tidelift"
}
],
"time": "2023-08-04T06:48:08+00:00"
"time": "2023-08-15T05:34:23+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2129,16 +2129,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.0",
"version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "72f01e6586e0caf6af81297897bd112eb7e9627c"
"reference": "2db5010a484d53ebf536087a70b4a5423c102372"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/72f01e6586e0caf6af81297897bd112eb7e9627c",
"reference": "72f01e6586e0caf6af81297897bd112eb7e9627c",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372",
"reference": "2db5010a484d53ebf536087a70b4a5423c102372",
"shasum": ""
},
"require": {
@ -2149,7 +2149,7 @@
"sebastian/exporter": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
"phpunit/phpunit": "^10.3"
},
"type": "library",
"extra": {
@ -2193,7 +2193,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.0"
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1"
},
"funding": [
{
@ -2201,7 +2202,7 @@
"type": "github"
}
],
"time": "2023-02-03T07:07:16+00:00"
"time": "2023-08-14T13:18:12+00:00"
},
{
"name": "sebastian/complexity",

View File

@ -36,6 +36,9 @@ use pocketmine\data\runtime\RuntimeDataSizeCalculator;
use pocketmine\data\runtime\RuntimeDataWriter;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\enchantment\AvailableEnchantmentRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTagRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTags;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemBlock;
@ -422,6 +425,19 @@ class Block{
return $this->typeInfo->getBreakInfo();
}
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->typeInfo->getEnchantmentTags();
}
/**
* Do the actions needed so the block is broken with the Item
*

View File

@ -35,10 +35,12 @@ final class BlockTypeInfo{
/**
* @param string[] $typeTags
* @param string[] $enchantmentTags
*/
public function __construct(
private BlockBreakInfo $breakInfo,
array $typeTags = []
array $typeTags = [],
private array $enchantmentTags = []
){
$this->typeTags = array_fill_keys($typeTags, true);
}
@ -49,4 +51,17 @@ final class BlockTypeInfo{
public function getTypeTags() : array{ return array_keys($this->typeTags); }
public function hasTypeTag(string $tag) : bool{ return isset($this->typeTags[$tag]); }
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->enchantmentTags;
}
}

View File

@ -83,6 +83,10 @@ class Cake extends BaseCake{
return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
public function getResidue() : Block{
$clone = clone $this;
$clone->bites++;

View File

@ -63,6 +63,14 @@ final class PotionCauldron extends FillableCauldron{
/** @return $this */
public function setPotionItem(?Item $potionItem) : self{
if($potionItem !== null && !match($potionItem->getTypeId()){
ItemTypeIds::POTION,
ItemTypeIds::SPLASH_POTION,
ItemTypeIds::LINGERING_POTION => true,
default => false,
}){
throw new \InvalidArgumentException("Item must be a POTION, SPLASH_POTION or LINGERING_POTION");
}
$this->potionItem = $potionItem !== null ? (clone $potionItem)->setCount(1) : null;
return $this;
}

View File

@ -59,6 +59,7 @@ use pocketmine\block\utils\SaplingType;
use pocketmine\block\utils\WoodType;
use pocketmine\crafting\FurnaceType;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\Item;
use pocketmine\item\ToolTier;
use pocketmine\math\Facing;
@ -966,7 +967,7 @@ final class VanillaBlocks{
$pumpkinBreakInfo = new Info(BreakInfo::axe(1.0));
self::register("pumpkin", new Pumpkin(new BID(Ids::PUMPKIN), "Pumpkin", $pumpkinBreakInfo));
self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", $pumpkinBreakInfo));
self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK])));
self::register("lit_pumpkin", new LitPumpkin(new BID(Ids::LIT_PUMPKIN), "Jack o'Lantern", $pumpkinBreakInfo));
self::register("pumpkin_stem", new PumpkinStem(new BID(Ids::PUMPKIN_STEM), "Pumpkin Stem", new Info(BreakInfo::instant())));
@ -1002,7 +1003,7 @@ final class VanillaBlocks{
self::register("sea_lantern", new SeaLantern(new BID(Ids::SEA_LANTERN), "Sea Lantern", new Info(new BreakInfo(0.3))));
self::register("sea_pickle", new SeaPickle(new BID(Ids::SEA_PICKLE), "Sea Pickle", new Info(BreakInfo::instant())));
self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0))));
self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0), enchantmentTags: [EnchantmentTags::MASK])));
self::register("slime", new Slime(new BID(Ids::SLIME), "Slime Block", new Info(BreakInfo::instant())));
self::register("snow", new Snow(new BID(Ids::SNOW), "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD()))));
self::register("snow_layer", new SnowLayer(new BID(Ids::SNOW_LAYER), "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD()))));

View File

@ -23,9 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\event\player\PlayerEnchantOptionsRequestEvent;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\item\enchantment\EnchantmentHelper as Helper;
use pocketmine\item\enchantment\EnchantOption;
use pocketmine\item\Item;
use pocketmine\world\Position;
use function array_values;
use function count;
class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
@ -33,8 +39,47 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantOption[] $options */
private array $options = [];
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}
protected function onSlotChange(int $index, Item $before) : void{
if($index === self::SLOT_INPUT){
foreach($this->viewers as $viewer){
$this->options = [];
$item = $this->getInput();
$options = Helper::getEnchantOptions($this->holder, $item, $viewer->getEnchantmentSeed());
$event = new PlayerEnchantOptionsRequestEvent($viewer, $this, $options);
$event->call();
if(!$event->isCancelled() && count($event->getOptions()) > 0){
$this->options = array_values($event->getOptions());
$viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options);
}
}
}
parent::onSlotChange($index, $before);
}
public function getInput() : Item{
return $this->getItem(self::SLOT_INPUT);
}
public function getLapis() : Item{
return $this->getItem(self::SLOT_LAPIS);
}
public function getOutput(int $optionId) : ?Item{
$option = $this->getOption($optionId);
return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments());
}
public function getOption(int $optionId) : ?EnchantOption{
return $this->options[$optionId] ?? null;
}
}

View File

@ -223,6 +223,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::ECHO_SHARD, Items::ECHO_SHARD());
$this->map1to1Item(Ids::EGG, Items::EGG());
$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
$this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());

View File

@ -76,7 +76,7 @@ use function array_key_exists;
use function array_merge;
use function array_values;
use function min;
use function random_int;
use function mt_rand;
class Human extends Living implements ProjectileSource, InventoryHolder{
@ -211,6 +211,18 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->xpManager;
}
public function getEnchantmentSeed() : int{
return $this->xpSeed;
}
public function setEnchantmentSeed(int $seed) : void{
$this->xpSeed = $seed;
}
public function generateEnchantmentSeed() : int{
return mt_rand(Limits::INT32_MIN, Limits::INT32_MAX);
}
public function getXpDropAmount() : int{
//this causes some XP to be lost on death when above level 1 (by design), dropping at most enough points for
//about 7.5 levels of XP.
@ -334,7 +346,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if(($xpSeedTag = $nbt->getTag(self::TAG_XP_SEED)) instanceof IntTag){
$this->xpSeed = $xpSeedTag->getValue();
}else{
$this->xpSeed = random_int(Limits::INT32_MIN, Limits::INT32_MAX);
$this->xpSeed = $this->generateEnchantmentSeed();
}
}

View File

@ -0,0 +1,75 @@
<?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\player;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\event\Event;
use pocketmine\item\enchantment\EnchantOption;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function count;
/**
* Called when a player inserts an item into an enchanting table's input slot.
* The options provided by the event will be shown on the enchanting table menu.
*/
class PlayerEnchantOptionsRequestEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
/**
* @param EnchantOption[] $options
*/
public function __construct(
Player $player,
private readonly EnchantInventory $enchantInventory,
private array $options
){
$this->player = $player;
}
public function getEnchantInventory() : EnchantInventory{
return $this->enchantInventory;
}
/**
* @return EnchantOption[]
*/
public function getOptions() : array{
return $this->options;
}
/**
* @param EnchantOption[] $options
*/
public function setOptions(array $options) : void{
Utils::validateArrayValueType($options, function(EnchantOption $_) : void{ });
if(($optionCount = count($options)) > 3){
throw new \LogicException("The maximum number of options for an enchanting table is 3, but $optionCount have been passed");
}
$this->options = $options;
}
}

View File

@ -0,0 +1,85 @@
<?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\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\inventory\transaction\EnchantTransaction;
use pocketmine\item\enchantment\EnchantOption;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Called when a player enchants an item using an enchanting table.
*/
class PlayerItemEnchantEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private readonly EnchantTransaction $transaction,
private readonly EnchantOption $option,
private readonly Item $inputItem,
private readonly Item $outputItem,
private readonly int $cost
){
$this->player = $player;
}
/**
* Returns the inventory transaction involved in this enchant event.
*/
public function getTransaction() : EnchantTransaction{
return $this->transaction;
}
/**
* Returns the enchantment option used.
*/
public function getOption() : EnchantOption{
return $this->option;
}
/**
* Returns the item to be enchanted.
*/
public function getInputItem() : Item{
return clone $this->inputItem;
}
/**
* Returns the enchanted item.
*/
public function getOutputItem() : Item{
return clone $this->outputItem;
}
/**
* Returns the number of XP levels and lapis that will be subtracted after enchanting
* if the player is not in creative mode.
*/
public function getCost() : int{
return $this->cost;
}
}

View File

@ -0,0 +1,132 @@
<?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\transaction;
use pocketmine\event\player\PlayerItemEnchantEvent;
use pocketmine\item\enchantment\EnchantmentHelper;
use pocketmine\item\enchantment\EnchantOption;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use function count;
class EnchantTransaction extends InventoryTransaction{
private ?Item $inputItem = null;
private ?Item $outputItem = null;
public function __construct(
Player $source,
private readonly EnchantOption $option,
private readonly int $cost
){
parent::__construct($source);
}
private function validateOutput() : void{
if($this->inputItem === null || $this->outputItem === null){
throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before validating output");
}
$enchantedInput = EnchantmentHelper::enchantItem($this->inputItem, $this->option->getEnchantments());
if(!$this->outputItem->equalsExact($enchantedInput)){
throw new TransactionValidationException("Invalid output item");
}
}
private function validateFiniteResources(int $lapisSpent) : void{
if($lapisSpent !== $this->cost){
throw new TransactionValidationException("Expected the amount of lapis lazuli spent to be $this->cost, but received $lapisSpent");
}
$xpLevel = $this->source->getXpManager()->getXpLevel();
$requiredXpLevel = $this->option->getRequiredXpLevel();
if($xpLevel < $requiredXpLevel){
throw new TransactionValidationException("Player's XP level $xpLevel is less than the required XP level $requiredXpLevel");
}
if($xpLevel < $this->cost){
throw new TransactionValidationException("Player's XP level $xpLevel is less than the XP level cost $this->cost");
}
}
public function validate() : void{
if(count($this->actions) < 1){
throw new TransactionValidationException("Transaction must have at least one action to be executable");
}
/** @var Item[] $inputs */
$inputs = [];
/** @var Item[] $outputs */
$outputs = [];
$this->matchItems($outputs, $inputs);
$lapisSpent = 0;
foreach($inputs as $input){
if($input->getTypeId() === ItemTypeIds::LAPIS_LAZULI){
$lapisSpent = $input->getCount();
}else{
if($this->inputItem !== null){
throw new TransactionValidationException("Received more than 1 items to enchant");
}
$this->inputItem = $input;
}
}
if($this->inputItem === null){
throw new TransactionValidationException("No item to enchant received");
}
if(($outputCount = count($outputs)) !== 1){
throw new TransactionValidationException("Expected 1 output item, but received $outputCount");
}
$this->outputItem = $outputs[0];
$this->validateOutput();
if($this->source->hasFiniteResources()){
$this->validateFiniteResources($lapisSpent);
}
}
public function execute() : void{
parent::execute();
if($this->source->hasFiniteResources()){
$this->source->getXpManager()->subtractXpLevels($this->cost);
}
$this->source->setEnchantmentSeed($this->source->generateEnchantmentSeed());
}
protected function callExecuteEvent() : bool{
if($this->inputItem === null || $this->outputItem === null){
throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before executing the event");
}
$event = new PlayerItemEnchantEvent($this->source, $this, $this->option, $this->inputItem, $this->outputItem, $this->cost);
$event->call();
return !$event->isCancelled();
}
}

View File

@ -44,8 +44,11 @@ class Armor extends Durable{
protected ?Color $customColor = null;
public function __construct(ItemIdentifier $identifier, string $name, ArmorTypeInfo $info){
parent::__construct($identifier, $name);
/**
* @param string[] $enchantmentTags
*/
public function __construct(ItemIdentifier $identifier, string $name, ArmorTypeInfo $info, array $enchantmentTags = []){
parent::__construct($identifier, $name, $enchantmentTags);
$this->armorInfo = $info;
}
@ -72,6 +75,14 @@ class Armor extends Durable{
return $this->armorInfo->isFireProof();
}
public function getMaterial() : ArmorMaterial{
return $this->armorInfo->getMaterial();
}
public function getEnchantability() : int{
return $this->armorInfo->getMaterial()->getEnchantability();
}
/**
* Returns the dyed colour of this armour piece. This generally only applies to leather armour.
*/

View File

@ -0,0 +1,42 @@
<?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\item;
class ArmorMaterial{
public function __construct(
private readonly int $enchantability
){
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return $this->enchantability;
}
}

View File

@ -24,13 +24,18 @@ declare(strict_types=1);
namespace pocketmine\item;
class ArmorTypeInfo{
private ArmorMaterial $material;
public function __construct(
private int $defensePoints,
private int $maxDurability,
private int $armorSlot,
private int $toughness = 0,
private bool $fireProof = false
){}
private bool $fireProof = false,
?ArmorMaterial $material = null
){
$this->material = $material ?? VanillaArmorMaterials::LEATHER();
}
public function getDefensePoints() : int{
return $this->defensePoints;
@ -51,4 +56,8 @@ class ArmorTypeInfo{
public function isFireProof() : bool{
return $this->fireProof;
}
public function getMaterial() : ArmorMaterial{
return $this->material;
}
}

View File

@ -56,15 +56,17 @@ class ChorusFruit extends Food{
$maxY = $minY + 16;
$maxZ = $minZ + 16;
$worldMinY = $world->getMinY();
for($attempts = 0; $attempts < 16; ++$attempts){
$x = mt_rand($minX, $maxX);
$y = mt_rand($minY, $maxY);
$z = mt_rand($minZ, $maxZ);
while($y >= 0 && !$world->getBlockAt($x, $y, $z)->isSolid()){
while($y >= $worldMinY && !$world->getBlockAt($x, $y, $z)->isSolid()){
$y--;
}
if($y < 0){
if($y < $worldMinY){
continue;
}

View File

@ -0,0 +1,30 @@
<?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\item;
class EnchantedBook extends Item{
public function getMaxStackSize() : int{
return 1;
}
}

View File

@ -107,10 +107,13 @@ class Item implements \JsonSerializable{
* NOTE: This should NOT BE USED for creating items to set into an inventory. Use VanillaItems for that
* purpose.
* @see VanillaItems
*
* @param string[] $enchantmentTags
*/
public function __construct(
private ItemIdentifier $identifier,
protected string $name = "Unknown"
protected string $name = "Unknown",
private array $enchantmentTags = []
){
$this->nbt = new CompoundTag();
}
@ -455,6 +458,29 @@ class Item implements \JsonSerializable{
return $this->name;
}
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to this item during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->enchantmentTags;
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return 1;
}
final public function canBePlaced() : bool{
return $this->getBlock()->canBePlaced();
}

View File

@ -36,7 +36,7 @@ final class ItemBlock extends Item{
public function __construct(
private Block $block
){
parent::__construct(ItemIdentifier::fromBlock($block), $block->getName());
parent::__construct(ItemIdentifier::fromBlock($block), $block->getName(), $block->getEnchantmentTags());
}
protected function describeState(RuntimeDataDescriber $w) : void{

View File

@ -303,8 +303,9 @@ final class ItemTypeIds{
public const MANGROVE_BOAT = 20264;
public const GLOW_BERRIES = 20265;
public const CHERRY_SIGN = 20266;
public const ENCHANTED_BOOK = 20267;
public const FIRST_UNUSED_ITEM_ID = 20267;
public const FIRST_UNUSED_ITEM_ID = 20268;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -1277,6 +1277,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("egg", fn() => Items::EGG());
$result->register("elixir", fn() => Items::MEDICINE()->setType(MedicineType::ELIXIR()));
$result->register("emerald", fn() => Items::EMERALD());
$result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK());
$result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE());
$result->register("ender_pearl", fn() => Items::ENDER_PEARL());

View File

@ -26,8 +26,11 @@ namespace pocketmine\item;
abstract class TieredTool extends Tool{
protected ToolTier $tier;
public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier){
parent::__construct($identifier, $name);
/**
* @param string[] $enchantmentTags
*/
public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier, array $enchantmentTags = []){
parent::__construct($identifier, $name, $enchantmentTags);
$this->tier = $tier;
}
@ -43,6 +46,10 @@ abstract class TieredTool extends Tool{
return $this->tier->getBaseEfficiency();
}
public function getEnchantability() : int{
return $this->tier->getEnchantability();
}
public function getFuelTime() : int{
if($this->tier->equals(ToolTier::WOOD())){
return 200;

View File

@ -45,12 +45,12 @@ final class ToolTier{
protected static function setup() : void{
self::registerAll(
new self("wood", 1, 60, 5, 2),
new self("gold", 2, 33, 5, 12),
new self("stone", 3, 132, 6, 4),
new self("iron", 4, 251, 7, 6),
new self("diamond", 5, 1562, 8, 8),
new self("netherite", 6, 2032, 9, 9)
new self("wood", 1, 60, 5, 2, 15),
new self("gold", 2, 33, 5, 12, 22),
new self("stone", 3, 132, 6, 4, 5),
new self("iron", 4, 251, 7, 6, 14),
new self("diamond", 5, 1562, 8, 8, 10),
new self("netherite", 6, 2032, 9, 9, 15)
);
}
@ -59,7 +59,8 @@ final class ToolTier{
private int $harvestLevel,
private int $maxDurability,
private int $baseAttackPoints,
private int $baseEfficiency
private int $baseEfficiency,
private int $enchantability
){
$this->Enum___construct($name);
}
@ -79,4 +80,14 @@ final class ToolTier{
public function getBaseEfficiency() : int{
return $this->baseEfficiency;
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return $this->enchantability;
}
}

View File

@ -0,0 +1,73 @@
<?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\item;
use pocketmine\utils\RegistryTrait;
/**
* This doc-block is generated automatically, do not modify it manually.
* This must be regenerated whenever registry members are added, removed or changed.
* @see build/generate-registry-annotations.php
* @generate-registry-docblock
*
* @method static ArmorMaterial CHAINMAIL()
* @method static ArmorMaterial DIAMOND()
* @method static ArmorMaterial GOLD()
* @method static ArmorMaterial IRON()
* @method static ArmorMaterial LEATHER()
* @method static ArmorMaterial NETHERITE()
* @method static ArmorMaterial TURTLE()
*/
final class VanillaArmorMaterials{
use RegistryTrait;
private function __construct(){
// NOOP
}
protected static function register(string $name, ArmorMaterial $armorMaterial) : void{
self::_registryRegister($name, $armorMaterial);
}
/**
* @return ArmorMaterial[]
* @phpstan-return array<string, ArmorMaterial>
*/
public static function getAll() : array{
// phpstan doesn't support generic traits yet :(
/** @var ArmorMaterial[] $result */
$result = self::_registryGetAll();
return $result;
}
protected static function setup() : void{
self::register("leather", new ArmorMaterial(15));
self::register("chainmail", new ArmorMaterial(12));
self::register("iron", new ArmorMaterial(9));
self::register("turtle", new ArmorMaterial(9));
self::register("gold", new ArmorMaterial(25));
self::register("diamond", new ArmorMaterial(10));
self::register("netherite", new ArmorMaterial(15));
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\utils\RecordType;
use pocketmine\block\VanillaBlocks;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\entity\Entity;
use pocketmine\entity\Location;
@ -32,8 +31,10 @@ use pocketmine\entity\Squid;
use pocketmine\entity\Villager;
use pocketmine\entity\Zombie;
use pocketmine\inventory\ArmorInventory;
use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\ItemIdentifier as IID;
use pocketmine\item\ItemTypeIds as Ids;
use pocketmine\item\VanillaArmorMaterials as ArmorMaterials;
use pocketmine\math\Vector3;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\CloningRegistryTrait;
@ -151,6 +152,7 @@ use pocketmine\world\World;
* @method static Item ECHO_SHARD()
* @method static Egg EGG()
* @method static Item EMERALD()
* @method static EnchantedBook ENCHANTED_BOOK()
* @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
* @method static EnderPearl ENDER_PEARL()
* @method static ExperienceBottle EXPERIENCE_BOTTLE()
@ -337,7 +339,7 @@ final class VanillaItems{
self::registerSpawnEggs();
self::registerTierToolItems();
self::register("air", VanillaBlocks::AIR()->asItem()->setCount(0));
self::register("air", Blocks::AIR()->asItem()->setCount(0));
self::register("acacia_sign", new ItemBlockWallOrFloor(new IID(Ids::ACACIA_SIGN), Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
self::register("amethyst_shard", new Item(new IID(Ids::AMETHYST_SHARD), "Amethyst Shard"));
@ -355,8 +357,8 @@ final class VanillaItems{
self::register("bleach", new Item(new IID(Ids::BLEACH), "Bleach"));
self::register("bone", new Item(new IID(Ids::BONE), "Bone"));
self::register("bone_meal", new Fertilizer(new IID(Ids::BONE_MEAL), "Bone Meal"));
self::register("book", new Book(new IID(Ids::BOOK), "Book"));
self::register("bow", new Bow(new IID(Ids::BOW), "Bow"));
self::register("book", new Book(new IID(Ids::BOOK), "Book", [EnchantmentTags::ALL]));
self::register("bow", new Bow(new IID(Ids::BOW), "Bow", [EnchantmentTags::BOW]));
self::register("bowl", new Bowl(new IID(Ids::BOWL), "Bowl"));
self::register("bread", new Bread(new IID(Ids::BREAD), "Bread"));
self::register("brick", new Item(new IID(Ids::BRICK), "Brick"));
@ -408,7 +410,7 @@ final class VanillaItems{
self::register("clownfish", new Clownfish(new IID(Ids::CLOWNFISH), "Clownfish"));
self::register("coal", new Coal(new IID(Ids::COAL), "Coal"));
self::register("cocoa_beans", new CocoaBeans(new IID(Ids::COCOA_BEANS), "Cocoa Beans"));
self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass"));
self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass", [EnchantmentTags::COMPASS]));
self::register("cooked_chicken", new CookedChicken(new IID(Ids::COOKED_CHICKEN), "Cooked Chicken"));
self::register("cooked_fish", new CookedFish(new IID(Ids::COOKED_FISH), "Cooked Fish"));
self::register("cooked_mutton", new CookedMutton(new IID(Ids::COOKED_MUTTON), "Cooked Mutton"));
@ -429,15 +431,16 @@ final class VanillaItems{
self::register("echo_shard", new Item(new IID(Ids::ECHO_SHARD), "Echo Shard"));
self::register("egg", new Egg(new IID(Ids::EGG), "Egg"));
self::register("emerald", new Item(new IID(Ids::EMERALD), "Emerald"));
self::register("enchanted_book", new EnchantedBook(new IID(Ids::ENCHANTED_BOOK), "Enchanted Book"));
self::register("enchanted_golden_apple", new GoldenAppleEnchanted(new IID(Ids::ENCHANTED_GOLDEN_APPLE), "Enchanted Golden Apple"));
self::register("ender_pearl", new EnderPearl(new IID(Ids::ENDER_PEARL), "Ender Pearl"));
self::register("experience_bottle", new ExperienceBottle(new IID(Ids::EXPERIENCE_BOTTLE), "Bottle o' Enchanting"));
self::register("feather", new Item(new IID(Ids::FEATHER), "Feather"));
self::register("fermented_spider_eye", new Item(new IID(Ids::FERMENTED_SPIDER_EYE), "Fermented Spider Eye"));
self::register("fire_charge", new FireCharge(new IID(Ids::FIRE_CHARGE), "Fire Charge"));
self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod"));
self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
self::register("flint", new Item(new IID(Ids::FLINT), "Flint"));
self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel"));
self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear"));
self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle"));
self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon"));
@ -521,7 +524,7 @@ final class VanillaItems{
self::register("redstone_dust", new Redstone(new IID(Ids::REDSTONE_DUST), "Redstone"));
self::register("rotten_flesh", new RottenFlesh(new IID(Ids::ROTTEN_FLESH), "Rotten Flesh"));
self::register("scute", new Item(new IID(Ids::SCUTE), "Scute"));
self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears"));
self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears", [EnchantmentTags::SHEARS]));
self::register("shulker_shell", new Item(new IID(Ids::SHULKER_SHELL), "Shulker Shell"));
self::register("slimeball", new Item(new IID(Ids::SLIMEBALL), "Slimeball"));
self::register("snowball", new Snowball(new IID(Ids::SNOWBALL), "Snowball"));
@ -577,67 +580,67 @@ final class VanillaItems{
}
private static function registerTierToolItems() : void{
self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND()));
self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD()));
self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON()));
self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE()));
self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE()));
self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD()));
self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND()));
self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD()));
self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON()));
self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE()));
self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE()));
self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD()));
self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND()));
self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD()));
self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON()));
self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE()));
self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE()));
self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD()));
self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND()));
self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD()));
self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON()));
self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE()));
self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE()));
self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD()));
self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND()));
self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD()));
self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON()));
self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE()));
self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE()));
self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD()));
self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND(), [EnchantmentTags::AXE]));
self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD(), [EnchantmentTags::AXE]));
self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON(), [EnchantmentTags::AXE]));
self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE(), [EnchantmentTags::AXE]));
self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE(), [EnchantmentTags::AXE]));
self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD(), [EnchantmentTags::AXE]));
self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND(), [EnchantmentTags::HOE]));
self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD(), [EnchantmentTags::HOE]));
self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON(), [EnchantmentTags::HOE]));
self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE(), [EnchantmentTags::HOE]));
self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE(), [EnchantmentTags::HOE]));
self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD(), [EnchantmentTags::HOE]));
self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND(), [EnchantmentTags::PICKAXE]));
self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD(), [EnchantmentTags::PICKAXE]));
self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON(), [EnchantmentTags::PICKAXE]));
self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE(), [EnchantmentTags::PICKAXE]));
self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE(), [EnchantmentTags::PICKAXE]));
self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD(), [EnchantmentTags::PICKAXE]));
self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND(), [EnchantmentTags::SHOVEL]));
self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD(), [EnchantmentTags::SHOVEL]));
self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON(), [EnchantmentTags::SHOVEL]));
self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE(), [EnchantmentTags::SHOVEL]));
self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE(), [EnchantmentTags::SHOVEL]));
self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD(), [EnchantmentTags::SHOVEL]));
self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND(), [EnchantmentTags::SWORD]));
self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD(), [EnchantmentTags::SWORD]));
self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON(), [EnchantmentTags::SWORD]));
self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE(), [EnchantmentTags::SWORD]));
self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE(), [EnchantmentTags::SWORD]));
self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD(), [EnchantmentTags::SWORD]));
}
private static function registerArmorItems() : void{
self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET)));
self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2)));
self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET)));
self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET)));
self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET)));
self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true)));
self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS]));
self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS]));
self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS]));
self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS]));
self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS]));
self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS]));
self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST)));
self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2)));
self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST)));
self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST)));
self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST)));
self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true)));
self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE]));
self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE]));
self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE]));
self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE]));
self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE]));
self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE]));
self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2)));
self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD)));
self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD)));
self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true)));
self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD)));
self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET]));
self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET]));
self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET]));
self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET]));
self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET]));
self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET]));
self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET]));
self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS)));
self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2)));
self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS)));
self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS)));
self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS)));
self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true)));
self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS]));
self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS]));
self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS]));
self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS]));
self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS]));
self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
}
}

View File

@ -0,0 +1,211 @@
<?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\item\enchantment;
use pocketmine\item\enchantment\ItemEnchantmentTagRegistry as TagRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
use pocketmine\item\Item;
use pocketmine\utils\SingletonTrait;
use function array_filter;
use function array_values;
use function count;
use function spl_object_id;
/**
* Registry of enchantments that can be applied to items during in-game enchanting (enchanting table, anvil, fishing, etc.).
*/
final class AvailableEnchantmentRegistry{
use SingletonTrait;
/** @var Enchantment[] */
private array $enchantments = [];
/** @var string[][] */
private array $primaryItemTags = [];
/** @var string[][] */
private array $secondaryItemTags = [];
private function __construct(){
$this->register(Enchantments::PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::FIRE_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::FEATHER_FALLING(), [Tags::BOOTS], []);
$this->register(Enchantments::BLAST_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::PROJECTILE_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
$this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
$this->register(Enchantments::EFFICIENCY(), [Tags::DIG_TOOLS], [Tags::SHEARS]);
$this->register(Enchantments::FORTUNE(), [Tags::DIG_TOOLS], []);
$this->register(Enchantments::SILK_TOUCH(), [Tags::DIG_TOOLS], [Tags::SHEARS]);
$this->register(
Enchantments::UNBREAKING(),
[Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD],
[Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
);
$this->register(Enchantments::POWER(), [Tags::BOW], []);
$this->register(Enchantments::PUNCH(), [Tags::BOW], []);
$this->register(Enchantments::FLAME(), [Tags::BOW], []);
$this->register(Enchantments::INFINITY(), [Tags::BOW], []);
$this->register(
Enchantments::MENDING(),
[],
[Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD,
Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
);
$this->register(Enchantments::VANISHING(), [], [Tags::ALL]);
$this->register(Enchantments::SWIFT_SNEAK(), [], [Tags::LEGGINGS]);
}
/**
* @param string[] $primaryItemTags
* @param string[] $secondaryItemTags
*/
public function register(Enchantment $enchantment, array $primaryItemTags, array $secondaryItemTags) : void{
$this->enchantments[spl_object_id($enchantment)] = $enchantment;
$this->setPrimaryItemTags($enchantment, $primaryItemTags);
$this->setSecondaryItemTags($enchantment, $secondaryItemTags);
}
public function unregister(Enchantment $enchantment) : void{
unset($this->enchantments[spl_object_id($enchantment)]);
unset($this->primaryItemTags[spl_object_id($enchantment)]);
unset($this->secondaryItemTags[spl_object_id($enchantment)]);
}
public function unregisterAll() : void{
$this->enchantments = [];
$this->primaryItemTags = [];
$this->secondaryItemTags = [];
}
public function isRegistered(Enchantment $enchantment) : bool{
return isset($this->enchantments[spl_object_id($enchantment)]);
}
/**
* Returns primary compatibility tags for the specified enchantment.
*
* An item matching at least one of these tags (or its descendents) can be:
* - Offered this enchantment in an enchanting table
* - Enchanted by any means allowed by secondary tags
*
* @return string[]
*/
public function getPrimaryItemTags(Enchantment $enchantment) : array{
return $this->primaryItemTags[spl_object_id($enchantment)] ?? [];
}
/**
* @param string[] $tags
*/
public function setPrimaryItemTags(Enchantment $enchantment, array $tags) : void{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set primary item tags for non-registered enchantment");
}
$this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}
/**
* Returns secondary compatibility tags for the specified enchantment.
*
* An item matching at least one of these tags (or its descendents) can be:
* - Combined with an enchanted book with this enchantment in an anvil
* - Obtained as loot with this enchantment, e.g. fishing, treasure chests, mob equipment, etc.
*
* @return string[]
*/
public function getSecondaryItemTags(Enchantment $enchantment) : array{
return $this->secondaryItemTags[spl_object_id($enchantment)] ?? [];
}
/**
* @param string[] $tags
*/
public function setSecondaryItemTags(Enchantment $enchantment, array $tags) : void{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set secondary item tags for non-registered enchantment");
}
$this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}
/**
* Returns enchantments that can be applied to the specified item in an enchanting table (primary only).
*
* @return Enchantment[]
*/
public function getPrimaryEnchantmentsForItem(Item $item) : array{
$itemTags = $item->getEnchantmentTags();
if(count($itemTags) === 0 || $item->hasEnchantments()){
return [];
}
return array_filter(
$this->enchantments,
fn(Enchantment $e) => TagRegistry::getInstance()->isTagArrayIntersection($this->getPrimaryItemTags($e), $itemTags)
);
}
/**
* Returns all available enchantments compatible with the item.
*
* Warning: not suitable for obtaining enchantments for an enchanting table
* (use {@link AvailableEnchantmentRegistry::getPrimaryEnchantmentsForItem()} for that).
*
* @return Enchantment[]
*/
public function getAllEnchantmentsForItem(Item $item) : array{
if(count($item->getEnchantmentTags()) === 0){
return [];
}
return array_filter(
$this->enchantments,
fn(Enchantment $enchantment) => $this->isAvailableForItem($enchantment, $item)
);
}
/**
* Returns whether the specified enchantment can be applied to the particular item.
*
* Warning: not suitable for checking the availability of enchantment for an enchanting table.
*/
public function isAvailableForItem(Enchantment $enchantment, Item $item) : bool{
$itemTags = $item->getEnchantmentTags();
$tagRegistry = TagRegistry::getInstance();
return $tagRegistry->isTagArrayIntersection($this->getPrimaryItemTags($enchantment), $itemTags) ||
$tagRegistry->isTagArrayIntersection($this->getSecondaryItemTags($enchantment), $itemTags);
}
/**
* @return Enchantment[]
*/
public function getAll() : array{
return $this->enchantments;
}
}

View File

@ -0,0 +1,66 @@
<?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\item\enchantment;
/**
* Represents an option on the enchanting table menu.
* If selected, all the enchantments in the option will be applied to the item.
*/
class EnchantOption{
/**
* @param EnchantmentInstance[] $enchantments
*/
public function __construct(
private int $requiredXpLevel,
private string $displayName,
private array $enchantments
){}
/**
* Returns the minimum amount of XP levels required to select this enchantment option.
* It's NOT the number of XP levels that will be subtracted after enchanting.
*/
public function getRequiredXpLevel() : int{
return $this->requiredXpLevel;
}
/**
* Returns the name that will be translated to the 'Standard Galactic Alphabet' client-side.
* This can be any arbitrary text string, since the vanilla client cannot read the text anyway.
* Example: 'bless creature range free'.
*/
public function getDisplayName() : string{
return $this->displayName;
}
/**
* Returns the enchantments that will be applied to the item when this option is clicked.
*
* @return EnchantmentInstance[]
*/
public function getEnchantments() : array{
return $this->enchantments;
}
}

View File

@ -23,9 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item\enchantment;
use DaveRandom\CallbackValidator\CallbackType;
use DaveRandom\CallbackValidator\ParameterType;
use DaveRandom\CallbackValidator\ReturnType;
use pocketmine\lang\Translatable;
use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable;
use pocketmine\utils\Utils;
/**
* Manages enchantment type data.
@ -34,13 +38,32 @@ class Enchantment{
use NotCloneable;
use NotSerializable;
/** @var \Closure(int $level) : int $minEnchantingPower */
private \Closure $minEnchantingPower;
/**
* @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower
*
* @param int $primaryItemFlags @deprecated
* @param int $secondaryItemFlags @deprecated
* @param int $enchantingPowerRange Value used to calculate the maximum enchanting power (minEnchantingPower + enchantingPowerRange)
*/
public function __construct(
private Translatable|string $name,
private int $rarity,
private int $primaryItemFlags,
private int $secondaryItemFlags,
private int $maxLevel
){}
private int $maxLevel,
?\Closure $minEnchantingPower = null,
private int $enchantingPowerRange = 50
){
$this->minEnchantingPower = $minEnchantingPower ?? fn(int $level) : int => 1;
Utils::validateCallableSignature(new CallbackType(
new ReturnType("int"),
new ParameterType("level", "int")
), $this->minEnchantingPower);
}
/**
* Returns a translation key for this enchantment's name.
@ -58,6 +81,8 @@ class Enchantment{
/**
* Returns a bitset indicating what item types can have this item applied from an enchanting table.
*
* @deprecated
*/
public function getPrimaryItemFlags() : int{
return $this->primaryItemFlags;
@ -66,6 +91,8 @@ class Enchantment{
/**
* Returns a bitset indicating what item types cannot have this item applied from an enchanting table, but can from
* an anvil.
*
* @deprecated
*/
public function getSecondaryItemFlags() : int{
return $this->secondaryItemFlags;
@ -73,6 +100,8 @@ class Enchantment{
/**
* Returns whether this enchantment can apply to the item type from an enchanting table.
*
* @deprecated
*/
public function hasPrimaryItemType(int $flag) : bool{
return ($this->primaryItemFlags & $flag) !== 0;
@ -80,6 +109,8 @@ class Enchantment{
/**
* Returns whether this enchantment can apply to the item type from an anvil, if it is not a primary item.
*
* @deprecated
*/
public function hasSecondaryItemType(int $flag) : bool{
return ($this->secondaryItemFlags & $flag) !== 0;
@ -92,5 +123,34 @@ class Enchantment{
return $this->maxLevel;
}
//TODO: methods for min/max XP cost bounds based on enchantment level (not needed yet - enchanting is client-side)
/**
* Returns whether this enchantment can be applied to the item along with the given enchantment.
*/
public function isCompatibleWith(Enchantment $other) : bool{
return IncompatibleEnchantmentRegistry::getInstance()->areCompatible($this, $other);
}
/**
* Returns the minimum enchanting power value required for the particular level of the enchantment
* to be available in an enchanting table.
*
* Enchanting power is a random value based on the number of bookshelves around an enchanting table
* and the enchantability of the item being enchanted. It is only used when determining the available
* enchantments for the enchantment options.
*/
public function getMinEnchantingPower(int $level) : int{
return ($this->minEnchantingPower)($level);
}
/**
* Returns the maximum enchanting power value allowed for the particular level of the enchantment
* to be available in an enchanting table.
*
* Enchanting power is a random value based on the number of bookshelves around an enchanting table
* and the enchantability of the item being enchanted. It is only used when determining the available
* enchantments for the enchantment options.
*/
public function getMaxEnchantingPower(int $level) : int{
return $this->getMinEnchantingPower($level) + $this->enchantingPowerRange;
}
}

View File

@ -0,0 +1,217 @@
<?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\item\enchantment;
use pocketmine\block\BlockTypeIds;
use pocketmine\item\enchantment\AvailableEnchantmentRegistry as EnchantmentRegistry;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\VanillaItems as Items;
use pocketmine\utils\Random;
use pocketmine\world\Position;
use function abs;
use function array_filter;
use function chr;
use function count;
use function floor;
use function max;
use function min;
use function ord;
use function round;
final class EnchantmentHelper{
private const MAX_BOOKSHELF_COUNT = 15;
/**
* @param EnchantmentInstance[] $enchantments
*/
public static function enchantItem(Item $item, array $enchantments) : Item{
$resultItem = $item->getTypeId() === ItemTypeIds::BOOK ? Items::ENCHANTED_BOOK() : clone $item;
foreach($enchantments as $enchantment){
$resultItem->addEnchantment($enchantment);
}
return $resultItem;
}
/**
* @return EnchantOption[]
*/
public static function getEnchantOptions(Position $tablePos, Item $input, int $seed) : array{
if($input->isNull() || $input->hasEnchantments()){
return [];
}
$random = new Random($seed);
$bookshelfCount = self::countBookshelves($tablePos);
$baseRequiredLevel = $random->nextRange(1, 8) + ($bookshelfCount >> 1) + $random->nextRange(0, $bookshelfCount);
$topRequiredLevel = (int) floor(max($baseRequiredLevel / 3, 1));
$middleRequiredLevel = (int) floor($baseRequiredLevel * 2 / 3 + 1);
$bottomRequiredLevel = max($baseRequiredLevel, $bookshelfCount * 2);
return [
self::createEnchantOption($random, $input, $topRequiredLevel),
self::createEnchantOption($random, $input, $middleRequiredLevel),
self::createEnchantOption($random, $input, $bottomRequiredLevel),
];
}
private static function countBookshelves(Position $tablePos) : int{
$bookshelfCount = 0;
$world = $tablePos->getWorld();
for($x = -2; $x <= 2; $x++){
for($z = -2; $z <= 2; $z++){
// We only check blocks at a distance of 2 blocks from the enchanting table
if(abs($x) !== 2 && abs($z) !== 2){
continue;
}
// Ensure the space between the bookshelf stack at this X/Z and the enchanting table is empty
for($y = 0; $y <= 1; $y++){
// Calculate the coordinates of the space between the bookshelf and the enchanting table
$spaceX = max(min($x, 1), -1);
$spaceZ = max(min($z, 1), -1);
$spaceBlock = $world->getBlock($tablePos->add($spaceX, $y, $spaceZ));
if($spaceBlock->getTypeId() !== BlockTypeIds::AIR){
continue 2;
}
}
// Finally, check the number of bookshelves at the current position
for($y = 0; $y <= 1; $y++){
$block = $world->getBlock($tablePos->add($x, $y, $z));
if($block->getTypeId() === BlockTypeIds::BOOKSHELF){
$bookshelfCount++;
if($bookshelfCount === self::MAX_BOOKSHELF_COUNT){
return $bookshelfCount;
}
}
}
}
}
return $bookshelfCount;
}
private static function createEnchantOption(Random $random, Item $inputItem, int $requiredXpLevel) : EnchantOption{
$enchantingPower = $requiredXpLevel;
$enchantability = $inputItem->getEnchantability();
$enchantingPower = $enchantingPower + $random->nextRange(0, $enchantability >> 2) + $random->nextRange(0, $enchantability >> 2) + 1;
// Random bonus for enchanting power between 0.85 and 1.15
$bonus = 1 + ($random->nextFloat() + $random->nextFloat() - 1) * 0.15;
$enchantingPower = (int) round($enchantingPower * $bonus);
$resultEnchantments = [];
$availableEnchantments = self::getAvailableEnchantments($enchantingPower, $inputItem);
$lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
if($lastEnchantment !== null){
$resultEnchantments[] = $lastEnchantment;
// With probability (power + 1) / 50, continue adding enchantments
while($random->nextFloat() <= ($enchantingPower + 1) / 50){
// Remove from the list of available enchantments anything that conflicts
// with previously-chosen enchantments
$availableEnchantments = array_filter(
$availableEnchantments,
function(EnchantmentInstance $e) use ($lastEnchantment){
return $e->getType() !== $lastEnchantment->getType() &&
$e->getType()->isCompatibleWith($lastEnchantment->getType());
}
);
$lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
if($lastEnchantment === null){
break;
}
$resultEnchantments[] = $lastEnchantment;
$enchantingPower >>= 1;
}
}
return new EnchantOption($requiredXpLevel, self::getRandomOptionName($random), $resultEnchantments);
}
/**
* @return EnchantmentInstance[]
*/
private static function getAvailableEnchantments(int $enchantingPower, Item $item) : array{
$list = [];
foreach(EnchantmentRegistry::getInstance()->getPrimaryEnchantmentsForItem($item) as $enchantment){
for($lvl = $enchantment->getMaxLevel(); $lvl > 0; $lvl--){
if($enchantingPower >= $enchantment->getMinEnchantingPower($lvl) &&
$enchantingPower <= $enchantment->getMaxEnchantingPower($lvl)
){
$list[] = new EnchantmentInstance($enchantment, $lvl);
break;
}
}
}
return $list;
}
/**
* @param EnchantmentInstance[] $enchantments
*/
private static function getRandomWeightedEnchantment(Random $random, array $enchantments) : ?EnchantmentInstance{
if(count($enchantments) === 0){
return null;
}
$totalWeight = 0;
foreach($enchantments as $enchantment){
$totalWeight += $enchantment->getType()->getRarity();
}
$result = null;
$randomWeight = $random->nextRange(1, $totalWeight);
foreach($enchantments as $enchantment){
$randomWeight -= $enchantment->getType()->getRarity();
if($randomWeight <= 0){
$result = $enchantment;
break;
}
}
return $result;
}
private static function getRandomOptionName(Random $random) : string{
$name = "";
for($i = $random->nextRange(5, 15); $i > 0; $i--){
$name .= chr($random->nextRange(ord("a"), ord("z")));
}
return $name;
}
}

View File

@ -0,0 +1,34 @@
<?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\item\enchantment;
/**
* Constants for groupings of incompatible enchantments.
* Enchantments belonging to the same incompatibility group cannot be applied side-by-side on the same item.
*/
final class IncompatibleEnchantmentGroups{
public const PROTECTION = "protection";
public const BOW_INFINITE = "bow_infinite";
public const DIG_DROP = "dig_drop";
}

View File

@ -0,0 +1,94 @@
<?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\item\enchantment;
use pocketmine\item\enchantment\IncompatibleEnchantmentGroups as Groups;
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
use pocketmine\utils\SingletonTrait;
use function array_intersect_key;
use function count;
use function spl_object_id;
/**
* Manages which enchantments are incompatible with each other.
* Enchantments can be added to groups to make them incompatible with all other enchantments already in that group.
*/
final class IncompatibleEnchantmentRegistry{
use SingletonTrait;
/**
* @phpstan-var array<int, array<string, true>>
* @var true[][]
*/
private array $incompatibilityMap = [];
private function __construct(){
$this->register(Groups::PROTECTION, [Enchantments::PROTECTION(), Enchantments::FIRE_PROTECTION(), Enchantments::BLAST_PROTECTION(), Enchantments::PROJECTILE_PROTECTION()]);
$this->register(Groups::BOW_INFINITE, [Enchantments::INFINITY(), Enchantments::MENDING()]);
$this->register(Groups::DIG_DROP, [Enchantments::FORTUNE(), Enchantments::SILK_TOUCH()]);
}
/**
* Register incompatibility for an enchantment group.
*
* All enchantments belonging to the same group are incompatible with each other,
* i.e. they cannot be added together on the same item.
*
* @param Enchantment[] $enchantments
*/
public function register(string $tag, array $enchantments) : void{
foreach($enchantments as $enchantment){
$this->incompatibilityMap[spl_object_id($enchantment)][$tag] = true;
}
}
/**
* Unregister incompatibility for some enchantments of a particular group.
*
* @param Enchantment[] $enchantments
*/
public function unregister(string $tag, array $enchantments) : void{
foreach($enchantments as $enchantment){
unset($this->incompatibilityMap[spl_object_id($enchantment)][$tag]);
}
}
/**
* Unregister incompatibility for all enchantments of a particular group.
*/
public function unregisterAll(string $tag) : void{
foreach($this->incompatibilityMap as $id => $tags){
unset($this->incompatibilityMap[$id][$tag]);
}
}
/**
* Returns whether two enchantments can be applied to the same item.
*/
public function areCompatible(Enchantment $first, Enchantment $second) : bool{
$firstIncompatibilities = $this->incompatibilityMap[spl_object_id($first)] ?? [];
$secondIncompatibilities = $this->incompatibilityMap[spl_object_id($second)] ?? [];
return count(array_intersect_key($firstIncompatibilities, $secondIncompatibilities)) === 0;
}
}

View File

@ -0,0 +1,190 @@
<?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\item\enchantment;
use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use function array_diff;
use function array_intersect;
use function array_merge;
use function array_search;
use function array_shift;
use function array_unique;
use function count;
/**
* Manages known item enchantment tags and the relations between them.
* Used to determine which tags belong to which other tags, and to check if lists of tags intersect.
*/
final class ItemEnchantmentTagRegistry{
use SingletonTrait;
/**
* @phpstan-var array<string, list<string>>
* @var string[][]
*/
private array $tagMap = [];
private function __construct(){
$this->register(Tags::ARMOR, [Tags::HELMET, Tags::CHESTPLATE, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Tags::SHIELD);
$this->register(Tags::SWORD);
$this->register(Tags::TRIDENT);
$this->register(Tags::BOW);
$this->register(Tags::CROSSBOW);
$this->register(Tags::SHEARS);
$this->register(Tags::FLINT_AND_STEEL);
$this->register(Tags::DIG_TOOLS, [Tags::AXE, Tags::PICKAXE, Tags::SHOVEL, Tags::HOE]);
$this->register(Tags::FISHING_ROD);
$this->register(Tags::CARROT_ON_STICK);
$this->register(Tags::COMPASS);
$this->register(Tags::MASK);
$this->register(Tags::ELYTRA);
$this->register(Tags::BRUSH);
$this->register(Tags::WEAPONS, [
Tags::SWORD,
Tags::TRIDENT,
Tags::BOW,
Tags::CROSSBOW,
Tags::DIG_TOOLS,
]);
}
/**
* Register tag and its nested tags.
*
* @param string[] $nestedTags
*/
public function register(string $tag, array $nestedTags = []) : void{
$this->assertNotInternalTag($tag);
foreach($nestedTags as $nestedTag){
if(!isset($this->tagMap[$nestedTag])){
$this->register($nestedTag);
}
$this->tagMap[$tag][] = $nestedTag;
}
if(!isset($this->tagMap[$tag])){
$this->tagMap[$tag] = [];
$this->tagMap[Tags::ALL][] = $tag;
}
}
public function unregister(string $tag) : void{
if(!isset($this->tagMap[$tag])){
return;
}
$this->assertNotInternalTag($tag);
unset($this->tagMap[$tag]);
foreach(Utils::stringifyKeys($this->tagMap) as $key => $nestedTags){
if(($nestedKey = array_search($tag, $nestedTags, true)) !== false){
unset($this->tagMap[$key][$nestedKey]);
}
}
}
/**
* Remove specified nested tags.
*
* @param string[] $nestedTags
*/
public function removeNested(string $tag, array $nestedTags) : void{
$this->assertNotInternalTag($tag);
$this->tagMap[$tag] = array_diff($this->tagMap[$tag], $nestedTags);
}
/**
* Returns nested tags of a particular tag.
*
* @return string[]
*/
public function getNested(string $tag) : array{
return $this->tagMap[$tag] ?? [];
}
/**
* @param string[] $firstTags
* @param string[] $secondTags
*/
public function isTagArrayIntersection(array $firstTags, array $secondTags) : bool{
if(count($firstTags) === 0 || count($secondTags) === 0){
return false;
}
$firstLeafTags = $this->getLeafTagsForArray($firstTags);
$secondLeafTags = $this->getLeafTagsForArray($secondTags);
return count(array_intersect($firstLeafTags, $secondLeafTags)) !== 0;
}
/**
* Returns all tags that are recursively nested within each tag in the array and do not have any nested tags.
*
* @param string[] $tags
*
* @return string[]
*/
private function getLeafTagsForArray(array $tags) : array{
$leafTagArrays = [];
foreach($tags as $tag){
$leafTagArrays[] = $this->getLeafTags($tag);
}
return array_unique(array_merge(...$leafTagArrays));
}
/**
* Returns all tags that are recursively nested within the given tag and do not have any nested tags.
*
* @return string[]
*/
private function getLeafTags(string $tag) : array{
$result = [];
$tagsToHandle = [$tag];
while(count($tagsToHandle) !== 0){
$currentTag = array_shift($tagsToHandle);
$nestedTags = $this->getNested($currentTag);
if(count($nestedTags) === 0){
$result[] = $currentTag;
}else{
$tagsToHandle = array_merge($tagsToHandle, $nestedTags);
}
}
return $result;
}
private function assertNotInternalTag(string $tag) : void{
if($tag === Tags::ALL){
throw new \InvalidArgumentException(
"Cannot perform any operations on the internal item enchantment tag '$tag'"
);
}
}
}

View File

@ -0,0 +1,57 @@
<?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\item\enchantment;
/**
* Tags used by items and enchantments to determine which enchantments can be applied to which items.
* Some tags may contain other tags.
* @see ItemEnchantmentTagRegistry
*/
final class ItemEnchantmentTags{
public const ALL = "all";
public const ARMOR = "armor";
public const HELMET = "helmet";
public const CHESTPLATE = "chestplate";
public const LEGGINGS = "leggings";
public const BOOTS = "boots";
public const SHIELD = "shield";
public const SWORD = "sword";
public const TRIDENT = "trident";
public const BOW = "bow";
public const CROSSBOW = "crossbow";
public const SHEARS = "shears";
public const FLINT_AND_STEEL = "flint_and_steel";
public const DIG_TOOLS = "dig_tools";
public const AXE = "axe";
public const PICKAXE = "pickaxe";
public const SHOVEL = "shovel";
public const HOE = "hoe";
public const FISHING_ROD = "fishing_rod";
public const CARROT_ON_STICK = "carrot_on_stick";
public const COMPASS = "compass";
public const MASK = "mask";
public const ELYTRA = "elytra";
public const BRUSH = "brush";
public const WEAPONS = "weapons";
}

View File

@ -23,14 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item\enchantment;
/** @deprecated */
final class ItemFlags{
private function __construct(){
//NOOP
}
//TODO: this should probably move to protocol
public const NONE = 0x0;
public const ALL = 0xffff;
public const ARMOR = self::HEAD | self::TORSO | self::LEGS | self::FEET;

View File

@ -36,10 +36,15 @@ class ProtectionEnchantment extends Enchantment{
/**
* ProtectionEnchantment constructor.
*
* @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower
*
* @param int $primaryItemFlags @deprecated
* @param int $secondaryItemFlags @deprecated
* @param int[]|null $applicableDamageTypes EntityDamageEvent::CAUSE_* constants which this enchantment type applies to, or null if it applies to all types of damage.
* @param int $enchantingPowerRange Value used to calculate the maximum enchanting power (minEnchantingPower + enchantingPowerRange)
*/
public function __construct(Translatable|string $name, int $rarity, int $primaryItemFlags, int $secondaryItemFlags, int $maxLevel, float $typeModifier, ?array $applicableDamageTypes){
parent::__construct($name, $rarity, $primaryItemFlags, $secondaryItemFlags, $maxLevel);
public function __construct(Translatable|string $name, int $rarity, int $primaryItemFlags, int $secondaryItemFlags, int $maxLevel, float $typeModifier, ?array $applicableDamageTypes, ?\Closure $minEnchantingPower = null, int $enchantingPowerRange = 50){
parent::__construct($name, $rarity, $primaryItemFlags, $secondaryItemFlags, $maxLevel, $minEnchantingPower, $enchantingPowerRange);
$this->typeModifier = $typeModifier;
if($applicableDamageTypes !== null){

View File

@ -59,47 +59,224 @@ final class VanillaEnchantments{
use RegistryTrait;
protected static function setup() : void{
self::register("PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_all(), Rarity::COMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 0.75, null));
self::register("FIRE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fire(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.25, [
EntityDamageEvent::CAUSE_FIRE,
EntityDamageEvent::CAUSE_FIRE_TICK,
EntityDamageEvent::CAUSE_LAVA
//TODO: check fireballs
]));
self::register("FEATHER_FALLING", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fall(), Rarity::UNCOMMON, ItemFlags::FEET, ItemFlags::NONE, 4, 2.5, [
EntityDamageEvent::CAUSE_FALL
]));
self::register("BLAST_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_explosion(), Rarity::RARE, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
]));
self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_projectile(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_PROJECTILE
]));
self::register("THORNS", new Enchantment(KnownTranslationFactory::enchantment_thorns(), Rarity::MYTHIC, ItemFlags::TORSO, ItemFlags::HEAD | ItemFlags::LEGS | ItemFlags::FEET, 3));
self::register("RESPIRATION", new Enchantment(KnownTranslationFactory::enchantment_oxygen(), Rarity::RARE, ItemFlags::HEAD, ItemFlags::NONE, 3));
self::register("PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_all(),
Rarity::COMMON,
0,
0,
4,
0.75,
null,
fn(int $level) : int => 11 * ($level - 1) + 1,
20
));
self::register("FIRE_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_fire(),
Rarity::UNCOMMON,
0,
0,
4,
1.25,
[
EntityDamageEvent::CAUSE_FIRE,
EntityDamageEvent::CAUSE_FIRE_TICK,
EntityDamageEvent::CAUSE_LAVA
//TODO: check fireballs
],
fn(int $level) : int => 8 * ($level - 1) + 10,
12
));
self::register("FEATHER_FALLING", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_fall(),
Rarity::UNCOMMON,
0,
0,
4,
2.5,
[
EntityDamageEvent::CAUSE_FALL
],
fn(int $level) : int => 6 * ($level - 1) + 5,
10
));
self::register("BLAST_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_explosion(),
Rarity::RARE,
0,
0,
4,
1.5,
[
EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
],
fn(int $level) : int => 8 * ($level - 1) + 5,
12
));
self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_projectile(),
Rarity::UNCOMMON,
0,
0,
4,
1.5,
[
EntityDamageEvent::CAUSE_PROJECTILE
],
fn(int $level) : int => 6 * ($level - 1) + 3,
15
));
self::register("THORNS", new Enchantment(
KnownTranslationFactory::enchantment_thorns(),
Rarity::MYTHIC,
0,
0,
3,
fn(int $level) : int => 20 * ($level - 1) + 10,
50
));
self::register("RESPIRATION", new Enchantment(
KnownTranslationFactory::enchantment_oxygen(),
Rarity::RARE,
0,
0,
3,
fn(int $level) : int => 10 * $level,
30
));
self::register("SHARPNESS", new SharpnessEnchantment(KnownTranslationFactory::enchantment_damage_all(), Rarity::COMMON, ItemFlags::SWORD, ItemFlags::AXE, 5));
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
self::register("SHARPNESS", new SharpnessEnchantment(
KnownTranslationFactory::enchantment_damage_all(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 11 * ($level - 1) + 1,
20
));
self::register("KNOCKBACK", new KnockbackEnchantment(
KnownTranslationFactory::enchantment_knockback(),
Rarity::UNCOMMON,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 5,
50
));
self::register("FIRE_ASPECT", new FireAspectEnchantment(
KnownTranslationFactory::enchantment_fire(),
Rarity::RARE,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 10,
50
));
//TODO: smite, bane of arthropods, looting (these don't make sense now because their applicable mobs don't exist yet)
self::register("KNOCKBACK", new KnockbackEnchantment(KnownTranslationFactory::enchantment_knockback(), Rarity::UNCOMMON, ItemFlags::SWORD, ItemFlags::NONE, 2));
self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2));
self::register("EFFICIENCY", new Enchantment(
KnownTranslationFactory::enchantment_digging(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 10 * ($level - 1) + 1,
50
));
self::register("FORTUNE", new Enchantment(
KnownTranslationFactory::enchantment_lootBonusDigger(),
Rarity::RARE,
0,
0,
3,
fn(int $level) : int => 9 * ($level - 1) + 15,
50
));
self::register("SILK_TOUCH", new Enchantment(
KnownTranslationFactory::enchantment_untouching(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 15,
50
));
self::register("UNBREAKING", new Enchantment(
KnownTranslationFactory::enchantment_durability(),
Rarity::UNCOMMON,
0,
0,
3,
fn(int $level) : int => 8 * ($level - 1) + 5,
50
));
self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5));
self::register("FORTUNE", new Enchantment(KnownTranslationFactory::enchantment_lootBonusDigger(), Rarity::RARE, ItemFlags::DIG, ItemFlags::NONE, 3));
self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1));
self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3));
self::register("POWER", new Enchantment(
KnownTranslationFactory::enchantment_arrowDamage(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 10 * ($level - 1) + 1,
15
));
self::register("PUNCH", new Enchantment(
KnownTranslationFactory::enchantment_arrowKnockback(),
Rarity::RARE,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 12,
25
));
self::register("FLAME", new Enchantment(
KnownTranslationFactory::enchantment_arrowFire(),
Rarity::RARE,
0,
0,
1,
fn(int $level) : int => 20,
30
));
self::register("INFINITY", new Enchantment(
KnownTranslationFactory::enchantment_arrowInfinite(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 20,
30
));
self::register("POWER", new Enchantment(KnownTranslationFactory::enchantment_arrowDamage(), Rarity::COMMON, ItemFlags::BOW, ItemFlags::NONE, 5));
self::register("PUNCH", new Enchantment(KnownTranslationFactory::enchantment_arrowKnockback(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 2));
self::register("FLAME", new Enchantment(KnownTranslationFactory::enchantment_arrowFire(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 1));
self::register("INFINITY", new Enchantment(KnownTranslationFactory::enchantment_arrowInfinite(), Rarity::MYTHIC, ItemFlags::BOW, ItemFlags::NONE, 1));
self::register("MENDING", new Enchantment(
KnownTranslationFactory::enchantment_mending(),
Rarity::RARE,
0,
0,
1,
fn(int $level) : int => 25,
50
));
self::register("MENDING", new Enchantment(KnownTranslationFactory::enchantment_mending(), Rarity::RARE, ItemFlags::NONE, ItemFlags::ALL, 1));
self::register("VANISHING", new Enchantment(
KnownTranslationFactory::enchantment_curse_vanishing(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 25,
25
));
self::register("VANISHING", new Enchantment(KnownTranslationFactory::enchantment_curse_vanishing(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::ALL, 1));
self::register("SWIFT_SNEAK", new Enchantment(KnownTranslationFactory::enchantment_swift_sneak(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::LEGS, 3));
self::register("SWIFT_SNEAK", new Enchantment(
KnownTranslationFactory::enchantment_swift_sneak(),
Rarity::MYTHIC,
0,
0,
3,
fn(int $level) : int => 10 * $level,
5
));
}
protected static function register(string $name, Enchantment $member) : void{

View File

@ -35,9 +35,12 @@ use pocketmine\block\inventory\LoomInventory;
use pocketmine\block\inventory\SmithingTableInventory;
use pocketmine\block\inventory\StonecutterInventory;
use pocketmine\crafting\FurnaceType;
use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\EnchantOption;
use pocketmine\network\mcpe\cache\CreativeInventoryCache;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
@ -46,7 +49,10 @@ use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\Enchant;
use pocketmine\network\mcpe\protocol\types\EnchantOption as ProtocolEnchantOption;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
@ -58,6 +64,7 @@ use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
use function array_keys;
use function array_map;
use function array_search;
use function count;
use function get_class;
@ -103,6 +110,12 @@ class InventoryManager{
private bool $fullSyncRequested = false;
/** @var int[] network recipe ID => enchanting table option index */
private array $enchantingTableOptions = [];
//TODO: this should be based on the total number of crafting recipes - if there are ever 100k recipes, this will
//conflict with regular recipes
private int $nextEnchantingTableOptionId = 100000;
public function __construct(
private Player $player,
private NetworkSession $session
@ -382,6 +395,7 @@ class InventoryManager{
throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed");
}
$this->pendingCloseWindowId = $this->lastInventoryNetworkId;
$this->enchantingTableOptions = [];
}
}
@ -603,6 +617,39 @@ class InventoryManager{
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory()));
}
/**
* @param EnchantOption[] $options
*/
public function syncEnchantingTableOptions(array $options) : void{
$protocolOptions = [];
foreach($options as $index => $option){
$optionId = $this->nextEnchantingTableOptionId++;
$this->enchantingTableOptions[$optionId] = $index;
$protocolEnchantments = array_map(
fn(EnchantmentInstance $e) => new Enchant(EnchantmentIdMap::getInstance()->toId($e->getType()), $e->getLevel()),
$option->getEnchantments()
);
// We don't pay attention to the $slotFlags, $heldActivatedEnchantments and $selfActivatedEnchantments
// as everything works fine without them (perhaps these values are used somehow in the BDS).
$protocolOptions[] = new ProtocolEnchantOption(
$option->getRequiredXpLevel(),
0, $protocolEnchantments,
[],
[],
$option->getDisplayName(),
$optionId
);
}
$this->session->sendDataPacket(PlayerEnchantOptionsPacket::create($protocolOptions));
}
public function getEnchantingTableOptionIndex(int $recipeId) : ?int{
return $this->enchantingTableOptions[$recipeId] ?? null;
}
private function newItemStackId() : int{
return $this->nextItemStackId++;
}

View File

@ -23,11 +23,13 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\EnchantTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionBuilderInventory;
@ -287,7 +289,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
private function assertDoingCrafting() : void{
if(!$this->specialTransaction instanceof CraftingTransaction){
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantTransaction){
if($this->specialTransaction === null){
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
}else{
@ -333,7 +335,16 @@ class ItemStackRequestExecutor{
$this->setNextCreatedItem($item, true);
}elseif($action instanceof CraftRecipeStackRequestAction){
$this->beginCrafting($action->getRecipeId(), 1);
$window = $this->player->getCurrentWindow();
if($window instanceof EnchantInventory){
$optionId = $this->inventoryManager->getEnchantingTableOptionIndex($action->getRecipeId());
if($optionId !== null && ($option = $window->getOption($optionId)) !== null){
$this->specialTransaction = new EnchantTransaction($this->player, $option, $optionId + 1);
$this->setNextCreatedItem($window->getOutput($optionId));
}
}else{
$this->beginCrafting($action->getRecipeId(), 1);
}
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
}elseif($action instanceof CraftingConsumeInputStackRequestAction){

View File

@ -52,9 +52,9 @@ final class GeneratorManager{
}
});
$this->addGenerator(Normal::class, "normal", fn() => null);
$this->addGenerator(Normal::class, "default", fn() => null);
$this->addGenerator(Nether::class, "hell", fn() => null);
$this->addAlias("normal", "default");
$this->addGenerator(Nether::class, "nether", fn() => null);
$this->addAlias("nether", "hell");
}
/**
@ -80,6 +80,22 @@ final class GeneratorManager{
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator);
}
/**
* Aliases an already-registered generator name to another name. Useful if you want to map a generator name to an
* existing generator without having to replicate the parameters.
*/
public function addAlias(string $name, string $alias) : void{
$name = strtolower($name);
$alias = strtolower($alias);
if(!isset($this->list[$name])){
throw new \InvalidArgumentException("Alias \"$name\" is not assigned");
}
if(isset($this->list[$alias])){
throw new \InvalidArgumentException("Alias \"$alias\" is already assigned");
}
$this->list[$alias] = $this->list[$name];
}
/**
* Returns a list of names for registered generators.
*