From 672bb0457012612ef59502b33717ee789c4d6bfe Mon Sep 17 00:00:00 2001 From: 0ddlyoko Date: Fri, 6 Nov 2020 17:54:01 +0100 Subject: Add correct implementation of crops (#4802) * [FIX] Add correct implementation of seed drops. > Official percentage of drops has been implemented * Fix C++ conventions * Change "Vals" variable to "m_Vals" * [FIX] Add correct implementation of Carrots, Potatoes, Wheat & Beetroots seed * Add Fortune support with crops Add fortune support with Wheat, Carrots, Potatoes & Beetroots seeds * [FIX] Right-clicking on a grown Beetroot in survival consume 2 bone meals Fix #4805 * Add documentation for "cWorld::IsFullGrownPlantAt" method * Fix dispenser that full grown a plant > Change methods cItemDyeHandler::FertilizePlant & cItemDyeHandler::growPlantsAround to static * Display particle even if tree doesn't grow * When right-clicking on a full grown melon / pumpkin seed, no longer produce a melon / pumpkin Before this commit, when you right-click on a melon or a pumpkin seed, a melon / pumpkin block spawned. With this commit, it no longer spawns * [FIX] Do not create melon / pumpkin block when right-clicking with a bone meal This fix will prevent the creation of a melon / pumpkin block when you right-click with a bone meal on a melon / pumpkin plant - It just detect if the plant is full grown. if yes, the method "Grow" is not called - Remove IsFullGrownPlant Co-authored-by: 12xx12 <12xx12100@gmail.com> Co-authored-by: Tiger Wang --- src/Blocks/BlockCrops.h | 45 ++++++------- src/Blocks/BlockPlant.h | 11 +++- src/Blocks/BlockStems.h | 169 +++++++++++++++++++++++++++++++----------------- 3 files changed, 139 insertions(+), 86 deletions(-) (limited to 'src/Blocks') diff --git a/src/Blocks/BlockCrops.h b/src/Blocks/BlockCrops.h index aab2c18cd..fe90d6127 100644 --- a/src/Blocks/BlockCrops.h +++ b/src/Blocks/BlockCrops.h @@ -52,37 +52,42 @@ private: } // Fully grown, drop the crop's produce: - cItems res; + cItems Res; + switch (m_BlockType) { case E_BLOCK_BEETROOTS: { const auto SeedCount = CalculateSeedCount(0, 3, ToolFortuneLevel(a_Tool)); - res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount); - res.Add(E_ITEM_BEETROOT); + Res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount); + Res.Add(E_ITEM_BEETROOT); break; } case E_BLOCK_CROPS: { - res.Add(E_ITEM_WHEAT); + // https://minecraft.fandom.com/wiki/Seeds_(Wheat) + Res.Add(E_ITEM_WHEAT); const auto SeedCount = CalculateSeedCount(1, 3, ToolFortuneLevel(a_Tool)); - res.Add(E_ITEM_SEEDS, SeedCount); + Res.Add(E_ITEM_SEEDS, SeedCount); break; } case E_BLOCK_CARROTS: { + // https://minecraft.gamepedia.com/Carrot#Breaking const auto CarrotCount = CalculateSeedCount(1, 4, ToolFortuneLevel(a_Tool)); - res.Add(E_ITEM_CARROT, CarrotCount); + Res.Add(E_ITEM_CARROT, CarrotCount); break; } case E_BLOCK_POTATOES: { + // https://minecraft.gamepedia.com/Potato#Breaking const auto PotatoCount = CalculateSeedCount(2, 3, ToolFortuneLevel(a_Tool)); - res.Add(E_ITEM_POTATO, PotatoCount); + Res.Add(E_ITEM_POTATO, PotatoCount); if (rand.RandBool(0.02)) { - // With a 2% chance, drop a poisonous potato as well - res.Add(E_ITEM_POISONOUS_POTATO); + // https://minecraft.gamepedia.com/Poisonous_Potato#Obtaining + // With a 2% chance, drop a poisonous potato as well: + Res.Add(E_ITEM_POISONOUS_POTATO); } break; } @@ -92,7 +97,7 @@ private: break; } } // switch (m_BlockType) - return res; + return Res; } @@ -101,16 +106,10 @@ private: virtual int Grow(cChunk & a_Chunk, Vector3i a_RelPos, int a_NumStages = 1) const override { - auto oldMeta = a_Chunk.GetMeta(a_RelPos); - if (oldMeta >= RipeMeta) - { - // Already ripe - return 0; - } - auto newMeta = std::min(oldMeta + a_NumStages, RipeMeta); - ASSERT(newMeta > oldMeta); - a_Chunk.GetWorld()->SetBlock(a_Chunk.RelativeToAbsolute(a_RelPos), m_BlockType, static_cast(newMeta)); - return newMeta - oldMeta; + const auto OldMeta = a_Chunk.GetMeta(a_RelPos); + const auto NewMeta = std::clamp(static_cast(OldMeta + a_NumStages), 0, RipeMeta); + a_Chunk.SetMeta(a_RelPos, NewMeta); + return NewMeta - OldMeta; } @@ -131,8 +130,4 @@ private: UNUSED(a_Meta); return 7; } -} ; - - - - +}; diff --git a/src/Blocks/BlockPlant.h b/src/Blocks/BlockPlant.h index 71890b3cf..ab3f137a5 100644 --- a/src/Blocks/BlockPlant.h +++ b/src/Blocks/BlockPlant.h @@ -150,7 +150,10 @@ private: { case paGrowth: { - Grow(a_Chunk, a_RelPos); + if (Grow(a_Chunk, a_RelPos) == 0) + { + BearFruit(a_Chunk, a_RelPos); + } break; } case paDeath: @@ -161,4 +164,10 @@ private: case paStay: break; // do nothing } } + + /** Grows the final produce next to the stem at the specified position. */ + virtual void BearFruit(cChunk & a_Chunk, const Vector3i a_StemRelPos) const + { + // Nothing to do by default + } }; diff --git a/src/Blocks/BlockStems.h b/src/Blocks/BlockStems.h index be64d81f8..fa80d4127 100644 --- a/src/Blocks/BlockStems.h +++ b/src/Blocks/BlockStems.h @@ -24,7 +24,32 @@ private: virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, const cEntity * a_Digger, const cItem * a_Tool) const override { - return cItem(StemPickupType, 1, 0); + /* + Use correct percent: + https://minecraft.gamepedia.com/Melon_Seeds#Breaking + https://minecraft.gamepedia.com/Pumpkin_Seeds#Breaking + */ + + // Age > 7 (Impossible) + if (a_BlockMeta > 7) + { + return cItem(StemPickupType); + } + + const auto Threshold = GetRandomProvider().RandReal(100); + double Cumulative = 0; + char Count = 0; + + for (; Count < 4; Count++) + { + Cumulative += m_AgeSeedDropProbability[static_cast(a_BlockMeta)][static_cast(Count)]; + if (Cumulative > Threshold) + { + break; + } + } + + return cItem(StemPickupType, Count); } @@ -52,60 +77,51 @@ private: virtual int Grow(cChunk & a_Chunk, Vector3i a_RelPos, int a_NumStages = 1) const override { - auto oldMeta = a_Chunk.GetMeta(a_RelPos); - auto meta = oldMeta + a_NumStages; - a_Chunk.SetBlock(a_RelPos, m_BlockType, static_cast(std::min(meta, 7))); // Update the stem - if (meta > 7) - { - if (growProduce(a_Chunk, a_RelPos)) - { - return 8 - oldMeta; - } - else - { - return 7 - oldMeta; - } - } - return meta - oldMeta; + const auto OldMeta = a_Chunk.GetMeta(a_RelPos); + const auto NewMeta = std::clamp(static_cast(OldMeta + a_NumStages), 0, 7); + a_Chunk.SetMeta(a_RelPos, NewMeta); + return NewMeta - OldMeta; } - /** Grows the final produce next to the stem at the specified pos. - Returns true if successful, false if not. */ - static bool growProduce(cChunk & a_Chunk, Vector3i a_StemRelPos) + + + + + virtual void BearFruit(cChunk & a_Chunk, const Vector3i a_StemRelPos) const override { - auto & random = GetRandomProvider(); + auto & Random = GetRandomProvider(); // Check if there's another produce around the stem, if so, abort: - static const Vector3i neighborOfs[] = + static constexpr std::array NeighborOfs = { - { 1, 0, 0}, - {-1, 0, 0}, - { 0, 0, 1}, - { 0, 0, -1}, + { + { 1, 0, 0}, + {-1, 0, 0}, + { 0, 0, 1}, + { 0, 0, -1}, + } }; - bool isValid; - BLOCKTYPE blockType[4]; - NIBBLETYPE blockMeta; // unused - isValid = a_Chunk.UnboundedRelGetBlock(a_StemRelPos + neighborOfs[0], blockType[0], blockMeta); - isValid = isValid && a_Chunk.UnboundedRelGetBlock(a_StemRelPos + neighborOfs[1], blockType[1], blockMeta); - isValid = isValid && a_Chunk.UnboundedRelGetBlock(a_StemRelPos + neighborOfs[2], blockType[2], blockMeta); - isValid = isValid && a_Chunk.UnboundedRelGetBlock(a_StemRelPos + neighborOfs[3], blockType[3], blockMeta); + + std::array BlockType; if ( - !isValid || - (blockType[0] == ProduceBlockType) || - (blockType[1] == ProduceBlockType) || - (blockType[2] == ProduceBlockType) || - (blockType[3] == ProduceBlockType) + !a_Chunk.UnboundedRelGetBlockType(a_StemRelPos + NeighborOfs[0], BlockType[0]) || + !a_Chunk.UnboundedRelGetBlockType(a_StemRelPos + NeighborOfs[1], BlockType[1]) || + !a_Chunk.UnboundedRelGetBlockType(a_StemRelPos + NeighborOfs[2], BlockType[2]) || + !a_Chunk.UnboundedRelGetBlockType(a_StemRelPos + NeighborOfs[3], BlockType[3]) || + (BlockType[0] == ProduceBlockType) || + (BlockType[1] == ProduceBlockType) || + (BlockType[2] == ProduceBlockType) || + (BlockType[3] == ProduceBlockType) ) { - // Neighbors not valid or already taken by the same produce - return false; + // Neighbors not valid or already taken by the same produce: + return; } // Pick a direction in which to place the produce: int x = 0, z = 0; - int checkType = random.RandInt(3); // The index to the neighbors array which should be checked for emptiness - switch (checkType) + const auto CheckType = Random.RandInt(3); // The index to the neighbors array which should be checked for emptiness + switch (CheckType) { case 0: x = 1; break; case 1: x = -1; break; @@ -114,7 +130,7 @@ private: } // Check that the block in that direction is empty: - switch (blockType[checkType]) + switch (BlockType[CheckType]) { case E_BLOCK_AIR: case E_BLOCK_SNOW: @@ -123,39 +139,72 @@ private: { break; } - default: return false; + default: return; } // Check if there's soil under the neighbor. We already know the neighbors are valid. Place produce if ok - BLOCKTYPE soilType; - auto produceRelPos = a_StemRelPos + Vector3i(x, 0, z); - VERIFY(a_Chunk.UnboundedRelGetBlock(produceRelPos.addedY(-1), soilType, blockMeta)); - switch (soilType) + BLOCKTYPE SoilType; + const auto ProduceRelPos = a_StemRelPos + Vector3i(x, 0, z); + VERIFY(a_Chunk.UnboundedRelGetBlockType(ProduceRelPos.addedY(-1), SoilType)); + + switch (SoilType) { case E_BLOCK_DIRT: case E_BLOCK_GRASS: case E_BLOCK_FARMLAND: { - // Place a randomly-facing produce: - NIBBLETYPE meta = (ProduceBlockType == E_BLOCK_MELON) ? 0 : static_cast(random.RandInt(4) % 4); - auto produceAbsPos = a_Chunk.RelativeToAbsolute(produceRelPos); + const NIBBLETYPE Meta = (ProduceBlockType == E_BLOCK_MELON) ? 0 : static_cast(Random.RandInt(4) % 4); + FLOGD("Growing melon / pumpkin at {0} (<{1}, {2}> from stem), overwriting {3}, growing on top of {4}, meta {5}", - produceAbsPos, + a_Chunk.RelativeToAbsolute(ProduceRelPos), x, z, - ItemTypeToString(blockType[checkType]), - ItemTypeToString(soilType), - meta + ItemTypeToString(BlockType[CheckType]), + ItemTypeToString(SoilType), + Meta ); - a_Chunk.GetWorld()->SetBlock(produceAbsPos, ProduceBlockType, meta); - return true; + + // Place a randomly-facing produce: + a_Chunk.SetBlock(ProduceRelPos, ProduceBlockType, Meta); } } - return false; } + +private: + + // https://minecraft.gamepedia.com/Pumpkin_Seeds#Breaking + // https://minecraft.gamepedia.com/Melon_Seeds#Breaking + /** The array describes how many seed may be dropped at which age. The inner arrays describe the probability to drop 0, 1, 2, 3 seeds. + The outer describes the age of the stem. */ + static constexpr std::array, 8> m_AgeSeedDropProbability + { + { + { + 81.3, 17.42, 1.24, 0.03 + }, + { + 65.1, 30.04, 4.62, 0.24 + }, + { + 51.2, 38.4, 9.6, 0.8 + }, + { + 39.44, 43.02, 15.64, 1.9 + }, + { + 29.13, 44.44, 22.22, 3.7 + }, + { + 21.6, 43.2, 28.8, 6.4 + }, + { + 15.17, 39.82, 34.84, 10.16 + }, + { + 10.16, 34.84, 39.82, 15.17 + } + } + }; } ; using cBlockMelonStemHandler = cBlockStemsHandler; using cBlockPumpkinStemHandler = cBlockStemsHandler; - - - -- cgit v1.2.3