1
0
Fork 0

Refactored block-to-pickup conversion. (#4417)

This commit is contained in:
Mattes D 2019-10-16 10:06:34 +02:00 committed by GitHub
parent 241d97bbf9
commit 221cc4ec5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 2496 additions and 1744 deletions

View File

@ -7909,6 +7909,17 @@ end
Notes = "Adds a new item to the end of the collection",
},
},
AddItemGrid =
{
Params =
{
{
Name = "ItemGrid",
Type = "cItemGrid",
},
},
Notes = "Adds a copy of each item in the specified {{cItemGrid|ItemGrid}}.",
},
Clear =
{
Notes = "Removes all items from the collection",
@ -7921,7 +7932,7 @@ end
Type = "cItems",
},
},
Notes = "Creates a new cItems object",
Notes = "Creates a new empty cItems object",
},
Contains =
{

View File

@ -562,7 +562,7 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
Type = "boolean",
},
},
Notes = "Replaces the specified block with air, without dropping the usual pickups for the block. Wakes up the simulators for the block and its neighbors. Returns true on success, or false if the chunk is not loaded or invalid coords.",
Notes = "Replaces the specified block with air, without dropping the usual pickups for the block. Wakes up the simulators for the block and its neighbors. Returns true on success, or false if the chunk is not loaded or invalid coords. See also DropBlockAsPickups() for the version that drops pickups.",
},
DoExplosionAt =
{
@ -1072,6 +1072,34 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
},
Notes = "If there is the player with the uuid, calls the CallbackFunction with the {{cPlayer}} parameter representing the player. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre> The function returns false if the player was not found, or whatever bool value the callback returned if the player was found.",
},
DropBlockAsPickups =
{
Params =
{
{
Name = "BlockPos",
Type = "Vector3i",
},
{
Name = "Digger",
Type = "cEntity",
IsOptional = true,
},
{
Name = "Tool",
Type = "cItem",
IsOptional = true,
},
},
Returns =
{
{
Name = "IsSuccess",
Type = "boolean",
}
},
Notes = "Digs up the specified block and spawns the appropriate pickups for it. The optional Digger parameter specifies the {{cEntity|entity}} who dug the block, usually a {{cPlayer|player}}. The optional Tool parameter specifies the tool used to dig the block, not present means an empty hand. Returns true on success, false if the chunk is not present. See also DigBlock() for the pickup-less version.",
},
FastSetBlock =
{
{
@ -2459,6 +2487,34 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
},
Notes = "Returns true if the specified location has wet weather (rain or storm), using the same logic as IsWeatherWetAt, except that any rain-blocking blocks above the specified position will block the precipitation and this function will return false.",
},
PickupsFromBlock =
{
Params =
{
{
Name = "BlockPos",
Type = "Vector3i",
},
{
Name = "Digger",
Type = "cEntity",
IsOptional = true,
},
{
Name = "Tool",
Type = "cItem",
IsOptional = true,
},
},
Returns =
{
{
Name = "Items",
Type = "cItems",
},
},
Notes = "Returns all the pickups that would result if the Digger dug up the block at BlockPos using Tool. Digger is usually a {{cPlayer}}, but can be nil for natural causes. Tool is usually the equipped {{cItem|item}}, can be nil for empty hand. Returns an empty {{cItems}} object if the chunk is not present."
},
PrepareChunk =
{
Params =
@ -2685,13 +2741,8 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
Name = "BlockMeta",
Type = "number",
},
{
Name = "ShouldSendToClients",
Type = "boolean",
IsOptional = true,
},
},
Notes = "Sets the block at the specified coords, replaces the block entities for the previous block type, creates a new block entity for the new block, if appropriate, and wakes up the simulators. This is the preferred way to set blocks, as opposed to FastSetBlock(), which is only to be used under special circumstances. If ShouldSendToClients is true (default), the change is broadcast to all players who have this chunk loaded; if false, the change is made server-side only.",
Notes = "Sets the block at the specified coords, replaces the block entities for the previous block type, creates a new block entity for the new block, if appropriate, and wakes up the simulators. This is the preferred way to set blocks, as opposed to FastSetBlock(), which is only to be used under special circumstances.",
},
SetBlockMeta =
{

View File

@ -44,7 +44,7 @@ public:
/** Calls the specified hook with the params given. Returns the bool that the hook callback returns. */
virtual bool OnBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) = 0;
virtual bool OnBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) = 0;
virtual bool OnBlockToPickups (cWorld & a_World, Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, const cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool, cItems & a_Pickups) = 0;
virtual bool OnBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) = 0;
virtual bool OnBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) = 0;
virtual bool OnChat (cPlayer & a_Player, AString & a_Message) = 0;

View File

@ -227,9 +227,24 @@ bool cPluginLua::OnBlockSpread(cWorld & a_World, int a_BlockX, int a_BlockY, int
bool cPluginLua::OnBlockToPickups(cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups)
bool cPluginLua::OnBlockToPickups(
cWorld & a_World,
Vector3i a_BlockPos,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta,
const cBlockEntity * a_BlockEntity,
const cEntity * a_Digger,
const cItem * a_Tool,
cItems & a_Pickups
)
{
return CallSimpleHooks(cPluginManager::HOOK_BLOCK_TO_PICKUPS, &a_World, a_Digger, a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, &a_Pickups);
// TODO: Change the hook signature to reflect the real parameters to this function, once we are allowed to make breaking API changes
return CallSimpleHooks(
cPluginManager::HOOK_BLOCK_TO_PICKUPS,
&a_World,
a_Digger,
a_BlockPos.x, a_BlockPos.y, a_BlockPos.z,
a_BlockType, a_BlockMeta, &a_Pickups
);
}

View File

@ -66,7 +66,7 @@ public:
virtual void Tick(float a_Dt) override;
virtual bool OnBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) override;
virtual bool OnBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) override;
virtual bool OnBlockToPickups (cWorld & a_World, Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, const cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool, cItems & a_Pickups) override;
virtual bool OnBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) override;
virtual bool OnBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_BrewingstandEntity) override;
virtual bool OnChat (cPlayer & a_Player, AString & a_Message) override;

View File

@ -248,14 +248,18 @@ bool cPluginManager::CallHookBlockSpread(cWorld & a_World, int a_BlockX, int a_B
bool cPluginManager::CallHookBlockToPickups(
cWorld & a_World, cEntity * a_Digger,
int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta,
cWorld & a_World,
Vector3i a_BlockPos,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta,
const cBlockEntity * a_BlockEntity,
const cEntity * a_Digger,
const cItem * a_Tool,
cItems & a_Pickups
)
{
return GenericCallHook(HOOK_BLOCK_TO_PICKUPS, [&](cPlugin * a_Plugin)
{
return a_Plugin->OnBlockToPickups(a_World, a_Digger, a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_Pickups);
return a_Plugin->OnBlockToPickups(a_World, a_BlockPos, a_BlockType, a_BlockMeta, a_BlockEntity, a_Digger, a_Tool, a_Pickups);
}
);
}

View File

@ -225,7 +225,7 @@ public:
// Calls for individual hooks. Each returns false if the action is to continue or true if the plugin wants to abort
bool CallHookBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source);
bool CallHookBlockToPickups (cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups);
bool CallHookBlockToPickups (cWorld & a_World, Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, const cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool, cItems & a_Pickups);
bool CallHookBrewingCompleting (cWorld & a_World, cBrewingstandEntity & a_Brewingstand);
bool CallHookBrewingCompleted (cWorld & a_World, cBrewingstandEntity & a_Brewingstand);
bool CallHookChat (cPlayer & a_Player, AString & a_Message);

View File

@ -14,6 +14,7 @@
#include "Blocks/BlockHandler.h"
#include "ChunkData.h"
#include "BlockEntities/BlockEntity.h"
#include "Item.h"
@ -2215,6 +2216,21 @@ bool cBlockArea::ForEachBlockEntity(cBlockEntityCallback a_Callback)
cItems cBlockArea::PickupsFromBlock(Vector3i a_AbsPos, const cEntity * a_Digger, const cItem * a_Tool)
{
auto relPos = a_AbsPos - m_Origin;
BLOCKTYPE blockType;
NIBBLETYPE blockMeta;
GetRelBlockTypeMeta(relPos.x, relPos.y, relPos.z, blockType, blockMeta);
auto blockEntity = GetBlockEntityRel(relPos);
auto handler = BlockHandler(blockType);
return handler->ConvertToPickups(blockMeta, blockEntity, a_Digger, a_Tool);
}
void cBlockArea::SetRelNibble(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Value, NIBBLETYPE * a_Array)
{
if (a_Array == nullptr)
@ -2710,6 +2726,20 @@ void cBlockArea::RemoveNonMatchingBlockEntities(void)
cBlockEntity * cBlockArea::GetBlockEntityRel(Vector3i a_RelPos)
{
if (!HasBlockEntities())
{
return nullptr;
}
auto itr = m_BlockEntities->find(MakeIndex(a_RelPos));
return (itr == m_BlockEntities->end()) ? nullptr : itr->second;
}
////////////////////////////////////////////////////////////////////////////////
// cBlockArea::sBlockEntityDeleter:

View File

@ -24,6 +24,7 @@
// fwd:
class cCuboid;
class cItems;
using cBlockEntityCallback = cFunctionRef<bool(cBlockEntity &)>;
@ -421,6 +422,9 @@ public:
/** Direct read-only access to block entities. */
const cBlockEntities & GetBlockEntities(void) const { ASSERT(HasBlockEntities()); return *m_BlockEntities; }
/** Returns the pickups that would result if the block at the specified position was mined by a_Digger, using a_Tool. */
cItems PickupsFromBlock(Vector3i a_AbsPos, const cEntity * a_Digger = nullptr, const cItem * a_Tool = nullptr);
protected:
@ -520,6 +524,9 @@ protected:
/** Removes from m_BlockEntities those BEs that no longer match the blocktype at their coords. */
void RemoveNonMatchingBlockEntities(void);
/** Returns the cBlockEntity at the specified coords, or nullptr if none. */
cBlockEntity * GetBlockEntityRel(Vector3i a_RelPos);
// tolua_begin
} ;
// tolua_end

View File

@ -27,11 +27,6 @@ public:
return cPluginManager::Get()->CallHookBlockSpread(m_World, a_BlockX, a_BlockY, a_BlockZ, a_Source);
}
virtual bool CallHookBlockToPickups(cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) override
{
return cPluginManager::Get()->CallHookBlockToPickups(m_World, a_Digger, a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_Pickups);
}
virtual bool CallHookPlayerBreakingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override
{
return cPluginManager::Get()->CallHookPlayerBreakingBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_BlockType, a_BlockMeta);

View File

@ -18,11 +18,19 @@ public:
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
a_Pickups.push_back(cItem(E_BLOCK_ANVIL, 1, a_BlockMeta >> 2));
return cItem(m_BlockType, 1, a_BlockMeta >> 2);
}
virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override
{
cWindow * Window = new cAnvilWindow(a_BlockX, a_BlockY, a_BlockZ);

View File

@ -15,35 +15,33 @@
void cBlockBedHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
void cBlockBedHandler::OnBroken(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, Vector3i a_BlockPos, BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta)
{
Vector3i ThisPos(a_BlockX, a_BlockY, a_BlockZ);
NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(ThisPos);
Vector3i Direction = MetaDataToDirection(OldMeta & 0x3);
if (OldMeta & 0x8)
auto Direction = MetaDataToDirection(a_OldBlockMeta & 0x03);
if ((a_OldBlockMeta & 0x08) != 0)
{
// Was pillow
if (a_ChunkInterface.GetBlock(ThisPos - Direction) == E_BLOCK_BED)
if (a_ChunkInterface.GetBlock(a_BlockPos - Direction) == E_BLOCK_BED)
{
// First replace the bed with air
a_ChunkInterface.FastSetBlock(ThisPos - Direction, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockPos - Direction, E_BLOCK_AIR, 0);
// Then destroy the bed entity
Vector3i PillowPos(ThisPos - Direction);
a_ChunkInterface.SetBlock(PillowPos.x, PillowPos.y, PillowPos.z, E_BLOCK_AIR, 0);
Vector3i PillowPos(a_BlockPos - Direction);
a_ChunkInterface.SetBlock(PillowPos, E_BLOCK_AIR, 0);
}
}
else
{
// Was foot end
if (a_ChunkInterface.GetBlock(ThisPos + Direction) == E_BLOCK_BED)
if (a_ChunkInterface.GetBlock(a_BlockPos + Direction) == E_BLOCK_BED)
{
// First replace the bed with air
a_ChunkInterface.FastSetBlock(ThisPos + Direction, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockPos + Direction, E_BLOCK_AIR, 0);
// Then destroy the bed entity
Vector3i FootPos(ThisPos + Direction);
a_ChunkInterface.SetBlock(FootPos.x, FootPos.y, FootPos.z, E_BLOCK_AIR, 0);
Vector3i FootPos(a_BlockPos + Direction);
a_ChunkInterface.SetBlock(FootPos, E_BLOCK_AIR, 0);
}
}
}
@ -155,14 +153,12 @@ void cBlockBedHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWor
void cBlockBedHandler::ConvertToPickups(cWorldInterface & a_WorldInterface, cItems & a_Pickups, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ)
cItems cBlockBedHandler::ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool)
{
short Color = E_META_WOOL_RED;
a_WorldInterface.DoWithBedAt(a_BlockX, a_BlockY, a_BlockZ, [&](cBedEntity & a_Bed)
{
Color = a_Bed.GetColor();
return true;
}
);
a_Pickups.Add(cItem(E_ITEM_BED, 1, Color));
short color = E_META_WOOL_RED;
if (a_BlockEntity != nullptr)
{
color = reinterpret_cast<cBedEntity *>(a_BlockEntity)->GetColor();
}
return cItem(E_ITEM_BED, 1, color);
}

View File

@ -4,7 +4,7 @@
#pragma once
#include "BlockEntity.h"
#include "MetaRotator.h"
#include "Mixins.h"
#include "ChunkInterface.h"
@ -18,24 +18,28 @@ class cWorldInterface;
class cBlockBedHandler :
public cMetaRotator<cBlockEntityHandler, 0x3, 0x02, 0x03, 0x00, 0x01, true>
{
using super = cMetaRotator<cBlockEntityHandler, 0x3, 0x02, 0x03, 0x00, 0x01, true>;
public:
cBlockBedHandler(BLOCKTYPE a_BlockType)
: cMetaRotator<cBlockEntityHandler, 0x3, 0x02, 0x03, 0x00, 0x01, true>(a_BlockType)
cBlockBedHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override;
// Overrides:
virtual void OnBroken(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, Vector3i a_BlockPos, BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta) override;
virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override;
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override;
virtual void OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, const sSetBlock & a_BlockChange) override;
virtual bool IsUseable(void) override
{
return true;
}
virtual void ConvertToPickups(cItems & Pickups, NIBBLETYPE Meta) override {}
virtual void ConvertToPickups(cWorldInterface & a_WorldInterface, cItems & a_Pickups, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) override;
// Bed specific helper functions
static NIBBLETYPE RotationToMetaData(double a_Rotation)
@ -51,18 +55,26 @@ public:
return (static_cast<NIBBLETYPE>(a_Rotation + 2)) % 4;
}
static Vector3i MetaDataToDirection(NIBBLETYPE a_MetaData)
{
switch (a_MetaData)
{
case 0: return Vector3i(0, 0, 1);
case 1: return Vector3i(-1, 0, 0);
case 2: return Vector3i(0, 0, -1);
case 3: return Vector3i(1, 0, 0);
case 0: return Vector3i( 0, 0, 1);
case 1: return Vector3i(-1, 0, 0);
case 2: return Vector3i( 0, 0, -1);
case 3: return Vector3i( 1, 0, 0);
}
return Vector3i();
}
static void SetBedOccupationState(cChunkInterface & a_ChunkInterface, const Vector3i & a_BedPosition, bool a_IsOccupied)
{
auto Meta = a_ChunkInterface.GetBlockMeta(a_BedPosition);
@ -78,7 +90,9 @@ public:
a_ChunkInterface.SetBlockMeta(a_BedPosition.x, a_BedPosition.y, a_BedPosition.z, Meta);
}
virtual void OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, const sSetBlock & a_BlockChange) override;
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{

View File

@ -42,106 +42,53 @@ public:
);
}
virtual void DropBlock(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_BlockPluginInterface, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_CanDrop) override
{
Vector3i Pos(a_BlockX, a_BlockY, a_BlockZ);
NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(Pos);
int AlternateY = a_BlockY;
int BottomY = a_BlockY;
if (Meta & 0x8)
{
--AlternateY;
--BottomY;
}
else
{
++AlternateY;
}
// also destroy the other block if it has a valid height and is a big flower
if (cChunkDef::IsValidHeight(AlternateY) && a_ChunkInterface.GetBlock({Pos.x, AlternateY, Pos.z}) == E_BLOCK_BIG_FLOWER)
{
super::DropBlock(a_ChunkInterface, a_WorldInterface, a_BlockPluginInterface, a_Digger, a_BlockX, BottomY, a_BlockZ, a_CanDrop);
a_ChunkInterface.FastSetBlock(a_BlockX, AlternateY, a_BlockZ, E_BLOCK_AIR, 0);
}
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
if (IsMetaTopPart(a_BlockMeta))
{
return; // No way to tell flower type
return {}; // No drops from the top part
}
NIBBLETYPE Meta = a_BlockMeta & 0x7;
if (Meta == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS)
// With shears, drop self (even tall grass and fern):
if ((a_Tool != nullptr) && (a_Tool->m_ItemType == E_ITEM_SHEARS))
{
// Bit 0x08 specifies whether this is a top part or bottom; cut it off from the pickup:
return cItem(m_BlockType, 1, a_BlockMeta & 0x07);
}
// Tall grass drops seeds, large fern drops nothing, others drop self:
auto flowerType = a_BlockMeta & 0x07;
if (flowerType == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS)
{
if (GetRandomProvider().RandBool(1.0 / 24.0))
{
a_Pickups.push_back(cItem(E_ITEM_SEEDS));
return cItem(E_ITEM_SEEDS);
}
}
else if (Meta != E_META_BIG_FLOWER_LARGE_FERN)
else if (flowerType != E_META_BIG_FLOWER_LARGE_FERN)
{
a_Pickups.push_back(cItem(E_BLOCK_BIG_FLOWER, 1, Meta));
return cItem(m_BlockType, 1, static_cast<short>(flowerType));
}
return {};
}
virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override
{
Vector3i BlockPos(a_BlockX, a_BlockY, a_BlockZ);
NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(BlockPos);
if (Meta & 0x8)
{
Meta = a_ChunkInterface.GetBlockMeta(BlockPos - Vector3i(0, 1, 0));
}
NIBBLETYPE FlowerMeta = Meta & 0x7;
if (!a_Player.IsGameModeCreative())
{
if (
(a_Player.GetEquippedItem().m_ItemType == E_ITEM_SHEARS) &&
(
(FlowerMeta == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS) ||
(FlowerMeta == E_META_BIG_FLOWER_LARGE_FERN)
)
)
{
if (GetRandomProvider().RandBool(0.10))
{
cItems Pickups;
if (FlowerMeta == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS)
{
Pickups.Add(E_BLOCK_TALL_GRASS, 2, 1);
}
else if (FlowerMeta == E_META_BIG_FLOWER_LARGE_FERN)
{
Pickups.Add(E_BLOCK_TALL_GRASS, 2, 2);
}
a_WorldInterface.SpawnItemPickups(Pickups, BlockPos.x, BlockPos.y, BlockPos.z);
}
a_Player.UseEquippedItem();
}
}
if (
(a_Player.GetEquippedItem().m_ItemType != E_ITEM_SHEARS) &&
(
(FlowerMeta == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS) ||
(FlowerMeta == E_META_BIG_FLOWER_LARGE_FERN)
)
)
{
a_ChunkInterface.SetBlock(BlockPos.x, BlockPos.y, BlockPos.z, 0, 0);
a_Player.UseEquippedItem(cItemHandler::dlaBreakBlockInstant);
}
}
bool IsMetaTopPart(NIBBLETYPE a_Meta)
{
return (a_Meta & 0x08) != 0;
return ((a_Meta & 0x08) != 0);
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override
{
if (a_RelY <= 0)
@ -155,31 +102,36 @@ public:
return IsBlockTypeOfDirt(BlockType) || ((BlockType == E_BLOCK_BIG_FLOWER) && !IsMetaTopPart(BlockMeta));
}
virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override
{
Vector3i BlockPos(a_BlockX, a_BlockY, a_BlockZ);
NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(BlockPos);
if (OldMeta & 0x8)
virtual void OnBroken(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, Vector3i a_BlockPos, BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta) override
{
if ((a_OldBlockMeta & 0x8) != 0)
{
// Was upper part of flower
Vector3i LowerPart = BlockPos - Vector3i(0, 1, 0);
if (a_ChunkInterface.GetBlock(LowerPart) == m_BlockType)
auto lowerPartPos = a_BlockPos - Vector3i(0, 1, 0);
if (a_ChunkInterface.GetBlock(lowerPartPos) == a_OldBlockType)
{
a_ChunkInterface.FastSetBlock(LowerPart, E_BLOCK_AIR, 0);
a_ChunkInterface.DropBlockAsPickups(lowerPartPos);
}
}
else
{
// Was lower part
Vector3i UpperPart = BlockPos + Vector3i(0, 1, 0);
if (a_ChunkInterface.GetBlock(UpperPart) == m_BlockType)
auto upperPartPos = a_BlockPos + Vector3i(0, 1, 0);
if (a_ChunkInterface.GetBlock(upperPartPos) == a_OldBlockType)
{
a_ChunkInterface.FastSetBlock(UpperPart, E_BLOCK_AIR, 0);
a_ChunkInterface.DropBlockAsPickups(upperPartPos);
}
}
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{
UNUSED(a_Meta);

View File

@ -2,28 +2,27 @@
#pragma once
#include "BlockEntity.h"
#include "MetaRotator.h"
#include "Mixins.h"
class cBlockBrewingStandHandler :
public cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>
public cClearMetaOnDrop<cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>>
{
using super = cClearMetaOnDrop<cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>>;
public:
cBlockBrewingStandHandler(BLOCKTYPE a_BlockType)
: cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>(a_BlockType)
cBlockBrewingStandHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
a_Pickups.push_back(cItem(E_ITEM_BREWING_STAND, 1, 0));
}
virtual bool IsUseable() override
{
return true;
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{

View File

@ -2,20 +2,27 @@
#include "BlockHandler.h"
#include "../Chunk.h"
#include "MetaRotator.h"
#include "Mixins.h"
class cBlockButtonHandler :
public cMetaRotator<cBlockHandler, 0x07, 0x04, 0x01, 0x03, 0x02, true>
public cClearMetaOnDrop<cMetaRotator<cBlockHandler, 0x07, 0x04, 0x01, 0x03, 0x02, true>>
{
using super = cClearMetaOnDrop<cMetaRotator<cBlockHandler, 0x07, 0x04, 0x01, 0x03, 0x02, true>>;
public:
cBlockButtonHandler(BLOCKTYPE a_BlockType)
: cMetaRotator<cBlockHandler, 0x07, 0x04, 0x01, 0x03, 0x02, true>(a_BlockType)
cBlockButtonHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override
{
Vector3i Pos(a_BlockX, a_BlockY, a_BlockZ);
@ -53,12 +60,6 @@ public:
return true;
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
// Reset meta to 0
a_Pickups.push_back(cItem(m_BlockType, 1, 0));
}
virtual bool IsUseable(void) override
{
return true;

View File

@ -8,21 +8,31 @@
class cBlockCactusHandler :
public cBlockPlant
public cBlockPlant<false>
{
typedef cBlockPlant Super;
using super = cBlockPlant<false>;
public:
cBlockCactusHandler(BLOCKTYPE a_BlockType)
: Super(a_BlockType, false)
cBlockCactusHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
// Reset meta to 0
a_Pickups.push_back(cItem(m_BlockType, 1, 0));
return cItem(m_BlockType, 1, 0);
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override
{
if (a_RelY <= 0)
@ -88,7 +98,7 @@ protected:
auto Action = paStay;
if (((a_RelY + 1) < cChunkDef::Height) && (a_Chunk.GetBlock(a_RelX, a_RelY + 1, a_RelZ) == E_BLOCK_AIR))
{
Action = Super::CanGrow(a_Chunk, a_RelX, a_RelY, a_RelZ);
Action = super::CanGrow(a_Chunk, a_RelX, a_RelY, a_RelZ);
}
return Action;

View File

@ -35,11 +35,20 @@ public:
return true;
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
// Give nothing
return {};
}
virtual bool IsUseable(void) override
{
return true;

View File

@ -17,12 +17,19 @@
class cBlockCarpetHandler :
public cBlockHandler
{
using super = cBlockHandler;
public:
cBlockCarpetHandler(BLOCKTYPE a_BlockType) :
cBlockHandler(a_BlockType)
super(a_BlockType)
{
}
virtual bool GetPlacementBlockTypeMeta(
cChunkInterface & a_ChunkInterface, cPlayer & a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
@ -35,16 +42,19 @@ public:
return true;
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
a_Pickups.push_back(cItem(E_BLOCK_CARPET, 1, a_BlockMeta));
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override
{
return (a_RelY > 0) && (a_Chunk.GetBlock(a_RelX, a_RelY - 1, a_RelZ) != E_BLOCK_AIR);
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{
switch (a_Meta)

View File

@ -8,17 +8,15 @@
class cBlockCauldronHandler :
public cBlockHandler
public cClearMetaOnDrop<cBlockHandler>
{
public:
cBlockCauldronHandler(BLOCKTYPE a_BlockType)
: cBlockHandler(a_BlockType)
{
}
using super = cClearMetaOnDrop<cBlockHandler>;
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
public:
cBlockCauldronHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
a_Pickups.push_back(cItem(E_ITEM_CAULDRON, 1, 0));
}
virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override

View File

@ -1,24 +1,31 @@
#pragma once
#include "BlockEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockArea.h"
#include "../Entities/Player.h"
#include "MetaRotator.h"
#include "Mixins.h"
class cBlockChestHandler :
public cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>
public cMetaRotator<cContainerEntityHandler<cBlockEntityHandler>, 0x07, 0x02, 0x05, 0x03, 0x04>
{
using super = cMetaRotator<cContainerEntityHandler<cBlockEntityHandler>, 0x07, 0x02, 0x05, 0x03, 0x04>;
public:
cBlockChestHandler(BLOCKTYPE a_BlockType)
: cMetaRotator<cBlockEntityHandler, 0x07, 0x02, 0x05, 0x03, 0x04>(a_BlockType)
cBlockChestHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual bool GetPlacementBlockTypeMeta(
cChunkInterface & a_ChunkInterface, cPlayer & a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
@ -65,6 +72,10 @@ public:
return true;
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override
{
int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width;
@ -72,6 +83,10 @@ public:
return CanBeAt(a_ChunkInterface, BlockX, a_RelY, BlockZ);
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
cBlockArea Area;
@ -137,6 +152,10 @@ public:
return (NumChestNeighbors < 2);
}
/** Translates player yaw when placing a chest into the chest block metadata. Valid for single chests only */
static NIBBLETYPE PlayerYawToMetaData(double a_Yaw)
{
@ -164,6 +183,10 @@ public:
}
}
/** If there's a chest in the a_Area in the specified coords, modifies its meta to a_NewMeta and returns true. */
bool CheckAndAdjustNeighbor(cChunkInterface & a_ChunkInterface, const cBlockArea & a_Area, int a_RelX, int a_RelZ, NIBBLETYPE a_NewMeta)
{
@ -175,10 +198,9 @@ public:
return true;
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
a_Pickups.push_back(cItem(m_BlockType, 1, 0));
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{

View File

@ -18,11 +18,27 @@ public:
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_Meta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
a_Pickups.push_back(cItem(E_ITEM_STRING, 1, 0));
// Silk touch gives cobweb, anything else gives just string:
if (ToolHasSilkTouch(a_Tool))
{
return cItem(m_BlockType, 1, 0);
}
else
{
return cItem(E_ITEM_STRING, 1, 0);
}
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{
UNUSED(a_Meta);

View File

@ -44,12 +44,21 @@ public:
}
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
int GrowState = a_BlockMeta >> 2;
a_Pickups.Add(E_ITEM_DYE, ((GrowState >= 2) ? 3 : 1), E_META_DYE_BROWN);
// If fully grown, give 3 items, otherwise just one:
auto growState = a_BlockMeta >> 2;
return cItem(E_ITEM_DYE, ((growState >= 2) ? 3 : 1), E_META_DYE_BROWN);
}
static eBlockFace MetaToBlockFace(NIBBLETYPE a_Meta)
{
switch (a_Meta & 0x3)

View File

@ -10,17 +10,29 @@
class cBlockCommandBlockHandler :
public cBlockEntityHandler
{
using super = cBlockEntityHandler;
public:
cBlockCommandBlockHandler(BLOCKTYPE a_BlockType)
: cBlockEntityHandler(a_BlockType)
cBlockCommandBlockHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
a_Pickups.push_back(cItem(E_BLOCK_AIR, 8, 0));
// Don't allow as a pickup:
return {};
}
virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) override
{
UNUSED(a_Meta);

View File

@ -3,18 +3,21 @@
#include "BlockHandler.h"
#include "BlockRedstoneRepeater.h"
#include "MetaRotator.h"
#include "Mixins.h"
class cBlockComparatorHandler :
public cMetaRotator<cBlockHandler, 0x03, 0x00, 0x01, 0x02, 0x03, true>
public cClearMetaOnDrop<cMetaRotator<cBlockHandler, 0x03, 0x00, 0x01, 0x02, 0x03, true>>
{
using super = cClearMetaOnDrop<cMetaRotator<cBlockHandler, 0x03, 0x00, 0x01, 0x02, 0x03, true>>;
public:
cBlockComparatorHandler(BLOCKTYPE a_BlockType)
: cMetaRotator<cBlockHandler, 0x03, 0x00, 0x01, 0x02, 0x03, true>(a_BlockType)
cBlockComparatorHandler(BLOCKTYPE a_BlockType):
super(a_BlockType)
{
}
@ -32,12 +35,6 @@ public:
a_WorldInterface.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player);
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
// Reset meta to 0
a_Pickups.push_back(cItem(E_ITEM_COMPARATOR, 1, 0));
}
virtual bool IsUseable(void) override
{
return true;

View File

@ -10,9 +10,12 @@
class cBlockConcretePowderHandler :
public cBlockHandler
{
using super = cBlockHandler;
public:
cBlockConcretePowderHandler(BLOCKTYPE a_BlockType):
cBlockHandler(a_BlockType)
super(a_BlockType)
{
}
@ -20,21 +23,25 @@ public:
virtual void Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk) override
virtual void Check(
cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface,
Vector3i a_RelPos,
cChunk & a_Chunk
) override
{
if (GetSoaked(Vector3i(a_RelX, a_RelY, a_RelZ), a_Chunk))
if (GetSoaked(a_RelPos, a_Chunk))
{
return;
}
cBlockHandler::Check(a_ChunkInterface, a_PluginInterface, a_RelX, a_RelY, a_RelZ, a_Chunk);
super::Check(a_ChunkInterface, a_PluginInterface, a_RelPos, a_Chunk);
}
/** Check blocks above and around to see if they are water. If one is, convert this into concrete block.
Returns TRUE if the block was changed. */
/** Check blocks above and around to see if they are water. If one is, converts this into concrete block.
Returns true if the block was changed. */
bool GetSoaked(Vector3i a_Rel, cChunk & a_Chunk)
{
static const std::array<Vector3i, 5> WaterCheck
@ -62,7 +69,7 @@ public:
{
NIBBLETYPE BlockMeta;
BlockMeta = a_Chunk.GetMeta(a_Rel.x, a_Rel.y, a_Rel.z);
a_Chunk.SetBlock(a_Rel.x, a_Rel.y, a_Rel.z, E_BLOCK_CONCRETE, BlockMeta);
a_Chunk.SetBlock(a_Rel, E_BLOCK_CONCRETE, BlockMeta);
return true;
}
return false;

View File

@ -10,71 +10,70 @@
/** Common class that takes care of beetroots, carrots, potatoes and wheat */
template <NIBBLETYPE RipeMeta>
class cBlockCropsHandler :
public cBlockPlant
class cBlockCropsHandler:
public cBlockPlant<true>
{
typedef cBlockPlant Super;
using super = cBlockPlant<true>;
public:
cBlockCropsHandler(BLOCKTYPE a_BlockType):
Super(a_BlockType, true)
super(a_BlockType)
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_Meta) override
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, cBlockEntity * a_BlockEntity, const cEntity * a_Digger, const cItem * a_Tool) override
{
auto & rand = GetRandomProvider();
// If not fully grown, drop the "seed" of whatever is growing:
if (a_Meta < RipeMeta)
if (a_BlockMeta < RipeMeta)
{
switch (m_BlockType)
{
case E_BLOCK_BEETROOTS: a_Pickups.push_back(cItem(E_ITEM_BEETROOT_SEEDS, 1, 0)); break;
case E_BLOCK_CROPS: a_Pickups.push_back(cItem(E_ITEM_SEEDS, 1, 0)); break;
case E_BLOCK_CARROTS: a_Pickups.push_back(cItem(E_ITEM_CARROT, 1, 0)); break;
case E_BLOCK_POTATOES: a_Pickups.push_back(cItem(E_ITEM_POTATO, 1, 0)); break;
default:
{
ASSERT(!"Unhandled block type");
break;
}
case E_BLOCK_BEETROOTS: return cItem(E_ITEM_BEETROOT_SEEDS, 1, 0); break;
case E_BLOCK_CROPS: return cItem(E_ITEM_SEEDS, 1, 0); break;
case E_BLOCK_CARROTS: return cItem(E_ITEM_CARROT, 1, 0); break;
case E_BLOCK_POTATOES: return cItem(E_ITEM_POTATO, 1, 0); break;
}
return;
ASSERT(!"Unhandled block type");
return {};
}
// Fully grown, drop the crop's produce:
cItems res;
switch (m_BlockType)
{
case E_BLOCK_BEETROOTS:
{
char SeedCount = 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2); // [1 .. 3] with high preference of 2
a_Pickups.emplace_back(E_ITEM_BEETROOT_SEEDS, SeedCount, 0);
res.Add(E_ITEM_BEETROOT_SEEDS, SeedCount, 0);
char BeetrootCount = 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2); // [1 .. 3] with high preference of 2
a_Pickups.emplace_back(E_ITEM_BEETROOT, BeetrootCount, 0);
res.Add(E_ITEM_BEETROOT, BeetrootCount, 0);
break;
}
case E_BLOCK_CROPS:
{
a_Pickups.emplace_back(E_ITEM_WHEAT, 1, 0);
a_Pickups.emplace_back(E_ITEM_SEEDS, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
res.Add(E_ITEM_WHEAT, 1, 0);
res.Add(E_ITEM_SEEDS, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
break;
}
case E_BLOCK_CARROTS:
{
a_Pickups.emplace_back(E_ITEM_CARROT, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
res.Add(E_ITEM_CARROT, 1 + ((rand.RandInt<char>(2) + rand.RandInt<char>(2)) / 2), 0); // [1 .. 3] with high preference of 2
break;
}
case E_BLOCK_POTATOES:
{