Implement fortune enchantment (#5757)

This commit is contained in:
ShockedPlot7560 2023-07-17 12:13:45 +02:00 committed by GitHub
parent 8c8794ec71
commit fb6a7d279f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 220 additions and 49 deletions

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Beetroot extends Crops{
@ -33,7 +33,7 @@ class Beetroot extends Crops{
if($this->age >= self::MAX_AGE){
return [
VanillaItems::BEETROOT(),
VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3))
VanillaItems::BEETROOT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
];
}

View File

@ -23,15 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Carrot extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 4) : 1)
VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,7 @@ class CoalOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::COAL()
VanillaItems::COAL()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}

View File

@ -23,13 +23,16 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
final class CopperOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaItems::RAW_COPPER()];
return [
VanillaItems::RAW_COPPER()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 5)),
];
}
public function isAffectedBySilkTouch() : bool{ return true; }

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,7 @@ class DiamondOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::DIAMOND()
VanillaItems::DIAMOND()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}

View File

@ -23,9 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class DoubleTallGrass extends DoublePlant{
@ -34,8 +33,8 @@ class DoubleTallGrass extends DoublePlant{
}
public function getDropsForIncompatibleTool(Item $item) : array{
if($this->top && mt_rand(0, 7) === 0){
return [VanillaItems::WHEAT_SEEDS()];
if($this->top){
return FortuneDropHelper::grass($item);
}
return [];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,7 @@ class EmeraldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::EMERALD()
VanillaItems::EMERALD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -30,7 +31,7 @@ use function mt_rand;
final class GildedBlackstone extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
if(mt_rand(1, 10) === 1){
if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 5))];
}

View File

@ -23,9 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
use function min;
class Glowstone extends Transparent{
@ -35,7 +36,7 @@ class Glowstone extends Transparent{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::GLOWSTONE_DUST()->setCount(mt_rand(2, 4))
VanillaItems::GLOWSTONE_DUST()->setCount(min(4, FortuneDropHelper::discrete($item, 2, 4)))
];
}

View File

@ -25,15 +25,15 @@ namespace pocketmine\block;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Gravel extends Opaque implements Fallable{
use FallableTrait;
public function getDropsForCompatibleTool(Item $item) : array{
if(mt_rand(1, 10) === 1){
if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
return [
VanillaItems::FLINT()
];

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,7 @@ class LapisOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::LAPIS_LAZULI()->setCount(mt_rand(4, 8))
VanillaItems::LAPIS_LAZULI()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 9))
];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
@ -138,7 +139,8 @@ class Leaves extends Transparent{
}
$drops = [];
if(mt_rand(1, 20) === 1){ //Saplings
if(FortuneDropHelper::bonusChanceDivisor($item, 20, 4)){ //Saplings
// TODO: according to the wiki, the jungle saplings have a different drop rate
$sapling = (match($this->leavesType){
LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(),
LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(),
@ -155,10 +157,13 @@ class Leaves extends Transparent{
$drops[] = $sapling;
}
}
if(($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples
if(
($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) &&
FortuneDropHelper::bonusChanceDivisor($item, 200, 20)
){ //Apples
$drops[] = VanillaItems::APPLE();
}
if(mt_rand(1, 50) === 1){
if(FortuneDropHelper::bonusChanceDivisor($item, 50, 5)){
$drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2));
}

View File

@ -23,15 +23,16 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
use function min;
class Melon extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::MELON()->setCount(mt_rand(3, 7))
VanillaItems::MELON()->setCount(min(9, FortuneDropHelper::discrete($item, 3, 7)))
];
}

View File

@ -23,14 +23,14 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
final class NetherGoldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 6))];
return [VanillaItems::GOLD_NUGGET()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 6))];
}
public function isAffectedBySilkTouch() : bool{ return true; }

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,7 @@ class NetherQuartzOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::NETHER_QUARTZ()
VanillaItems::NETHER_QUARTZ()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
@ -179,7 +180,7 @@ class NetherVines extends Flowable{
}
public function getDropsForCompatibleTool(Item $item) : array{
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || mt_rand(1, 3) === 1){
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || FortuneDropHelper::bonusChanceFixed($item, 1 / 3, 2 / 9)){
return [$this->asItem()];
}
return [];

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
@ -85,7 +86,7 @@ class NetherWartPlant extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{
return [
$this->asItem()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 4) : 1)
$this->asItem()->setCount($this->age === self::MAX_AGE ? FortuneDropHelper::discrete($item, 2, 4) : 1)
];
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
@ -31,7 +32,8 @@ class Potato extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
$result = [
VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 5) : 1)
//min/max would be 2-5 in Java
VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
];
if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){
$result[] = VanillaItems::POISONOUS_POTATO();

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@ -81,7 +82,7 @@ class RedstoneOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::REDSTONE_DUST()->setCount(mt_rand(4, 5))
VanillaItems::REDSTONE_DUST()->setCount(FortuneDropHelper::discrete($item, 4, 5))
];
}

View File

@ -23,8 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function min;
class SeaLantern extends Transparent{
@ -34,7 +36,7 @@ class SeaLantern extends Transparent{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::PRISMARINE_CRYSTALS()->setCount(3)
VanillaItems::PRISMARINE_CRYSTALS()->setCount(min(5, FortuneDropHelper::discrete($item, 2, 3)))
];
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
@ -108,13 +109,14 @@ class SweetBerryBush extends Flowable{
}
public function getDropsForCompatibleTool(Item $item) : array{
if(($dropAmount = $this->getBerryDropAmount()) > 0){
return [
$this->asItem()->setCount($dropAmount)
];
}
return [];
$count = match($this->age){
self::STAGE_MATURE => FortuneDropHelper::discrete($item, 2, 3),
self::STAGE_BUSH_SOME_BERRIES => FortuneDropHelper::discrete($item, 1, 2),
default => 0
};
return [
$this->asItem()->setCount($count)
];
}
public function onNearbyBlockChange() : void{

View File

@ -23,13 +23,12 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class TallGrass extends Flowable{
@ -56,13 +55,7 @@ class TallGrass extends Flowable{
}
public function getDropsForIncompatibleTool(Item $item) : array{
if(mt_rand(0, 15) === 0){
return [
VanillaItems::WHEAT_SEEDS()
];
}
return [];
return FortuneDropHelper::grass($item);
}
public function getFlameEncouragement() : int{

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Wheat extends Crops{
@ -33,7 +33,7 @@ class Wheat extends Crops{
if($this->age >= self::MAX_AGE){
return [
VanillaItems::WHEAT(),
VanillaItems::WHEAT_SEEDS()->setCount(mt_rand(0, 3))
VanillaItems::WHEAT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
];
}else{
return [

View File

@ -0,0 +1,150 @@
<?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\block\utils;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function max;
use function min;
use function mt_getrandmax;
use function mt_rand;
final class FortuneDropHelper{
/**
* If a random number between 0-1 is greater than 2/(level+2), this multiplies the max drop amount by level+1, and
* picks a random amount between the minimum and multiplied maximum. Each level of fortune increases the chance of
* fortune activation, and also increases the maximum drop limit when activated.
*
* Otherwise, returns a random amount of the item between the minimum and original maximum.
*
* @param Item $usedItem The item used to break the block
* @param int $min Minimum amount
* @param int $maxBase Maximum amount, as if fortune level was 0
*
* @return int the number of items to drop
*/
public static function weighted(Item $usedItem, int $min, int $maxBase) : int{
if($maxBase < $min){
throw new \InvalidArgumentException("Maximum drop amount must be greater than or equal to minimum drop amount");
}
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
return mt_rand($min,
$fortuneLevel > 0 && mt_rand() / mt_getrandmax() > 2 / ($fortuneLevel + 2) ?
$maxBase * ($fortuneLevel + 1) :
$maxBase
);
}
/**
* Increases the drop amount according to a binomial distribution. The function will roll maxBase+level times, and add 1
* if a random number between 0-1 is less than the given probability. Each level of fortune adds one extra roll.
*
* As many as maxBase+level items can be dropped. This applies even if the fortune level is 0.
*
* @param float $chance The chance of adding 1 to the amount for each roll, must be in the range 0-1
* @param int $min Minimum amount
* @param int $minRolls Number of rolls if fortune level is 0, added to fortune level to calculate total rolls
*
* @return int the number of items to drop
*/
public static function binomial(Item $usedItem, int $min, int $minRolls = 3, float $chance = 4 / 7) : int{
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
$count = $min;
$rolls = $minRolls + $fortuneLevel;
for($i = 0; $i < $rolls; ++$i){
if(mt_rand() / mt_getrandmax() < $chance){
++$count;
}
}
return $count;
}
/**
* Grass have a fixed chance to drop wheat seed.
* Fortune level increases the maximum number of seeds that can be dropped.
* A discrete uniform distribution is used to determine the number of seeds dropped.
*
* TODO: I'm not sure this really belongs here, but it's preferable not to duplicate this code between grass and
* tall grass.
*
* @return Item[]
*/
public static function grass(Item $usedItem) : array{
if(FortuneDropHelper::bonusChanceDivisor($usedItem, 8, 2)){
return [
VanillaItems::WHEAT_SEEDS()
];
}
return [];
}
/**
* Adds the fortune level to the base max and picks a random number between the minimim and adjusted maximum.
* Each amount in the range has an equal chance of being picked.
*
* @param int $maxBase Maximum base amount, as if the fortune level was 0
*
* @return int the number of items to drop
*/
public static function discrete(Item $usedItem, int $min, int $maxBase) : int{
if($maxBase < $min){
throw new \InvalidArgumentException("Minimum base drop amount must be less than or equal to maximum base drop amount");
}
$max = $maxBase + $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
return mt_rand($min, $max);
}
/**
* Calculates a chance of getting an extra bonus drop by reducing the chance divisor by a given amount per fortune
* level.
*
* @param int $divisorBase The number to divide 1 by to get the chance, as if the fortune level was 0
* @param int $divisorSubtractPerLevel The amount to subtract from the divisor for each level of fortune
*
* @return bool whether the bonus drop should be added
*/
public static function bonusChanceDivisor(Item $usedItem, int $divisorBase, int $divisorSubtractPerLevel) : bool{
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
return mt_rand(1, max(1, $divisorBase - ($fortuneLevel * $divisorSubtractPerLevel))) === 1;
}
/**
* Calculates a chance of getting an extra bonus drop by increasing the chance by a fixed amount per fortune level.
*
* @param float $chanceBase The base chance of getting a bonus drop, as if the fortune level was 0
* @param float $addedChancePerLevel The amount to add to the chance for each level of fortune
*/
public static function bonusChanceFixed(Item $usedItem, float $chanceBase, float $addedChancePerLevel) : bool{
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
$chance = min(1, $chanceBase + ($fortuneLevel * $addedChancePerLevel));
return mt_rand() / mt_getrandmax() < $chance;
}
}

View File

@ -62,6 +62,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::FIRE_ASPECT, VanillaEnchantments::FIRE_ASPECT());
$this->register(EnchantmentIds::EFFICIENCY, VanillaEnchantments::EFFICIENCY());
$this->register(EnchantmentIds::FORTUNE, VanillaEnchantments::FORTUNE());
$this->register(EnchantmentIds::SILK_TOUCH, VanillaEnchantments::SILK_TOUCH());
$this->register(EnchantmentIds::UNBREAKING, VanillaEnchantments::UNBREAKING());

View File

@ -43,6 +43,7 @@ final class StringToEnchantmentParser extends StringToTParser{
$result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT());
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
$result->register("flame", fn() => VanillaEnchantments::FLAME());
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
$result->register("mending", fn() => VanillaEnchantments::MENDING());

View File

@ -39,6 +39,7 @@ use pocketmine\utils\RegistryTrait;
* @method static FireAspectEnchantment FIRE_ASPECT()
* @method static ProtectionEnchantment FIRE_PROTECTION()
* @method static Enchantment FLAME()
* @method static Enchantment FORTUNE()
* @method static Enchantment INFINITY()
* @method static KnockbackEnchantment KNOCKBACK()
* @method static Enchantment MENDING()
@ -85,6 +86,7 @@ final class VanillaEnchantments{
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, 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));