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:
parent
32af227fa7
commit
8eca58a1c9
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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:
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
} ;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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. */
|
||||
|
Loading…
Reference in New Issue
Block a user