summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/BlockEntities/DispenserEntity.cpp79
-rw-r--r--src/Blocks/BlockCrops.h45
-rw-r--r--src/Blocks/BlockPlant.h11
-rw-r--r--src/Blocks/BlockStems.h169
-rw-r--r--src/Chunk.cpp4
-rw-r--r--src/Items/ItemDye.h51
-rw-r--r--src/World.cpp30
-rw-r--r--src/World.h12
8 files changed, 213 insertions, 188 deletions
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);