1
0

Fortune Drops (#4932)

+ Implemented and standardized all clamped discrete random drops.
+ Changed cItems Add from push_back to emplace_back. Implement fortune for crops.
+ Enabled hoes to be enchanted with efficiency, silk touch and fortune. Made leaves, gravel and crops affected by fortune.

Co-authored-by: Tiger Wang <ziwei.tiger@outlook.com>
This commit is contained in:
KingCol13 2020-09-28 13:41:49 +01:00 committed by GitHub
parent 32af227fa7
commit 8eca58a1c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 149 additions and 72 deletions

View File

@ -65,15 +65,23 @@ private:
auto flowerType = a_BlockMeta & 0x07;
if (flowerType == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS)
{
if (GetRandomProvider().RandBool(1.0 / 24.0))
// Drop seeds, depending on bernoulli trial result:
if (GetRandomProvider().RandBool(0.875))
{
return cItem(E_ITEM_SEEDS);
// 87.5% chance of dropping nothing:
return {};
}
// 12.5% chance of dropping some seeds.
const auto DropNum = FortuneDiscreteRandom(1, 1, 2 * ToolFortuneLevel(a_Tool));
return cItem(E_ITEM_SEEDS, DropNum);
}
else if (flowerType != E_META_BIG_FLOWER_LARGE_FERN)
{
return cItem(m_BlockType, 1, static_cast<short>(flowerType));
}
return {};
}

View File

@ -21,6 +21,17 @@ public:
private:
/** Calculate the number of seeds to drop when the crop is broken. */
static char CalculateSeedCount(char a_Min, char a_BaseRolls, char a_FortuneLevel)
{
std::binomial_distribution Binomial(a_BaseRolls + a_FortuneLevel, 0.57);
return a_Min + Binomial(GetRandomProvider().Engine());
}
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, const cEntity * a_Digger, const cItem * a_Tool) const override
{
auto & rand = GetRandomProvider();
@ -30,11 +41,12 @@ private:
{
switch (m_BlockType)
{
case E_BLOCK_BEETROOTS: return cItem(E_ITEM_BEETROOT_SEEDS, 1, 0); break;
case E_BLOCK_CROPS: return cItem(E_ITEM_SEEDS, 1, 0); break;
case E_BLOCK_CARROTS: return cItem(E_ITEM_CARROT, 1, 0); break;
case E_BLOCK_POTATOES: return cItem(E_ITEM_POTATO, 1, 0); break;
case E_BLOCK_BEETROOTS: return cItem(E_ITEM_BEETROOT_SEEDS);
case E_BLOCK_CROPS: return cItem(E_ITEM_SEEDS);
case E_BLOCK_CARROTS: return cItem(E_ITEM_CARROT);
case E_BLOCK_POTATOES: return cItem(E_ITEM_POTATO);
}
ASSERT(!"Unhandled block type");
return {};
}
@ -45,30 +57,32 @@ private:
{
case E_BLOCK_BEETROOTS:
{
char SeedCount = 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2); // [1 .. 3] with high preference of 2
res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount, 0);
char BeetrootCount = 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2); // [1 .. 3] with high preference of 2
res.Add(E_ITEM_BEETROOT, BeetrootCount, 0);
const auto SeedCount = CalculateSeedCount(0, 3, ToolFortuneLevel(a_Tool));
res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount);
res.Add(E_ITEM_BEETROOT);
break;
}
case E_BLOCK_CROPS:
{
res.Add(E_ITEM_WHEAT, 1, 0);
res.Add(E_ITEM_SEEDS, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
res.Add(E_ITEM_WHEAT);
const auto SeedCount = CalculateSeedCount(1, 3, ToolFortuneLevel(a_Tool));
res.Add(E_ITEM_SEEDS, SeedCount);
break;
}
case E_BLOCK_CARROTS:
{
res.Add(E_ITEM_CARROT, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
const auto CarrotCount = CalculateSeedCount(1, 4, ToolFortuneLevel(a_Tool));
res.Add(E_ITEM_CARROT, CarrotCount);
break;
}
case E_BLOCK_POTATOES:
{
res.Add(E_ITEM_POTATO, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
if (rand.RandBool(0.05))
const auto PotatoCount = CalculateSeedCount(2, 3, ToolFortuneLevel(a_Tool));
res.Add(E_ITEM_POTATO, PotatoCount);
if (rand.RandBool(0.02))
{
// With a 5% chance, drop a poisonous potato as well
res.emplace_back(E_ITEM_POISONOUS_POTATO, 1, 0);
// With a 2% chance, drop a poisonous potato as well
res.Add(E_ITEM_POISONOUS_POTATO);
}
break;
}

View File

@ -21,16 +21,12 @@ private:
// Drop self only when using silk-touch:
if (ToolHasSilkTouch(a_Tool))
{
return cItem(E_BLOCK_GLOWSTONE, 1, 0);
return cItem(E_BLOCK_GLOWSTONE);
}
// Number of dust to drop, capped at the max amount of 4.
const auto Drops = std::min(
static_cast<char>(4),
GetRandomProvider().RandInt<char>(2, 4 + ToolFortuneLevel(a_Tool))
);
return cItem(E_ITEM_GLOWSTONE_DUST, Drops);
const auto DropNum = FortuneDiscreteRandom(2, 4, ToolFortuneLevel(a_Tool), 4);
return cItem(E_ITEM_GLOWSTONE_DUST, DropNum);
}

View File

@ -18,15 +18,19 @@ private:
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, const cEntity * a_Digger, const cItem * a_Tool) const override
{
// TODO: Handle the Fortune and Silk touch enchantments here
if (GetRandomProvider().RandBool(0.10))
if (ToolHasSilkTouch(a_Tool))
{
return cItem(E_ITEM_FLINT, 1, 0);
return cItem(E_BLOCK_GRAVEL);
}
else
// Denominator of probability from wiki, don't let it go below 1.
const auto Denominator = std::max(10 - 3 * ToolFortuneLevel(a_Tool), 1);
if (GetRandomProvider().RandBool(1.0 / Denominator))
{
return cItem(E_BLOCK_GRAVEL, 1, 0);
return cItem(E_ITEM_FLINT);
}
return cItem(E_BLOCK_GRAVEL);
}

View File

@ -658,6 +658,19 @@ unsigned char cBlockHandler::ToolFortuneLevel(const cItem * a_Tool)
char cBlockHandler::FortuneDiscreteRandom(char a_MinDrop, char a_DefaultMax, unsigned char a_BonusMax, char a_DropCap)
{
// First sample the discrete random distribution.
char DropNum = GetRandomProvider().RandInt<char>(a_MinDrop, a_DefaultMax + a_BonusMax);
// Then clamp to within range (clamp instead of min incase of overflow):
return std::clamp<char>(DropNum, a_MinDrop, a_DropCap);
}
const cBlockHandler & cBlockHandler::For(BLOCKTYPE a_BlockType)
{
// Switch on the block type, as an enumeration

View File

@ -227,6 +227,15 @@ public:
Can be used in ConvertToPickups() implementations. */
static unsigned char ToolFortuneLevel(const cItem * a_Tool);
/** Returns a random number of drops taking into account fortune.
Only applies to drops following clamped discrete random distribution.
a_DefaultMax is the maximum items from one block without fortune.
a_BonusMax is the amount to increase the max of randInt by, usually the fortune level (but not always)
a_DropCap is the maximum items from one block with fortune,
if unspecified set to 25 to prevent lag or crash with high level tools.
Similar to uniform_bonus_count at https://minecraft.gamepedia.com/Loot_table#Functions */
static char FortuneDiscreteRandom(char a_MinDrop, char a_DefaultMax, unsigned char a_BonusMax, char a_DropCap = 25);
// Gets the blockhandler for the given block type.
static const cBlockHandler & For(BLOCKTYPE a_BlockType);

View File

@ -25,6 +25,23 @@ public:
private:
static double FortuneDropProbability(unsigned char a_DefaultDenominator, unsigned char a_FirstDenominatorReduction, unsigned char a_FortuneLevel)
{
// Fortune 3 behaves like fortune 4 for some reason
if (a_FortuneLevel == 3)
{
a_FortuneLevel++;
}
// Denominator, capped at minimum of 10.
const auto Denominator = std::max<unsigned char>(10, a_DefaultDenominator - a_FortuneLevel * a_FirstDenominatorReduction);
return 1.0 / Denominator;
}
/** Returns true if the area contains a continous path from the specified block to a log block entirely made out of leaves blocks. */
static bool HasNearLog(cBlockArea & a_Area, const Vector3i a_BlockPos)
{
@ -98,44 +115,56 @@ private:
// If breaking with shears, drop self:
if ((a_Tool != nullptr) && (a_Tool->m_ItemType == E_ITEM_SHEARS))
{
return cItem(m_BlockType, a_BlockMeta & 0x03);
return cItem(m_BlockType, 1, a_BlockMeta & 0x03);
}
// There is a chance to drop a sapling that varies depending on the type of leaf broken.
// Note: It is possible (though very rare) for a single leaves block to drop both a sapling and an apple
// TODO: Take into account fortune for sapling drops.
double chance = 0.0;
auto & rand = GetRandomProvider();
cItems res;
double DropProbability;
const auto FortuneLevel = ToolFortuneLevel(a_Tool);
auto & Random = GetRandomProvider();
cItems Res;
if ((m_BlockType == E_BLOCK_LEAVES) && ((a_BlockMeta & 0x03) == E_META_LEAVES_JUNGLE))
{
// Jungle leaves have a 2.5% chance of dropping a sapling.
chance = 0.025;
// Jungle leaves have a 2.5% default chance of dropping a sapling.
DropProbability = FortuneDropProbability(40, 4, FortuneLevel);
}
else
{
// Other leaves have a 5% chance of dropping a sapling.
chance = 0.05;
// Other leaves have a 5% default chance of dropping a sapling.
DropProbability = FortuneDropProbability(20, 4, FortuneLevel);
}
if (rand.RandBool(chance))
if (Random.RandBool(DropProbability))
{
res.Add(
Res.Add(
E_BLOCK_SAPLING,
1,
(m_BlockType == E_BLOCK_LEAVES) ? (a_BlockMeta & 0x03) : static_cast<short>(4 + (a_BlockMeta & 0x01))
);
}
// 0.5 % chance of dropping an apple, if the leaves' type is Apple Leaves
// 0.5 % chance of dropping an apple, increased by fortune, if the leaves' type is Apple Leaves
if ((m_BlockType == E_BLOCK_LEAVES) && ((a_BlockMeta & 0x03) == E_META_LEAVES_APPLE))
{
if (rand.RandBool(0.005))
DropProbability = FortuneDropProbability(200, 20, FortuneLevel);
if (Random.RandBool(DropProbability))
{
res.Add(E_ITEM_RED_APPLE, 1, 0);
Res.Add(E_ITEM_RED_APPLE);
}
}
return res;
// 2% chance of dropping sticks (yuck) in 1.14
DropProbability = FortuneDropProbability(50, 5, FortuneLevel);
if (Random.RandBool(DropProbability))
{
// 1 or 2 sticks are dropped on success:
Res.Add(E_ITEM_STICK, Random.RandInt<char>(1, 2));
}
return Res;
}

View File

@ -20,7 +20,8 @@ private:
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, const cEntity * a_Digger, const cItem * a_Tool) const override
{
return cItem(E_ITEM_MELON_SLICE, GetRandomProvider().RandInt<char>(3, 7), 0);
const auto DropNum = FortuneDiscreteRandom(3, 7, ToolFortuneLevel(a_Tool), 9);
return cItem(E_ITEM_MELON_SLICE, DropNum);
}

View File

@ -24,13 +24,11 @@ private:
if (a_BlockMeta == 0x03)
{
// Fully grown, drop the entire produce:
auto & rand = GetRandomProvider();
return cItem(E_ITEM_NETHER_WART, 1 + (rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2, 0);
}
else
{
return cItem(E_ITEM_NETHER_WART);
const auto DropNum = FortuneDiscreteRandom(2, 4, ToolFortuneLevel(a_Tool));
return cItem(E_ITEM_NETHER_WART, DropNum);
}
return cItem(E_ITEM_NETHER_WART);
}

View File

@ -31,19 +31,23 @@ private:
}
}
auto & Random = GetRandomProvider();
const auto FortuneLevel = ToolFortuneLevel(a_Tool);
const auto Drops = std::max(static_cast<char>(1), FloorC<char>(Random.RandReal(FortuneLevel + 2.0)));
if ((m_BlockType == E_BLOCK_REDSTONE_ORE) || (m_BlockType == E_BLOCK_REDSTONE_ORE_GLOWING))
{ // Redstone follows the discrete random distribution, unlike other ores
const auto DropNum = FortuneDiscreteRandom(4, 5, FortuneLevel);
return cItem(E_ITEM_REDSTONE_DUST, DropNum);
}
auto & Random = GetRandomProvider();
const auto DropMult = std::max(static_cast<char>(1), FloorC<char>(Random.RandReal(FortuneLevel + 2.0)));
switch (m_BlockType)
{
case E_BLOCK_LAPIS_ORE: return cItem(E_ITEM_DYE, Drops * Random.RandInt<char>(4, 9), 4);
case E_BLOCK_REDSTONE_ORE: // Handled by next case (glowing redstone)
case E_BLOCK_REDSTONE_ORE_GLOWING: return cItem(E_ITEM_REDSTONE_DUST, Random.RandInt<char>(4, 5 + FortuneLevel));
case E_BLOCK_DIAMOND_ORE: return cItem(E_ITEM_DIAMOND, Drops);
case E_BLOCK_EMERALD_ORE: return cItem(E_ITEM_EMERALD, Drops);
case E_BLOCK_COAL_ORE: return cItem(E_ITEM_COAL, Drops);
case E_BLOCK_NETHER_QUARTZ_ORE: return cItem(E_ITEM_NETHER_QUARTZ, Drops);
case E_BLOCK_LAPIS_ORE: return cItem(E_ITEM_DYE, DropMult * Random.RandInt<char>(4, 9), 4);
case E_BLOCK_DIAMOND_ORE: return cItem(E_ITEM_DIAMOND, DropMult);
case E_BLOCK_EMERALD_ORE: return cItem(E_ITEM_EMERALD, DropMult);
case E_BLOCK_COAL_ORE: return cItem(E_ITEM_COAL, DropMult);
case E_BLOCK_NETHER_QUARTZ_ORE: return cItem(E_ITEM_NETHER_QUARTZ, DropMult);
case E_BLOCK_CLAY: return cItem(E_ITEM_CLAY, 4);
default:
{

View File

@ -23,15 +23,11 @@ private:
// Drop self only when using silk-touch:
if (ToolHasSilkTouch(a_Tool))
{
return cItem(E_BLOCK_SEA_LANTERN, 1, 0);
return cItem(E_BLOCK_SEA_LANTERN);
}
// Number of crystals to drop, capped at the max amount of 5.
const auto Drops = std::min(
static_cast<char>(5),
GetRandomProvider().RandInt<char>(2, 3 + ToolFortuneLevel(a_Tool))
);
return cItem(E_ITEM_PRISMARINE_CRYSTALS, Drops);
const auto DropNum = FortuneDiscreteRandom(2, 3, ToolFortuneLevel(a_Tool), 5);
return cItem(E_ITEM_PRISMARINE_CRYSTALS, DropNum);
}
} ;

View File

@ -37,12 +37,15 @@ private:
return cItem(m_BlockType, 1, a_BlockMeta);
}
// Drop seeds, sometimes:
if (GetRandomProvider().RandBool(0.125))
// Drop seeds, depending on bernoulli trial result:
if (GetRandomProvider().RandBool(0.875)) // 87.5% chance of dropping nothing
{
return cItem(E_ITEM_SEEDS);
return {};
}
return {};
// 12.5% chance of dropping 0 or more seeds.
const auto DropNum = FortuneDiscreteRandom(1, 1, 2 * ToolFortuneLevel(a_Tool));
return cItem(E_ITEM_SEEDS, DropNum);
}

View File

@ -230,6 +230,8 @@ public:
cItem * Get (int a_Idx);
void Set (int a_Idx, const cItem & a_Item);
void Add (const cItem & a_Item) {push_back(a_Item); }
void Add (short a_ItemType) { emplace_back(a_ItemType); }
void Add (short a_ItemType, char a_ItemCount) { emplace_back(a_ItemType, a_ItemCount); }
void Delete(int a_Idx);
void Clear (void) {clear(); }
size_t Size (void) const { return size(); }
@ -239,7 +241,7 @@ public:
void Add (short a_ItemType, char a_ItemCount, short a_ItemDamage)
{
push_back(cItem(a_ItemType, a_ItemCount, a_ItemDamage));
emplace_back(a_ItemType, a_ItemCount, a_ItemDamage);
}
/** Adds a copy of all items in a_ItemGrid. */