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/BlockEntities/DispenserEntity.cpp | 79 ++++++++-------- src/Blocks/BlockCrops.h | 45 ++++----- src/Blocks/BlockPlant.h | 11 ++- src/Blocks/BlockStems.h | 169 ++++++++++++++++++++++------------ src/Chunk.cpp | 4 +- src/Items/ItemDye.h | 51 +++++----- src/World.cpp | 30 ------ src/World.h | 12 +-- 8 files changed, 213 insertions(+), 188 deletions(-) (limited to 'src') diff --git a/src/BlockEntities/DispenserEntity.cpp b/src/BlockEntities/DispenserEntity.cpp index d97d7198e..c2ce6cf7c 100644 --- a/src/BlockEntities/DispenserEntity.cpp +++ b/src/BlockEntities/DispenserEntity.cpp @@ -10,6 +10,7 @@ #include "../Entities/ProjectileEntity.h" #include "../Simulator/FluidSimulator.h" #include "../Items/ItemSpawnEgg.h" +#include "../Items/ItemDye.h" @@ -25,24 +26,24 @@ cDispenserEntity::cDispenserEntity(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) { - Vector3i dispRelCoord(GetRelPos()); - auto meta = a_Chunk.GetMeta(dispRelCoord); - AddDropSpenserDir(dispRelCoord, meta); - auto dispChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(dispRelCoord); - if (dispChunk == nullptr) + Vector3i DispRelCoord(GetRelPos()); + auto Meta = a_Chunk.GetMeta(DispRelCoord); + AddDropSpenserDir(DispRelCoord, Meta); + auto DispChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(DispRelCoord); + if (DispChunk == nullptr) { // Would dispense into / interact with a non-loaded chunk, ignore the tick return; } - BLOCKTYPE dispBlock = dispChunk->GetBlock(dispRelCoord); - auto dispAbsCoord = dispChunk->RelativeToAbsolute(dispRelCoord); + BLOCKTYPE DispBlock = DispChunk->GetBlock(DispRelCoord); + auto DispAbsCoord = DispChunk->RelativeToAbsolute(DispRelCoord); // Dispense the item: const cItem & SlotItem = m_Contents.GetSlot(a_SlotNum); - if (ItemCategory::IsMinecart(SlotItem.m_ItemType) && IsBlockRail(dispBlock)) // only actually place the minecart if there are rails! + if (ItemCategory::IsMinecart(SlotItem.m_ItemType) && IsBlockRail(DispBlock)) // only actually place the minecart if there are rails! { - if (m_World->SpawnMinecart(dispAbsCoord.x + 0.5, dispAbsCoord.y + 0.5, dispAbsCoord.z + 0.5, SlotItem.m_ItemType) != cEntity::INVALID_ID) + if (m_World->SpawnMinecart(DispAbsCoord.x + 0.5, DispAbsCoord.y + 0.5, DispAbsCoord.z + 0.5, SlotItem.m_ItemType) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -52,15 +53,15 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) { case E_ITEM_BUCKET: { - LOGD("Dispensing empty bucket in slot %d; dispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(dispBlock).c_str(), dispBlock); - switch (dispBlock) + LOGD("Dispensing empty bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); + switch (DispBlock) { case E_BLOCK_STATIONARY_WATER: case E_BLOCK_WATER: { if (ScoopUpLiquid(a_SlotNum, E_ITEM_WATER_BUCKET)) { - dispChunk->SetBlock(dispRelCoord, E_BLOCK_AIR, 0); + DispChunk->SetBlock(DispRelCoord, E_BLOCK_AIR, 0); } break; } @@ -69,7 +70,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) { if (ScoopUpLiquid(a_SlotNum, E_ITEM_LAVA_BUCKET)) { - dispChunk->SetBlock(dispRelCoord, E_BLOCK_AIR, 0); + DispChunk->SetBlock(DispRelCoord, E_BLOCK_AIR, 0); } break; } @@ -84,10 +85,10 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_WATER_BUCKET: { - LOGD("Dispensing water bucket in slot %d; dispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(dispBlock).c_str(), dispBlock); - if (EmptyLiquidBucket(dispBlock, a_SlotNum)) + LOGD("Dispensing water bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); + if (EmptyLiquidBucket(DispBlock, a_SlotNum)) { - dispChunk->SetBlock(dispRelCoord, E_BLOCK_WATER, 0); + DispChunk->SetBlock(DispRelCoord, E_BLOCK_WATER, 0); } else { @@ -98,10 +99,10 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_LAVA_BUCKET: { - LOGD("Dispensing lava bucket in slot %d; dispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(dispBlock).c_str(), dispBlock); - if (EmptyLiquidBucket(dispBlock, a_SlotNum)) + LOGD("Dispensing lava bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); + if (EmptyLiquidBucket(DispBlock, a_SlotNum)) { - dispChunk->SetBlock(dispRelCoord, E_BLOCK_LAVA, 0); + DispChunk->SetBlock(DispRelCoord, E_BLOCK_LAVA, 0); } else { @@ -112,10 +113,10 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_SPAWN_EGG: { - double MobX = 0.5 + dispAbsCoord.x; - double MobZ = 0.5 + dispAbsCoord.z; + double MobX = 0.5 + DispAbsCoord.x; + double MobZ = 0.5 + DispAbsCoord.z; auto MonsterType = cItemSpawnEggHandler::ItemDamageToMonsterType(m_Contents.GetSlot(a_SlotNum).m_ItemDamage); - if (m_World->SpawnMob(MobX, dispAbsCoord.y, MobZ, MonsterType, false) != cEntity::INVALID_ID) + if (m_World->SpawnMob(MobX, DispAbsCoord.y, MobZ, MonsterType, false) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -125,9 +126,9 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_BLOCK_TNT: { // Spawn a primed TNT entity, if space allows: - if (!cBlockInfo::IsSolid(dispBlock)) + if (!cBlockInfo::IsSolid(DispBlock)) { - m_World->SpawnPrimedTNT(Vector3d(0.5, 0.5, 0.5) + dispAbsCoord, 80, 0); // 80 ticks fuse, no initial velocity + m_World->SpawnPrimedTNT(Vector3d(0.5, 0.5, 0.5) + DispAbsCoord, 80, 0); // 80 ticks fuse, no initial velocity m_Contents.ChangeSlotCount(a_SlotNum, -1); } break; @@ -136,9 +137,9 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_FLINT_AND_STEEL: { // Spawn fire if the block in front is air. - if (dispBlock == E_BLOCK_AIR) + if (DispBlock == E_BLOCK_AIR) { - dispChunk->SetBlock(dispRelCoord, E_BLOCK_FIRE, 0); + DispChunk->SetBlock(DispRelCoord, E_BLOCK_FIRE, 0); bool ItemBroke = m_Contents.DamageItem(a_SlotNum, 1); @@ -152,7 +153,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_FIRE_CHARGE: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkFireCharge, GetShootVector(meta) * 20) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkFireCharge, GetShootVector(Meta) * 20) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -161,7 +162,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_ARROW: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkArrow, GetShootVector(meta) * 30 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkArrow, GetShootVector(Meta) * 30 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -170,7 +171,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_SNOWBALL: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkSnowball, GetShootVector(meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkSnowball, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -179,7 +180,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_EGG: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkEgg, GetShootVector(meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkEgg, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -188,7 +189,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_BOTTLE_O_ENCHANTING: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkExpBottle, GetShootVector(meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkExpBottle, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -197,7 +198,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_POTION: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkSplashPotion, GetShootVector(meta) * 20 + Vector3d(0, 1, 0), &SlotItem) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkSplashPotion, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0), &SlotItem) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -211,7 +212,9 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) DropFromSlot(a_Chunk, a_SlotNum); break; } - if (m_World->GrowRipePlant(dispAbsCoord.x, dispAbsCoord.y, dispAbsCoord.z, true)) + + // Simulate a right-click with bonemeal: + if (cItemDyeHandler::FertilizePlant(*m_World, DispAbsCoord)) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } @@ -225,13 +228,13 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_ACACIA_BOAT: case E_ITEM_DARK_OAK_BOAT: { - Vector3d spawnPos = dispAbsCoord; - if (IsBlockWater(dispBlock)) + Vector3d spawnPos = DispAbsCoord; + if (IsBlockWater(DispBlock)) { // Water next to the dispenser, spawn a boat above the water block spawnPos.y += 1; } - else if (IsBlockWater(dispChunk->GetBlock(dispRelCoord.addedY(-1)))) + else if (IsBlockWater(DispChunk->GetBlock(DispRelCoord.addedY(-1)))) { // Water one block below the dispenser, spawn a boat at the dispenser's Y level // No adjustment needed @@ -243,7 +246,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) break; } - spawnPos += GetShootVector(meta) * 0.8; // A boat is bigger than one block. Add the shoot vector to put it outside the dispenser. + spawnPos += GetShootVector(Meta) * 0.8; // A boat is bigger than one block. Add the shoot vector to put it outside the dispenser. spawnPos += Vector3d(0.5, 0.5, 0.5); if (m_World->SpawnBoat(spawnPos, cBoat::ItemToMaterial(SlotItem))) @@ -255,7 +258,7 @@ void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) case E_ITEM_FIREWORK_ROCKET: { - if (SpawnProjectileFromDispenser(dispAbsCoord, cProjectileEntity::pkFirework, GetShootVector(meta) * 20 + Vector3d(0, 1, 0), &SlotItem) != cEntity::INVALID_ID) + if (SpawnProjectileFromDispenser(DispAbsCoord, cProjectileEntity::pkFirework, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0), &SlotItem) != cEntity::INVALID_ID) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } 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; - - - diff --git a/src/Chunk.cpp b/src/Chunk.cpp index df9292fae..663713cf3 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -820,10 +820,12 @@ void cChunk::TickBlocks(void) // Tick random blocks, but the first one should be m_BlockToTick (so that SetNextBlockToTick() works) auto Idx = cChunkDef::MakeIndexNoCheck(m_BlockToTick); + auto & Random = GetRandomProvider(); + for (int i = 0; i < 50; ++i) { auto Pos = cChunkDef::IndexToCoordinate(static_cast(Idx)); - Idx = m_World->GetTickRandomNumber(cChunkDef::NumBlocks - 1); + Idx = Random.RandInt(cChunkDef::NumBlocks - 1); if (Pos.y > cChunkDef::GetHeight(m_HeightMap, Pos.x, Pos.z)) { continue; // It's all air up here diff --git a/src/Items/ItemDye.h b/src/Items/ItemDye.h index da657007f..631e28e7d 100644 --- a/src/Items/ItemDye.h +++ b/src/Items/ItemDye.h @@ -102,13 +102,13 @@ public: fertilization success is reported even in the case when the chance fails (bonemeal still needs to be consumed). */ static bool FertilizePlant(cWorld & a_World, Vector3i a_BlockPos) { - BLOCKTYPE blockType; - NIBBLETYPE blockMeta; - if (!a_World.GetBlockTypeMeta(a_BlockPos, blockType, blockMeta)) + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!a_World.GetBlockTypeMeta(a_BlockPos, BlockType, BlockMeta)) { return false; } - switch (blockType) + switch (BlockType) { case E_BLOCK_WHEAT: case E_BLOCK_CARROTS: @@ -117,8 +117,8 @@ public: case E_BLOCK_PUMPKIN_STEM: { // Grow by 2 - 5 stages: - auto numStages = GetRandomProvider().RandInt(2, 5); - if (a_World.GrowPlantAt(a_BlockPos, numStages) <= 0) + auto NumStages = GetRandomProvider().RandInt(2, 5); + if (a_World.GrowPlantAt(a_BlockPos, NumStages) <= 0) { return false; } @@ -128,14 +128,21 @@ public: case E_BLOCK_BEETROOTS: { + if (a_World.GrowPlantAt(a_BlockPos, 1) <= 0) + { + // Fix GH #4805 (bonemeal should only advance growth, not spawn produce): + return false; + } + + a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); + // 75% chance of 1-stage growth: - if (GetRandomProvider().RandBool(0.75)) + if (!GetRandomProvider().RandBool(0.75)) { - if (a_World.GrowPlantAt(a_BlockPos, 1) > 0) - { - a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); - } + // Hit the 25%, rollback: + a_World.GrowPlantAt(a_BlockPos, -1); } + return true; } // case beetroots @@ -144,27 +151,25 @@ public: // 45% chance of growing to the next stage / full tree: if (GetRandomProvider().RandBool(0.45)) { - if (a_World.GrowPlantAt(a_BlockPos, 1) > 0) - { - a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); - } + a_World.GrowPlantAt(a_BlockPos, 1); } + a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); return true; - } + } // case sapling case E_BLOCK_BIG_FLOWER: { // Drop the corresponding flower item without destroying the block: - cItems pickups; - switch (blockMeta) + cItems Pickups; + switch (BlockMeta) { - case E_META_BIG_FLOWER_SUNFLOWER: pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_SUNFLOWER); break; - case E_META_BIG_FLOWER_LILAC: pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_LILAC); break; - case E_META_BIG_FLOWER_ROSE_BUSH: pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_ROSE_BUSH); break; - case E_META_BIG_FLOWER_PEONY: pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_PEONY); break; + case E_META_BIG_FLOWER_SUNFLOWER: Pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_SUNFLOWER); break; + case E_META_BIG_FLOWER_LILAC: Pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_LILAC); break; + case E_META_BIG_FLOWER_ROSE_BUSH: Pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_ROSE_BUSH); break; + case E_META_BIG_FLOWER_PEONY: Pickups.Add(E_BLOCK_BIG_FLOWER, 1, E_META_BIG_FLOWER_PEONY); break; } // TODO: Should we call any hook for this? - a_World.SpawnItemPickups(pickups, a_BlockPos); + a_World.SpawnItemPickups(Pickups, a_BlockPos); return true; } // big flower diff --git a/src/World.cpp b/src/World.cpp index 3f9150527..561adc4d0 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1801,36 +1801,6 @@ bool cWorld::GrowRipePlant(Vector3i a_BlockPos) -int cWorld::GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow) -{ - LOGWARNING("cWorld::GrowCactus is obsolete, use cWorld::GrowPlantAt instead"); - return m_ChunkMap->GrowPlantAt({a_BlockX, a_BlockY, a_BlockZ}); -} - - - - - -bool cWorld::GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType) -{ - LOGWARNING("cWorld::GrowMelonPumpkin is obsolete, use cWorld::GrowPlantAt instead"); - return (m_ChunkMap->GrowPlantAt({a_BlockX, a_BlockY, a_BlockZ}, 16) > 0); // 8 stages for the stem, 8 attempts for the produce -} - - - - - -int cWorld::GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow) -{ - LOGWARNING("cWorld::GrowSugarcane is obsolete, use cWorld::GrowPlantAt instead"); - return m_ChunkMap->GrowPlantAt({a_BlockX, a_BlockY, a_BlockZ}, a_NumBlocksToGrow); -} - - - - - EMCSBiome cWorld::GetBiomeAt (int a_BlockX, int a_BlockZ) { return m_ChunkMap->GetBiomeAt(a_BlockX, a_BlockZ); diff --git a/src/World.h b/src/World.h index 490ab59b4..e1d2a1abf 100644 --- a/src/World.h +++ b/src/World.h @@ -862,18 +862,10 @@ public: bool GrowRipePlant(int a_BlockX, int a_BlockY, int a_BlockZ, bool a_IsByBonemeal = false) { UNUSED(a_IsByBonemeal); - return GrowRipePlant({a_BlockX, a_BlockY, a_BlockZ}); + LOGWARNING("Warning: cWorld:GrowRipePlant function expects Vector3i-based coords rather than int-based coords. Emulating old-style call."); + return GrowRipePlant({ a_BlockX, a_BlockY, a_BlockZ }); } - /** Grows a cactus present at the block specified by the amount of blocks specified, up to the max height specified in the config; returns the amount of blocks the cactus grew inside this call */ - int GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow); - - /** Grows a melon or a pumpkin next to the block specified (assumed to be the stem); returns true if the pumpkin or melon sucessfully grew. */ - bool GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType); - - /** Grows a sugarcane present at the block specified by the amount of blocks specified, up to the max height specified in the config; returns the amount of blocks the sugarcane grew inside this call */ - int GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow); - /** Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value */ EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); -- cgit v1.2.3