From 8eca58a1c9d3ea928b301de1ad772a46164372e1 Mon Sep 17 00:00:00 2001 From: KingCol13 <48412633+KingCol13@users.noreply.github.com> Date: Mon, 28 Sep 2020 13:41:49 +0100 Subject: 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 --- src/Blocks/BlockBigFlower.h | 12 +++++++-- src/Blocks/BlockCrops.h | 44 ++++++++++++++++++++++----------- src/Blocks/BlockGlowstone.h | 10 +++----- src/Blocks/BlockGravel.h | 14 +++++++---- src/Blocks/BlockHandler.cpp | 13 ++++++++++ src/Blocks/BlockHandler.h | 9 +++++++ src/Blocks/BlockLeaves.h | 59 +++++++++++++++++++++++++++++++++----------- src/Blocks/BlockMelon.h | 3 ++- src/Blocks/BlockNetherWart.h | 10 +++----- src/Blocks/BlockOre.h | 22 ++++++++++------- src/Blocks/BlockSeaLantern.h | 10 +++----- src/Blocks/BlockTallGrass.h | 11 ++++++--- src/Item.h | 4 ++- 13 files changed, 149 insertions(+), 72 deletions(-) diff --git a/src/Blocks/BlockBigFlower.h b/src/Blocks/BlockBigFlower.h index 848cdce16..5df20dedf 100644 --- a/src/Blocks/BlockBigFlower.h +++ b/src/Blocks/BlockBigFlower.h @@ -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(flowerType)); } + return {}; } diff --git a/src/Blocks/BlockCrops.h b/src/Blocks/BlockCrops.h index 1d612e685..827b26881 100644 --- a/src/Blocks/BlockCrops.h +++ b/src/Blocks/BlockCrops.h @@ -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(2) + rand.RandInt(2)) / 2); // [1 .. 3] with high preference of 2 - res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount, 0); - char BeetrootCount = 1 + ((rand.RandInt(2) + rand.RandInt(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(2) + rand.RandInt(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(2) + rand.RandInt(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(2) + rand.RandInt(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; } diff --git a/src/Blocks/BlockGlowstone.h b/src/Blocks/BlockGlowstone.h index 1e187084d..395233b44 100644 --- a/src/Blocks/BlockGlowstone.h +++ b/src/Blocks/BlockGlowstone.h @@ -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(4), - GetRandomProvider().RandInt(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); } diff --git a/src/Blocks/BlockGravel.h b/src/Blocks/BlockGravel.h index 972dea4e4..2e145f5e5 100644 --- a/src/Blocks/BlockGravel.h +++ b/src/Blocks/BlockGravel.h @@ -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); } diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index 0066a06a5..a10c5e48d 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -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(a_MinDrop, a_DefaultMax + a_BonusMax); + + // Then clamp to within range (clamp instead of min incase of overflow): + return std::clamp(DropNum, a_MinDrop, a_DropCap); +} + + + + + const cBlockHandler & cBlockHandler::For(BLOCKTYPE a_BlockType) { // Switch on the block type, as an enumeration diff --git a/src/Blocks/BlockHandler.h b/src/Blocks/BlockHandler.h index 55dbeb5ec..cc701ca8e 100644 --- a/src/Blocks/BlockHandler.h +++ b/src/Blocks/BlockHandler.h @@ -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); diff --git a/src/Blocks/BlockLeaves.h b/src/Blocks/BlockLeaves.h index c08f1b6bb..1f726f779 100644 --- a/src/Blocks/BlockLeaves.h +++ b/src/Blocks/BlockLeaves.h @@ -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(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(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(1, 2)); + } + + return Res; } diff --git a/src/Blocks/BlockMelon.h b/src/Blocks/BlockMelon.h index 09841e642..da021830c 100644 --- a/src/Blocks/BlockMelon.h +++ b/src/Blocks/BlockMelon.h @@ -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(3, 7), 0); + const auto DropNum = FortuneDiscreteRandom(3, 7, ToolFortuneLevel(a_Tool), 9); + return cItem(E_ITEM_MELON_SLICE, DropNum); } diff --git a/src/Blocks/BlockNetherWart.h b/src/Blocks/BlockNetherWart.h index 590a55772..dde6b17d4 100644 --- a/src/Blocks/BlockNetherWart.h +++ b/src/Blocks/BlockNetherWart.h @@ -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(2) + rand.RandInt(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); } diff --git a/src/Blocks/BlockOre.h b/src/Blocks/BlockOre.h index 98db5198d..95bffc75e 100644 --- a/src/Blocks/BlockOre.h +++ b/src/Blocks/BlockOre.h @@ -31,19 +31,23 @@ private: } } - auto & Random = GetRandomProvider(); const auto FortuneLevel = ToolFortuneLevel(a_Tool); - const auto Drops = std::max(static_cast(1), FloorC(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(1), FloorC(Random.RandReal(FortuneLevel + 2.0))); switch (m_BlockType) { - case E_BLOCK_LAPIS_ORE: return cItem(E_ITEM_DYE, Drops * Random.RandInt(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(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(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: { diff --git a/src/Blocks/BlockSeaLantern.h b/src/Blocks/BlockSeaLantern.h index 9804642ca..d3adc95f4 100644 --- a/src/Blocks/BlockSeaLantern.h +++ b/src/Blocks/BlockSeaLantern.h @@ -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(5), - GetRandomProvider().RandInt(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); } } ; diff --git a/src/Blocks/BlockTallGrass.h b/src/Blocks/BlockTallGrass.h index f0da32cf5..d43d3ad04 100644 --- a/src/Blocks/BlockTallGrass.h +++ b/src/Blocks/BlockTallGrass.h @@ -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); } diff --git a/src/Item.h b/src/Item.h index 2f7b1a238..d3f853170 100644 --- a/src/Item.h +++ b/src/Item.h @@ -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. */ -- cgit v1.2.3