1
0
Fork 0

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 <ziwei.tiger@outlook.com>
This commit is contained in:
0ddlyoko 2020-11-06 17:54:01 +01:00 committed by GitHub
parent 36a67df105
commit 672bb04570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 218 additions and 306 deletions

View File

@ -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 =
@ -2176,76 +2118,21 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
Notes = "Grows the plant at the specified block by the specified number of stages. Returns the number of stages actually grown. Returns zero for non-growable blocks.",
},
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 =
{

View File

@ -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);
}

View File

@ -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;
}
} ;
};

View File

@ -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
}
};

View File

@ -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>;

View File

@ -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

View File

@ -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:
{
// 75% chance of 1-stage growth:
if (GetRandomProvider().RandBool(0.75))
if (a_World.GrowPlantAt(a_BlockPos, 1) <= 0)
{
if (a_World.GrowPlantAt(a_BlockPos, 1) > 0)
{
a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 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))
{
// 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

View File

@ -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);

View File

@ -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);