diff options
author | 0ddlyoko <nathangiaco@hotmail.com> | 2020-11-06 17:54:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-06 17:54:01 +0100 |
commit | 672bb0457012612ef59502b33717ee789c4d6bfe (patch) | |
tree | 962a76ff85b12a868009fa01659122386e695d0e | |
parent | Ender Crytal Fix and report proper cmake file for luabindingscheck fail (#5017) (diff) | |
download | cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar.gz cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar.bz2 cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar.lz cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar.xz cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.tar.zst cuberite-672bb0457012612ef59502b33717ee789c4d6bfe.zip |
-rw-r--r-- | Server/Plugins/APIDump/Classes/World.lua | 121 | ||||
-rw-r--r-- | src/BlockEntities/DispenserEntity.cpp | 79 | ||||
-rw-r--r-- | src/Blocks/BlockCrops.h | 45 | ||||
-rw-r--r-- | src/Blocks/BlockPlant.h | 11 | ||||
-rw-r--r-- | src/Blocks/BlockStems.h | 169 | ||||
-rw-r--r-- | src/Chunk.cpp | 4 | ||||
-rw-r--r-- | src/Items/ItemDye.h | 51 | ||||
-rw-r--r-- | src/World.cpp | 30 | ||||
-rw-r--r-- | src/World.h | 12 |
9 files changed, 217 insertions, 305 deletions
diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua index 2f5d0b8f2..a1f2bc979 100644 --- a/Server/Plugins/APIDump/Classes/World.lua +++ b/Server/Plugins/APIDump/Classes/World.lua @@ -2096,64 +2096,6 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i }, Notes = "Returns the total age of the world, in ticks. The age always grows, cannot be set by plugins and is unrelated to TimeOfDay.", }, - GrowCactus = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "NumBlocksToGrow", - Type = "number", - }, - }, - Returns = - { - { - Type = "number", - }, - }, - Notes = "OBSOLETE, use GrowPlantAt instead. Grows a cactus block at the specified coords, by up to the specified number of blocks. Adheres to the world's maximum cactus growth (GetMaxCactusHeight()). Returns the amount of blocks the cactus grew inside this call.", - }, - GrowMelonPumpkin = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "StemBlockType", - Type = "number", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "OBSOLETE, use GrowPlantAt instead. Grows a melon or pumpkin, based on the stem block type specified (assumed to be at the coords provided). Checks for normal melon / pumpkin growth conditions - stem not having another produce next to it and suitable ground below. Returns true if the melon or pumpkin grew successfully.", - }, GrowPlantAt = { Params = @@ -2177,75 +2119,20 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i }, GrowRipePlant = { - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "OBSOLETE, use the Vector3-based overload instead. Grows the plant at the specified coords to its full. Returns true if the plant was grown, false if not.", - }, - { - Params = - { - { - Name = "BlockPos", - Type = "Vector3i", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Grows the plant at the specified coords to its full. Returns true if the plant was grown, false if not.", - }, - }, - GrowSugarcane = - { Params = { { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "NumBlocksToGrow", - Type = "number", + Name = "BlockPos", + Type = "Vector3i", }, }, Returns = { { - Type = "number", + Type = "boolean", }, }, - Notes = "OBSOLETE, use GrowPlantAt instead. Grows a sugarcane block at the specified coords, by up to the specified number of blocks. Adheres to the world's maximum sugarcane growth (GetMaxSugarcaneHeight()). Returns the amount of blocks the sugarcane grew inside this call.", + Notes = "Grows the plant at the specified coords to its full. Returns true if the plant was grown, false if not.", }, GrowTree = { 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<int>(oldMeta + a_NumStages, RipeMeta); - ASSERT(newMeta > oldMeta); - a_Chunk.GetWorld()->SetBlock(a_Chunk.RelativeToAbsolute(a_RelPos), m_BlockType, static_cast<NIBBLETYPE>(newMeta)); - return newMeta - oldMeta; + const auto OldMeta = a_Chunk.GetMeta(a_RelPos); + const auto NewMeta = std::clamp<NIBBLETYPE>(static_cast<NIBBLETYPE>(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<double>(100); + double Cumulative = 0; + char Count = 0; + + for (; Count < 4; Count++) + { + Cumulative += m_AgeSeedDropProbability[static_cast<size_t>(a_BlockMeta)][static_cast<size_t>(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<NIBBLETYPE>(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<NIBBLETYPE>(static_cast<NIBBLETYPE>(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<Vector3i, 4> 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, 4> 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<size_t>(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<NIBBLETYPE>(random.RandInt(4) % 4); - auto produceAbsPos = a_Chunk.RelativeToAbsolute(produceRelPos); + const NIBBLETYPE Meta = (ProduceBlockType == E_BLOCK_MELON) ? 0 : static_cast<NIBBLETYPE>(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<std::array<double, 4>, 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<E_BLOCK_MELON, E_ITEM_MELON_SEEDS>; using cBlockPumpkinStemHandler = cBlockStemsHandler<E_BLOCK_PUMPKIN, E_ITEM_PUMPKIN_SEEDS>; - - - 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<size_t>(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); |