1
0

Merge pull request #1686 from mc-server/PlaceBlockRefactor

Place block refactor
This commit is contained in:
Mattes D 2014-12-25 00:31:56 +01:00
commit 1d593134da
48 changed files with 1487 additions and 784 deletions

View File

@ -1840,7 +1840,9 @@ a_Player:OpenWindow(Window);
MoveToWorld = { Params = "WorldName", Return = "bool", Return = "Moves the player to the specified world. Returns true if successful." },
OpenWindow = { Params = "{{cWindow|Window}}", Return = "", Notes = "Opens the specified UI window for the player." },
PermissionMatches = { Params = "Permission, Template", Return = "bool", Notes = "(STATIC) Returns true if the specified permission matches the specified template. The template may contain wildcards." },
PlaceBlock = { Params = "BlockX, BlockY, BlockZ, BlockType, BlockMeta", Return = "bool", Notes = "Places a block while impersonating the player. The {{OnPlayerPlacingBlock|HOOK_PLAYER_PLACING_BLOCK}} hook is called before the placement, and if it succeeds, the block is placed and the {{OnPlayerPlacedBlock|HOOK_PLAYER_PLACED_BLOCK}} hook is called. Returns true iff the block is successfully placed. Assumes that the block is in a currently loaded chunk." },
Respawn = { Params = "", Return = "", Notes = "Restores the health, extinguishes fire, makes visible and sends the Respawn packet." },
SendBlocksAround = { Params = "BlockX, BlockY, BlockZ, [Range]", Return = "", Notes = "Sends all the world's blocks in Range from the specified coords to the player, as a BlockChange packet. Range defaults to 1 (only one block sent)." },
SendMessage = { Params = "Message", Return = "", Notes = "Sends the specified message to the player." },
SendMessageFailure = { Params = "Message", Return = "", Notes = "Prepends Rose [INFO] / colours entire text (depending on ShouldUseChatPrefixes()) and sends message to player. For a command that failed to run because of insufficient permissions, etc." },
SendMessageFatal = { Params = "Message", Return = "", Notes = "Prepends Red [FATAL] / colours entire text (depending on ShouldUseChatPrefixes()) and sends message to player. For something serious, such as a plugin crash, etc." },

View File

@ -12,7 +12,11 @@ return
Use the {{cPlayer}}:GetWorld() function to get the world to which the block belongs.</p>
<p>
See also the {{OnPlayerPlacingBlock|HOOK_PLAYER_PLACING_BLOCK}} hook for a similar hook called
before the placement.
before the placement.</p>
<p>
If the client action results in multiple blocks being placed (such as a bed or a door), each separate
block is reported through this hook. All the blocks are already present in the world before the first
instance of this hook is called.
]],
Params =
{
@ -20,10 +24,6 @@ return
{ Name = "BlockX", Type = "number", Notes = "X-coord of the block" },
{ Name = "BlockY", Type = "number", Notes = "Y-coord of the block" },
{ Name = "BlockZ", Type = "number", Notes = "Z-coord of the block" },
{ Name = "BlockFace", Type = "number", Notes = "Face of the existing block upon which the player interacted. One of the BLOCK_FACE_ constants" },
{ Name = "CursorX", Type = "number", Notes = "X-coord of the cursor within the block face (0 .. 15)" },
{ Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor within the block face (0 .. 15)" },
{ Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor within the block face (0 .. 15)" },
{ Name = "BlockType", Type = "BLOCKTYPE", Notes = "The block type of the block" },
{ Name = "BlockMeta", Type = "NIBBLETYPE", Notes = "The block meta of the block" },
},

View File

@ -15,7 +15,11 @@ return
Use the {{cPlayer}}:GetWorld() function to get the world to which the block belongs.</p>
<p>
See also the {{OnPlayerPlacedBlock|HOOK_PLAYER_PLACED_BLOCK}} hook for a similar hook called after
the placement.
the placement.</p>
<p>
If the client action results in multiple blocks being placed (such as a bed or a door), each separate
block is reported through this hook and only if all of them succeed, all the blocks are placed. If
any one of the calls are refused by the plugin, all the blocks are refused and reverted on the client.
]],
Params =
{
@ -23,10 +27,6 @@ return
{ Name = "BlockX", Type = "number", Notes = "X-coord of the block" },
{ Name = "BlockY", Type = "number", Notes = "Y-coord of the block" },
{ Name = "BlockZ", Type = "number", Notes = "Z-coord of the block" },
{ Name = "BlockFace", Type = "number", Notes = "Face of the existing block upon which the player is interacting. One of the BLOCK_FACE_ constants" },
{ Name = "CursorX", Type = "number", Notes = "X-coord of the cursor within the block face (0 .. 15)" },
{ Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor within the block face (0 .. 15)" },
{ Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor within the block face (0 .. 15)" },
{ Name = "BlockType", Type = "BLOCKTYPE", Notes = "The block type of the block" },
{ Name = "BlockMeta", Type = "NIBBLETYPE", Notes = "The block meta of the block" },
},

View File

@ -76,8 +76,8 @@ public:
virtual bool OnPlayerJoined (cPlayer & a_Player) = 0;
virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) = 0;
virtual bool OnPlayerMoving (cPlayer & a_Player, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) = 0;
virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0;
virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0;
virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) = 0;
virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) = 0;
virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) = 0;
virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) = 0;
virtual bool OnPlayerShooting (cPlayer & a_Player) = 0;

View File

@ -857,14 +857,19 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi
bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange)
{
cCSLock Lock(m_CriticalSection);
bool res = false;
cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACED_BLOCK];
for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr)
{
m_LuaState.Call((int)(**itr), &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta, cLuaState::Return, res);
m_LuaState.Call((int)(**itr), &a_Player,
a_BlockChange.GetX(), a_BlockChange.GetY(), a_BlockChange.GetZ(),
a_BlockChange.m_BlockType, a_BlockChange.m_BlockMeta,
cLuaState::Return,
res
);
if (res)
{
return true;
@ -877,14 +882,19 @@ bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_Blo
bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange)
{
cCSLock Lock(m_CriticalSection);
bool res = false;
cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACING_BLOCK];
for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr)
{
m_LuaState.Call((int)(**itr), &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta, cLuaState::Return, res);
m_LuaState.Call((int)(**itr), &a_Player,
a_BlockChange.GetX(), a_BlockChange.GetY(), a_BlockChange.GetZ(),
a_BlockChange.m_BlockType, a_BlockChange.m_BlockMeta,
cLuaState::Return,
res
);
if (res)
{
return true;

View File

@ -100,8 +100,8 @@ public:
virtual bool OnPlayerJoined (cPlayer & a_Player) override;
virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) override;
virtual bool OnPlayerMoving (cPlayer & a_Player, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override;
virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override;
virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override;
virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) override;
virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) override;
virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override;
virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override;
virtual bool OnPlayerShooting (cPlayer & a_Player) override;

View File

@ -866,14 +866,14 @@ bool cPluginManager::CallHookPlayerMoving(cPlayer & a_Player, const Vector3d a_O
bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange)
{
FIND_HOOK(HOOK_PLAYER_PLACED_BLOCK);
VERIFY_HOOK;
for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr)
{
if ((*itr)->OnPlayerPlacedBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta))
if ((*itr)->OnPlayerPlacedBlock(a_Player, a_BlockChange))
{
return true;
}
@ -885,14 +885,14 @@ bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX,
bool cPluginManager::CallHookPlayerPlacingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
bool cPluginManager::CallHookPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange)
{
FIND_HOOK(HOOK_PLAYER_PLACING_BLOCK);
VERIFY_HOOK;
for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr)
{
if ((*itr)->OnPlayerPlacingBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta))
if ((*itr)->OnPlayerPlacingBlock(a_Player, a_BlockChange))
{
return true;
}

View File

@ -207,10 +207,10 @@ public:
bool CallHookPlayerFishing (cPlayer & a_Player, cItems a_Reward);
bool CallHookPlayerFoodLevelChange (cPlayer & a_Player, int a_NewFoodLevel);
bool CallHookPlayerJoined (cPlayer & a_Player);
bool CallHookPlayerMoving (cPlayer & a_Player, const Vector3d a_OldPosition, const Vector3d a_NewPosition);
bool CallHookPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status);
bool CallHookPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
bool CallHookPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
bool CallHookPlayerMoving (cPlayer & a_Player, const Vector3d a_OldPosition, const Vector3d a_NewPosition);
bool CallHookPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange);
bool CallHookPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange);
bool CallHookPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ);
bool CallHookPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity);
bool CallHookPlayerShooting (cPlayer & a_Player);

View File

@ -14,24 +14,6 @@
void cBlockBedHandler::OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
)
{
if (a_BlockMeta < 8)
{
Vector3i Direction = MetaDataToDirection(a_BlockMeta);
a_ChunkInterface.SetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, a_BlockMeta | 0x8);
}
}
void cBlockBedHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
@ -151,7 +133,7 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface
cPlayerBedStateUnsetter Unsetter(Vector3i(a_BlockX + PillowDirection.x, a_BlockY, a_BlockZ + PillowDirection.z), a_WorldInterface);
a_WorldInterface.ForEachPlayer(Unsetter);
a_WorldInterface.SetTimeOfDay(0);
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta & 0xB); // Where 0xB = 1011, and zero is to make sure 'occupied' bit is always unset
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta & 0x0b); // Clear the "occupied" bit of the bed's block
}
}
}

View File

@ -23,7 +23,6 @@ public:
}
virtual void OnPlacedByPlayer(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, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override;
virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override;
virtual void 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

@ -85,18 +85,6 @@ public:
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override
{
int Meta = (((int)floor(a_Player->GetYaw() * 4.0 / 360.0 + 0.5) & 0x3) + 2) % 4;
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, m_BlockType, 0x8 | Meta);
}
virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override
{
NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);

View File

@ -62,50 +62,11 @@ public:
}
// Single chest, get meta from rotation only
a_BlockMeta = RotationToMetaData(yaw);
a_BlockMeta = PlayerYawToMetaData(yaw);
return true;
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override
{
// Check if this forms a doublechest, if so, need to adjust the meta:
cBlockArea Area;
if (!Area.Read(&a_ChunkInterface, a_BlockX - 1, a_BlockX + 1, a_BlockY, a_BlockY, a_BlockZ - 1, a_BlockZ + 1))
{
return;
}
double rot = a_Player->GetYaw(); // FIXME: Rename rot to yaw
// Choose meta from player rotation, choose only between 2 or 3
NIBBLETYPE NewMeta = ((rot >= -90) && (rot < 90)) ? 2 : 3;
if (
CheckAndAdjustNeighbor(a_ChunkInterface, Area, 0, 1, NewMeta) ||
CheckAndAdjustNeighbor(a_ChunkInterface, Area, 2, 1, NewMeta)
)
{
// Forming a double chest in the X direction
return;
}
// Choose meta from player rotation, choose only between 4 or 5
NewMeta = (rot < 0) ? 4 : 5;
if (
CheckAndAdjustNeighbor(a_ChunkInterface, Area, 1, 0, NewMeta) ||
CheckAndAdjustNeighbor(a_ChunkInterface, Area, 2, 2, NewMeta)
)
{
// Forming a double chest in the Z direction
return;
}
// Single chest, no further processing needed
}
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;
@ -180,30 +141,30 @@ public:
}
/// Translates player rotation when placing a chest into the chest block metadata. Valid for single chests only
static NIBBLETYPE RotationToMetaData(double a_Rotation)
/** Translates player yaw when placing a chest into the chest block metadata. Valid for single chests only */
static NIBBLETYPE PlayerYawToMetaData(double a_Yaw)
{
a_Rotation += 90 + 45; // So its not aligned with axis
a_Yaw += 90 + 45; // So its not aligned with axis
if (a_Rotation > 360.f)
if (a_Yaw > 360.f)
{
a_Rotation -= 360.f;
a_Yaw -= 360.f;
}
if ((a_Rotation >= 0.f) && (a_Rotation < 90.f))
if ((a_Yaw >= 0.f) && (a_Yaw < 90.f))
{
return 0x4;
return 0x04;
}
else if ((a_Rotation >= 180) && (a_Rotation < 270))
else if ((a_Yaw >= 180) && (a_Yaw < 270))
{
return 0x5;
return 0x05;
}
else if ((a_Rotation >= 90) && (a_Rotation < 180))
else if ((a_Yaw >= 90) && (a_Yaw < 180))
{
return 0x2;
return 0x02;
}
else
{
return 0x3;
return 0x03;
}
}

View File

@ -23,7 +23,7 @@ void cBlockDoorHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldIn
if (OldMeta & 8)
{
// Was upper part of door
if (IsDoor(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)))
if (IsDoorBlockType(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)))
{
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
}
@ -31,7 +31,7 @@ void cBlockDoorHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldIn
else
{
// Was lower part
if (IsDoor(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ)))
if (IsDoorBlockType(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ)))
{
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_AIR, 0);
}
@ -84,52 +84,34 @@ void cBlockDoorHandler::OnCancelRightClick(cChunkInterface & a_ChunkInterface, c
void cBlockDoorHandler::OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
)
{
NIBBLETYPE a_TopBlockMeta = 8;
if (
((a_BlockMeta == 0) && (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1) == m_BlockType)) ||
((a_BlockMeta == 1) && (a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ) == m_BlockType)) ||
((a_BlockMeta == 2) && (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1) == m_BlockType)) ||
((a_BlockMeta == 3) && (a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ) == m_BlockType))
)
{
a_TopBlockMeta = 9;
}
a_ChunkInterface.SetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, m_BlockType, a_TopBlockMeta);
}
NIBBLETYPE cBlockDoorHandler::MetaRotateCCW(NIBBLETYPE a_Meta)
{
if (a_Meta & 0x08)
{
// The meta doesn't change for the top block
return a_Meta;
}
else
{
// Rotate the bottom block
return super::MetaRotateCCW(a_Meta);
}
}
NIBBLETYPE cBlockDoorHandler::MetaRotateCW(NIBBLETYPE a_Meta)
{
if (a_Meta & 0x08)
{
// The meta doesn't change for the top block
return a_Meta;
}
else
{
// Rotate the bottom block
return super::MetaRotateCW(a_Meta);
}
}
@ -138,8 +120,10 @@ NIBBLETYPE cBlockDoorHandler::MetaRotateCW(NIBBLETYPE a_Meta)
NIBBLETYPE cBlockDoorHandler::MetaMirrorXY(NIBBLETYPE a_Meta)
{
// Top bit (0x08) contains door panel type (Top/Bottom panel) Only Bottom panels contain position data
// Return a_Meta if panel is a top panel (0x08 bit is set to 1)
/*
Top bit (0x08) contains door block position (Top / Bottom). Only Bottom blocks contain position data
Return a_Meta if panel is a top panel (0x08 bit is set to 1)
*/
// Note: Currently, you can not properly mirror the hinges on a double door. The orientation of the door is stored
// in only the bottom tile while the hinge position is in the top tile. This function only operates on one tile at a time,

View File

@ -101,14 +101,6 @@ public:
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override;
virtual bool IsUseable(void) override
{
return true;
@ -117,11 +109,19 @@ public:
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));
return ((a_RelY > 0) && CanBeOn(a_Chunk.GetBlock(a_RelX, a_RelY - 1, a_RelZ)));
}
bool CanReplaceBlock(BLOCKTYPE a_BlockType)
/** Returns true if door can be placed on the specified block type. */
static bool CanBeOn(BLOCKTYPE a_BlockType)
{
// Vanilla refuses to place doors on transparent blocks
return !cBlockInfo::IsTransparent(a_BlockType);
}
static bool CanReplaceBlock(BLOCKTYPE a_BlockType)
{
switch (a_BlockType)
{
@ -170,8 +170,21 @@ public:
}
/** Returns a vector pointing one block in the direction the door is facing (where the outside is). */
inline static Vector3i GetRelativeDirectionToOutside(NIBBLETYPE a_BlockMeta)
{
switch (a_BlockMeta & 0x03)
{
case 0: return Vector3i(-1, 0, 0); // Facing West / XM
case 1: return Vector3i( 0, 0, -1); // Facing North / ZM
case 2: return Vector3i( 1, 0, 0); // Facing East / XP
default: return Vector3i( 0, 0, 1); // Facing South / ZP
}
}
/** Returns true if the specified blocktype is any kind of door */
inline static bool IsDoor(BLOCKTYPE a_Block)
inline static bool IsDoorBlockType(BLOCKTYPE a_Block)
{
switch (a_Block)
{
@ -193,6 +206,8 @@ public:
}
/** Returns true iff the door at the specified coords is open.
The coords may point to either the top part or the bottom part of the door. */
static NIBBLETYPE IsOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
NIBBLETYPE Meta = GetCompleteDoorMeta(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ);
@ -237,7 +252,7 @@ public:
static void SetOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_Open)
{
BLOCKTYPE Block = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ);
if (!IsDoor(Block))
if (!IsDoorBlockType(Block))
{
return;
}

View File

@ -369,7 +369,7 @@ void cBlockHandler::OnUpdate(cChunkInterface & cChunkInterface, cWorldInterface
void cBlockHandler::OnPlacedByPlayer(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, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
void cBlockHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange)
{
}

View File

@ -42,15 +42,12 @@ public:
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
);
/// Called by cWorld::SetBlock() after the block has been set
/** Called by cWorld::SetBlock() after the block has been set */
virtual void OnPlaced(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
/// Called by cClientHandle::HandlePlaceBlock() after the player has placed a new block. Called after OnPlaced().
/** Called by cPlayer::PlaceBlocks() for each block after it has been set to the world. Called after OnPlaced(). */
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange
);
/// Called before the player has destroyed a block
@ -96,7 +93,8 @@ public:
*/
// virtual bool CanBePlacedAt(cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Dir);
/// Called to check whether this block supports a rclk action. If it returns true, OnUse() is called
/** Called to check whether this block supports a rclk action.
If it returns true, OnUse() is called */
virtual bool IsUseable(void);
/** Indicates whether the client will click through this block.
@ -109,20 +107,21 @@ public:
*/
virtual bool DoesIgnoreBuildCollision(void);
/// <summary>Similar to DoesIgnoreBuildCollision(void), but is used for cases where block meta/player item-in-hand is needed to determine collision (thin snow)</summary>
/** Similar to DoesIgnoreBuildCollision(void), but is used for cases where block's meta or
player's item-in-hand is needed to determine collision (thin snow) */
virtual bool DoesIgnoreBuildCollision(cPlayer *, NIBBLETYPE a_Meta)
{
UNUSED(a_Meta);
return DoesIgnoreBuildCollision();
}
/// <summary>Returns if this block drops if it gets destroyed by an unsuitable situation. Default: true</summary>
/** Returns if this block drops if it gets destroyed by an unsuitable situation.
Default: true */
virtual bool DoesDropOnUnsuitable(void);
/** Called when one of the neighbors gets set; equivalent to MC block update.
By default drops if position no more suitable (CanBeAt(), DoesDropOnUnsuitable(), Drop()),
and wakes up all simulators on the block.
*/
and wakes up all simulators on the block. */
virtual void Check(cChunkInterface & ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk);
/// <summary>Rotates a given block meta counter-clockwise. Default: no change</summary>

View File

@ -12,16 +12,18 @@ class cBlockMobHeadHandler :
public cBlockEntityHandler
{
public:
cBlockMobHeadHandler(BLOCKTYPE a_BlockType)
: cBlockEntityHandler(a_BlockType)
cBlockMobHeadHandler(BLOCKTYPE a_BlockType):
cBlockEntityHandler(a_BlockType)
{
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
// The drop spawn is in OnDestroyed method
// The drop spawn is in the OnDestroyedByPlayer method
}
virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override
{
if (a_Player->IsGameModeCreative())
@ -61,202 +63,6 @@ public:
a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback);
}
bool TrySpawnWither(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
if (a_BlockY < 2)
{
return false;
}
class cCallback : public cBlockEntityCallback
{
bool m_IsWither;
virtual bool Item(cBlockEntity * a_BlockEntity)
{
if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD)
{
return false;
}
cMobHeadEntity * MobHeadEntity = static_cast<cMobHeadEntity*>(a_BlockEntity);
m_IsWither = (MobHeadEntity->GetType() == SKULL_TYPE_WITHER);
return false;
}
public:
cCallback () : m_IsWither(false) {}
bool IsWither(void) const { return m_IsWither; }
void Reset(void) { m_IsWither = false; }
} CallbackA, CallbackB;
class cPlayerCallback : public cPlayerListCallback
{
Vector3f m_Pos;
virtual bool Item(cPlayer * a_Player)
{
// TODO 2014-05-21 xdot: Vanilla minecraft uses an AABB check instead of a radius one
double Dist = (a_Player->GetPosition() - m_Pos).Length();
if (Dist < 50.0)
{
// If player is close, award achievement
a_Player->AwardAchievement(achSpawnWither);
}
return false;
}
public:
cPlayerCallback(const Vector3f & a_Pos) : m_Pos(a_Pos) {}
} PlayerCallback(Vector3f((float)a_BlockX, (float)a_BlockY, (float)a_BlockZ));
a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, CallbackA);
if (!CallbackA.IsWither())
{
return false;
}
CallbackA.Reset();
BLOCKTYPE BlockY1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ);
BLOCKTYPE BlockY2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ);
if ((BlockY1 != E_BLOCK_SOULSAND) || (BlockY2 != E_BLOCK_SOULSAND))
{
return false;
}
a_WorldInterface.DoWithBlockEntityAt(a_BlockX - 1, a_BlockY, a_BlockZ, CallbackA);
a_WorldInterface.DoWithBlockEntityAt(a_BlockX + 1, a_BlockY, a_BlockZ, CallbackB);
BLOCKTYPE Block1 = a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ);
BLOCKTYPE Block2 = a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ);
if ((Block1 == E_BLOCK_SOULSAND) && (Block2 == E_BLOCK_SOULSAND) && CallbackA.IsWither() && CallbackB.IsWither())
{
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0);
// Block entities
a_ChunkInterface.SetBlock(a_BlockX + 1, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.SetBlock(a_BlockX - 1, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
// Spawn the wither:
a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtWither);
// Award Achievement
a_WorldInterface.ForEachPlayer(PlayerCallback);
return true;
}
CallbackA.Reset();
CallbackB.Reset();
a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ - 1, CallbackA);
a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ + 1, CallbackB);
Block1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1);
Block2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1);
if ((Block1 == E_BLOCK_SOULSAND) && (Block2 == E_BLOCK_SOULSAND) && CallbackA.IsWither() && CallbackB.IsWither())
{
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0);
// Block entities
a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ + 1, E_BLOCK_AIR, 0);
a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ - 1, E_BLOCK_AIR, 0);
// Spawn the wither:
a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtWither);
// Award Achievement
a_WorldInterface.ForEachPlayer(PlayerCallback);
return true;
}
return false;
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override
{
class cCallback : public cBlockEntityCallback
{
cPlayer * m_Player;
NIBBLETYPE m_OldBlockMeta;
NIBBLETYPE m_NewBlockMeta;
virtual bool Item(cBlockEntity * a_BlockEntity)
{
if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD)
{
return false;
}
cMobHeadEntity * MobHeadEntity = static_cast<cMobHeadEntity*>(a_BlockEntity);
int Rotation = 0;
if (m_NewBlockMeta == 1)
{
Rotation = (int) floor(m_Player->GetYaw() * 16.0F / 360.0F + 0.5) & 0xF;
}
MobHeadEntity->SetType(static_cast<eMobHeadType>(m_OldBlockMeta));
MobHeadEntity->SetRotation(static_cast<eMobHeadRotation>(Rotation));
MobHeadEntity->GetWorld()->BroadcastBlockEntity(MobHeadEntity->GetPosX(), MobHeadEntity->GetPosY(), MobHeadEntity->GetPosZ());
return false;
}
public:
cCallback (cPlayer * a_CBPlayer, NIBBLETYPE a_OldBlockMeta, NIBBLETYPE a_NewBlockMeta) :
m_Player(a_CBPlayer),
m_OldBlockMeta(a_OldBlockMeta),
m_NewBlockMeta(a_NewBlockMeta)
{}
};
cCallback Callback(a_Player, a_BlockMeta, static_cast<NIBBLETYPE>(a_BlockFace));
a_BlockMeta = (NIBBLETYPE)a_BlockFace;
a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback);
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, a_BlockMeta);
if (a_BlockMeta == SKULL_TYPE_WITHER)
{
static const Vector3i Coords[] =
{
Vector3i( 0, 0, 0),
Vector3i( 1, 0, 0),
Vector3i(-1, 0, 0),
Vector3i( 0, 0, 1),
Vector3i( 0, 0, -1),
};
for (size_t i = 0; i < ARRAYCOUNT(Coords); ++i)
{
if (TrySpawnWither(a_ChunkInterface, a_WorldInterface, a_BlockX + Coords[i].x, a_BlockY, a_BlockZ + Coords[i].z))
{
break;
}
} // for i - Coords[]
}
}
} ;

View File

@ -17,70 +17,6 @@ public:
}
virtual void OnPlacedByPlayer(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, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override
{
// Check whether the pumpkin is a part of a golem or a snowman
if (a_BlockY < 2)
{
// The pumpkin is too low for a golem / snowman
return;
}
BLOCKTYPE BlockY1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ);
BLOCKTYPE BlockY2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ);
// Check for a snow golem:
if ((BlockY1 == E_BLOCK_SNOW_BLOCK) && (BlockY2 == E_BLOCK_SNOW_BLOCK))
{
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0);
a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtSnowGolem);
return;
}
// Check for an iron golem. First check only the body and legs, since those are the same for both orientations:
if ((BlockY1 != E_BLOCK_IRON_BLOCK) || (BlockY2 != E_BLOCK_IRON_BLOCK))
{
// One of the blocks is not an iron, no chance of a golem here
return;
}
// Now check both orientations for hands:
if (
(a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ) == E_BLOCK_IRON_BLOCK) &&
(a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ) == E_BLOCK_IRON_BLOCK)
)
{
// Remove the iron blocks:
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0);
// Spawn the golem:
a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtIronGolem);
}
else if (
(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1) == E_BLOCK_IRON_BLOCK) &&
(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1) == E_BLOCK_IRON_BLOCK)
)
{
// Remove the iron blocks:
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1, E_BLOCK_AIR, 0);
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0);
// Spawn the golem:
a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtIronGolem);
}
}
virtual bool GetPlacementBlockTypeMeta(
cChunkInterface & a_ChunkInterface, cPlayer * a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,

View File

@ -53,17 +53,6 @@ public:
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override
{
a_Player->GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ);
}
virtual NIBBLETYPE MetaRotateCW(NIBBLETYPE a_Meta) override
{
return (a_Meta + 4) & 0x0f;

View File

@ -27,17 +27,6 @@ public:
}
virtual void OnPlacedByPlayer(
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,
BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta
) override
{
a_Player->GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ);
}
virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override
{
int BlockX = (a_Chunk.GetPosX() * cChunkDef::Width) + a_RelX;

View File

@ -49,14 +49,14 @@
////////////////////////////////////////////////////////////////////////////////
// sSetBlock:
sSetBlock::sSetBlock( int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) // absolute block position
: x( a_BlockX)
, y( a_BlockY)
, z( a_BlockZ)
, BlockType( a_BlockType)
, BlockMeta( a_BlockMeta)
sSetBlock::sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta):
m_RelX(a_BlockX),
m_RelY(a_BlockY),
m_RelZ(a_BlockZ),
m_BlockType(a_BlockType),
m_BlockMeta(a_BlockMeta)
{
cChunkDef::AbsoluteToRelative(x, y, z, ChunkX, ChunkZ);
cChunkDef::AbsoluteToRelative(m_RelX, m_RelY, m_RelZ, m_ChunkX, m_ChunkZ);
}

View File

@ -347,18 +347,32 @@ public:
struct sSetBlock
{
int x, y, z;
int ChunkX, ChunkZ;
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
int m_RelX, m_RelY, m_RelZ;
int m_ChunkX, m_ChunkZ;
BLOCKTYPE m_BlockType;
NIBBLETYPE m_BlockMeta;
sSetBlock( int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // absolute block position
sSetBlock(int a_ChunkX, int a_ChunkZ, int a_X, int a_Y, int a_Z, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) :
x(a_X), y(a_Y), z(a_Z),
ChunkX(a_ChunkX), ChunkZ(a_ChunkZ),
BlockType(a_BlockType),
BlockMeta(a_BlockMeta)
{}
sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
sSetBlock(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) :
m_RelX(a_RelX), m_RelY(a_RelY), m_RelZ(a_RelZ),
m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ),
m_BlockType(a_BlockType),
m_BlockMeta(a_BlockMeta)
{
ASSERT((a_RelX >= 0) && (a_RelX < cChunkDef::Width));
ASSERT((a_RelZ >= 0) && (a_RelZ < cChunkDef::Width));
}
/** Returns the absolute X coord of the stored block. */
int GetX(void) const { return m_RelX + cChunkDef::Width * m_ChunkX; }
/** Returns the absolute Y coord of the stored block.
Is the same as relative Y coords, because there's no Y relativization. */
int GetY(void) const { return m_RelY; }
/** Returns the absolute Z coord of the stored block. */
int GetZ(void) const { return m_RelZ + cChunkDef::Width * m_ChunkZ; }
};
typedef std::list<sSetBlock> sSetBlockList;
@ -385,6 +399,17 @@ public:
typedef std::list<cChunkCoords> cChunkCoordsList;
typedef std::vector<cChunkCoords> cChunkCoordsVector;
/** A simple hash function for chunk coords, we assume that chunk coords won't use more than 16 bits, so the hash is almost an identity.
Used for std::unordered_map<cChunkCoords, ...> */
class cChunkCoordsHash
{
public:
size_t operator () (const cChunkCoords & a_Coords) const
{
return (static_cast<size_t>(a_Coords.m_ChunkX) << 16) ^ static_cast<size_t>(a_Coords.m_ChunkZ);
}
};

View File

@ -1101,17 +1101,17 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList)
// Process all items from a_BlockList, either successfully or by placing into Failed
while (!a_BlockList.empty())
{
int ChunkX = a_BlockList.front().ChunkX;
int ChunkZ = a_BlockList.front().ChunkZ;
int ChunkX = a_BlockList.front().m_ChunkX;
int ChunkZ = a_BlockList.front().m_ChunkZ;
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ);
if ((Chunk != nullptr) && Chunk->IsValid())
{
for (sSetBlockList::iterator itr = a_BlockList.begin(); itr != a_BlockList.end();)
{
if ((itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ))
if ((itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ))
{
Chunk->FastSetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta);
Chunk->FastSetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta);
itr = a_BlockList.erase(itr);
}
else
@ -1125,7 +1125,7 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList)
// The chunk is not valid, move all blocks within this chunk to Failed
for (sSetBlockList::iterator itr = a_BlockList.begin(); itr != a_BlockList.end();)
{
if ((itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ))
if ((itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ))
{
Failed.push_back(*itr);
itr = a_BlockList.erase(itr);
@ -1146,6 +1146,34 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList)
void cChunkMap::SetBlocks(const sSetBlockVector & a_Blocks)
{
cCSLock lock(m_CSLayers);
cChunkPtr chunk = nullptr;
int lastChunkX = 0x7fffffff; // Bogus coords so that chunk is updated on first pass
int lastChunkZ = 0x7fffffff;
for (auto block: a_Blocks)
{
// Update the chunk, if different from last time:
if ((block.m_ChunkX != lastChunkX) || (block.m_ChunkZ != lastChunkZ))
{
lastChunkX = block.m_ChunkX;
lastChunkZ = block.m_ChunkZ;
chunk = GetChunk(lastChunkX, lastChunkZ);
}
// If the chunk is valid, set the block:
if (chunk != nullptr)
{
chunk->SetBlock(block.m_RelX, block.m_RelY, block.m_RelZ, block.m_BlockType, block.m_BlockMeta);
}
} // for block - a_Blocks[]
}
void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player)
{
int BlockX = (int)(a_Player.GetPosX()); // Truncating doesn't matter much; we're scanning entire chunks anyway
@ -1175,28 +1203,28 @@ void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player)
BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ)
{
int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ;
int ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ);
// First check if it isn't queued in the m_FastSetBlockQueue:
{
int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ;
int ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ);
cCSLock Lock(m_CSFastSetBlock);
for (sSetBlockList::iterator itr = m_FastSetBlockQueue.begin(); itr != m_FastSetBlockQueue.end(); ++itr)
{
if ((itr->x == X) && (itr->y == Y) && (itr->z == Z) && (itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ))
if ((itr->m_RelX == X) && (itr->m_RelY == Y) && (itr->m_RelZ == Z) && (itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ))
{
return itr->BlockType;
return itr->m_BlockType;
}
} // for itr - m_FastSetBlockQueue[]
}
int ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ);
// Not in the queue, query the chunk, if loaded:
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ);
if ((Chunk != nullptr) && Chunk->IsValid())
{
return Chunk->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
return Chunk->GetBlock(X, Y, Z);
}
return 0;
}
@ -1207,25 +1235,28 @@ BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ)
NIBBLETYPE cChunkMap::GetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ)
{
int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ;
int ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ);
// First check if it isn't queued in the m_FastSetBlockQueue:
{
cCSLock Lock(m_CSFastSetBlock);
for (sSetBlockList::iterator itr = m_FastSetBlockQueue.begin(); itr != m_FastSetBlockQueue.end(); ++itr)
{
if ((itr->x == a_BlockX) && (itr->y == a_BlockY) && (itr->z == a_BlockZ))
if ((itr->m_RelX == X) && (itr->m_RelY == Y) && (itr->m_RelZ == Z) && (itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ))
{
return itr->BlockMeta;
return itr->m_BlockMeta;
}
} // for itr - m_FastSetBlockQueue[]
}
int ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ);
// Not in the queue, query the chunk, if loaded:
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ);
cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ);
if ((Chunk != nullptr) && Chunk->IsValid())
{
return Chunk->GetMeta(a_BlockX, a_BlockY, a_BlockZ);
return Chunk->GetMeta(X, Y, Z);
}
return 0;
}
@ -1373,14 +1404,14 @@ void cChunkMap::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_Filt
cCSLock Lock(m_CSLayers);
for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr)
{
cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ);
cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ);
if ((Chunk == nullptr) || !Chunk->IsValid())
{
continue;
}
if (Chunk->GetBlock(itr->x, itr->y, itr->z) == a_FilterBlockType)
if (Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ) == a_FilterBlockType)
{
Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta);
Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta);
}
}
}
@ -1394,24 +1425,24 @@ void cChunkMap::ReplaceTreeBlocks(const sSetBlockVector & a_Blocks)
cCSLock Lock(m_CSLayers);
for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr)
{
cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ);
cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ);
if ((Chunk == nullptr) || !Chunk->IsValid())
{
continue;
}
switch (Chunk->GetBlock(itr->x, itr->y, itr->z))
switch (Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ))
{
CASE_TREE_OVERWRITTEN_BLOCKS:
{
Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta);
Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta);
break;
}
case E_BLOCK_LEAVES:
case E_BLOCK_NEW_LEAVES:
{
if ((itr->BlockType == E_BLOCK_LOG) || (itr->BlockType == E_BLOCK_NEW_LOG))
if ((itr->m_BlockType == E_BLOCK_LOG) || (itr->m_BlockType == E_BLOCK_NEW_LOG))
{
Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta);
Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta);
}
break;
}
@ -1507,7 +1538,7 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure)
cCSLock Lock(m_CSLayers);
for (sSetBlockVector::iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr)
{
cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ);
cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ);
if ((Chunk == nullptr) || !Chunk->IsValid())
{
if (!a_ContinueOnFailure)
@ -1517,8 +1548,8 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure)
res = false;
continue;
}
itr->BlockType = Chunk->GetBlock(itr->x, itr->y, itr->z);
itr->BlockMeta = Chunk->GetMeta(itr->x, itr->y, itr->z);
itr->m_BlockType = Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ);
itr->m_BlockMeta = Chunk->GetMeta(itr->m_RelX, itr->m_RelY, itr->m_RelZ);
}
return res;
}
@ -1527,11 +1558,11 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure)
bool cChunkMap::DigBlock(int a_X, int a_Y, int a_Z)
bool cChunkMap::DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ)
{
int PosX = a_X, PosY = a_Y, PosZ = a_Z, ChunkX, ChunkZ;
int PosX = a_BlockX, PosY = a_BlockY, PosZ = a_BlockZ, ChunkX, ChunkZ;
cChunkDef::AbsoluteToRelative( PosX, PosY, PosZ, ChunkX, ChunkZ);
cChunkDef::AbsoluteToRelative(PosX, PosY, PosZ, ChunkX, ChunkZ);
{
cCSLock Lock(m_CSLayers);
@ -1542,7 +1573,7 @@ bool cChunkMap::DigBlock(int a_X, int a_Y, int a_Z)
}
DestChunk->SetBlock(PosX, PosY, PosZ, E_BLOCK_AIR, 0);
m_World->GetSimulatorManager()->WakeUp(a_X, a_Y, a_Z, DestChunk);
m_World->GetSimulatorManager()->WakeUp(a_BlockX, a_BlockY, a_BlockZ, DestChunk);
}
return true;

View File

@ -143,7 +143,13 @@ public:
void FastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
void FastSetQueuedBlocks();
void FastSetBlocks (sSetBlockList & a_BlockList);
void FastSetBlocks(sSetBlockList & a_BlockList);
/** Performs the specified single-block set operations simultaneously, as if SetBlock() was called for each item.
Is more efficient than calling SetBlock() multiple times.
If the chunk for any of the blocks is not loaded, the set operation is ignored silently. */
void SetBlocks(const sSetBlockVector & a_Blocks);
void CollectPickupsByPlayer(cPlayer & a_Player);
BLOCKTYPE GetBlock (int a_BlockX, int a_BlockY, int a_BlockZ);
@ -173,11 +179,20 @@ public:
(Re)sends the chunks to their relevant clients if successful. */
bool SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome);
/** Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. */
/** Retrieves block types and metas of the specified blocks.
If a chunk is not loaded, doesn't modify the block and consults a_ContinueOnFailure whether to process the rest of the array.
Returns true if all blocks were read, false if any one failed. */
bool GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure);
bool DigBlock (int a_X, int a_Y, int a_Z);
void SendBlockTo(int a_X, int a_Y, int a_Z, cPlayer * a_Player);
/** Removes the block at the specified coords and wakes up simulators.
Returns false if the chunk is not loaded (and the block is not dug).
Returns true if successful. */
bool DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ);
/** Sends the block at the specified coords to the specified player.
Uses a blockchange packet to send the block.
If the relevant chunk isn't loaded, doesn't do anything. */
void SendBlockTo(int a_BlockX, int a_BlockY, int a_BlockZ, cPlayer * a_Player);
/** Compares clients of two chunks, calls the callback accordingly */
void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback);

View File

@ -1346,12 +1346,19 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e
if (ItemHandler->IsPlaceable() && (a_BlockFace != BLOCK_FACE_NONE))
{
HandlePlaceBlock(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, *ItemHandler);
if (!ItemHandler->OnPlayerPlace(*World, *m_Player, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
{
// Placement failed, bail out
return;
}
}
else if ((ItemHandler->IsFood() || ItemHandler->IsDrinkable(EquippedDamage)))
{
if ((m_Player->IsSatiated() || m_Player->IsGameModeCreative()) &&
ItemHandler->IsFood() && (Equipped.m_ItemType != E_ITEM_GOLDEN_APPLE))
if (
(m_Player->IsSatiated() || m_Player->IsGameModeCreative()) && // Only creative or hungry players can eat
ItemHandler->IsFood() &&
(Equipped.m_ItemType != E_ITEM_GOLDEN_APPLE) // Golden apple is a special case, it is used instead of eaten
)
{
// The player is satiated or in creative, and trying to eat
return;
@ -1379,151 +1386,6 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e
void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler)
{
BLOCKTYPE EquippedBlock = (BLOCKTYPE)(m_Player->GetEquippedItem().m_ItemType);
if (a_BlockFace < 0)
{
// Clicked in air
return;
}
cWorld * World = m_Player->GetWorld();
BLOCKTYPE ClickedBlock;
NIBBLETYPE ClickedBlockMeta;
NIBBLETYPE EquippedBlockDamage = (NIBBLETYPE)(m_Player->GetEquippedItem().m_ItemDamage);
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The block is being placed outside the world, ignore this packet altogether (#128)
return;
}
World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);
// Special slab handling - placing a slab onto another slab produces a dblslab instead:
if (
cBlockSlabHandler::IsAnySlabType(ClickedBlock) && // Is there a slab already?
cBlockSlabHandler::IsAnySlabType(EquippedBlock) && // Is the player placing another slab?
((ClickedBlockMeta & 0x07) == EquippedBlockDamage) && // Is it the same slab type?
(
(a_BlockFace == BLOCK_FACE_TOP) || // Clicking the top of a bottom slab
(a_BlockFace == BLOCK_FACE_BOTTOM) // Clicking the bottom of a top slab
)
)
{
// Coordinates at clicked block, which was an eligible slab, and either top or bottom faces were clicked
// If clicked top face and slab occupies the top voxel, we want a slab to be placed above it (therefore increment Y)
// Else if clicked bottom face and slab occupies the bottom voxel, decrement Y for the same reason
// Don't touch coordinates if anything else because a dblslab opportunity is present
if ((ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_TOP))
{
++a_BlockY;
}
else if (!(ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_BOTTOM))
{
--a_BlockY;
}
World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);
}
else
{
// Check if the block ignores build collision (water, grass etc.):
if (
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() ||
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(m_Player, ClickedBlockMeta)
)
{
cChunkInterface ChunkInterface(World->GetChunkMap());
BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ);
}
else
{
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The block is being placed outside the world, ignore this packet altogether (#128)
return;
}
NIBBLETYPE PlaceMeta;
BLOCKTYPE PlaceBlock;
World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta);
// Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed.
// No need to do combinability (dblslab) checks, client will do that here.
if (cBlockSlabHandler::IsAnySlabType(PlaceBlock))
{
// It's a slab, don't do checks and proceed to double-slabbing
}
else
{
if (
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() &&
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(m_Player, PlaceMeta)
)
{
// Tried to place a block *into* another?
// Happens when you place a block aiming at side of block with a torch on it or stem beside it
return;
}
}
}
}
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
if (!a_ItemHandler.GetPlacementBlockTypeMeta(World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
{
// Handler refused the placement, send that information back to the client:
World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
m_Player->GetInventory().SendEquippedSlot();
return;
}
cBlockHandler * NewBlock = BlockHandler(BlockType);
if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
{
// A plugin doesn't agree with placing the block, revert the block on the client:
World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
m_Player->GetInventory().SendEquippedSlot();
return;
}
// The actual block placement:
World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
if (!m_Player->IsGameModeCreative())
{
m_Player->GetInventory().RemoveOneEquippedItem();
}
cChunkInterface ChunkInterface(World->GetChunkMap());
NewBlock->OnPlacedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType);
float Volume = 1.0f, Pitch = 0.8f;
if (PlaceSound == "dig.metal")
{
Pitch = 1.2f;
PlaceSound = "dig.stone";
}
else if (PlaceSound == "random.anvil_land")
{
Volume = 0.65f;
}
World->BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch);
cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
}
void cClientHandle::HandleChat(const AString & a_Message)
{
// We no longer need to postpone message processing, because the messages already arrive in the Tick thread

View File

@ -459,9 +459,6 @@ private:
UInt32 m_ProtocolVersion;
/** Handles the block placing packet when it is a real block placement (not block-using, item-using or eating) */
void HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler);
/** Returns true if the rate block interactions is within a reasonable limit (bot protection) */
bool CheckBlockInteractionsRate(void);

View File

@ -2,6 +2,7 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Player.h"
#include <unordered_map>
#include "../ChatColor.h"
#include "../Server.h"
#include "../UI/Window.h"
@ -19,6 +20,10 @@
#include "../WorldStorage/StatSerializer.h"
#include "../CompositeChat.h"
#include "../Blocks/BlockHandler.h"
#include "../Blocks/BlockSlab.h"
#include "../Blocks/ChunkInterface.h"
#include "../IniFile.h"
#include "json/json.h"
@ -2168,6 +2173,97 @@ void cPlayer::LoadRank(void)
bool cPlayer::PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
sSetBlockVector blk{{a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta}};
return PlaceBlocks(blk);
}
void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Range)
{
// Collect the coords of all the blocks to send:
sSetBlockVector blks;
for (int y = a_BlockY - a_Range + 1; y < a_BlockY + a_Range; y++)
{
for (int z = a_BlockZ - a_Range + 1; z < a_BlockZ + a_Range; z++)
{
for (int x = a_BlockX - a_Range + 1; x < a_BlockX + a_Range; x++)
{
blks.emplace_back(x, y, z, E_BLOCK_AIR, 0); // Use fake blocktype, it will get set later on.
};
};
} // for y
// Get the values of all the blocks:
if (!m_World->GetBlocks(blks, false))
{
LOGD("%s: Cannot query all blocks, not sending an update", __FUNCTION__);
return;
}
// Divide the block changes by their respective chunks:
std::unordered_map<cChunkCoords, sSetBlockVector, cChunkCoordsHash> Changes;
for (const auto & blk: blks)
{
Changes[cChunkCoords(blk.m_ChunkX, blk.m_ChunkZ)].push_back(blk);
} // for blk - blks[]
blks.clear();
// Send the blocks for each affected chunk:
for (auto itr = Changes.cbegin(), end = Changes.cend(); itr != end; ++itr)
{
m_ClientHandle->SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second);
}
}
bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks)
{
// Call the "placing" hooks; if any fail, abort:
cPluginManager * pm = cPluginManager::Get();
for (auto blk: a_Blocks)
{
if (pm->CallHookPlayerPlacingBlock(*this, blk))
{
// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
for (auto blk2: a_Blocks)
{
m_World->SendBlockTo(blk2.GetX(), blk2.GetY(), blk2.GetZ(), this);
}
return false;
}
} // for blk - a_Blocks[]
// Set the blocks:
m_World->SetBlocks(a_Blocks);
// Notify the blockhandlers:
cChunkInterface ChunkInterface(m_World->GetChunkMap());
for (auto blk: a_Blocks)
{
cBlockHandler * newBlock = BlockHandler(blk.m_BlockType);
newBlock->OnPlacedByPlayer(ChunkInterface, *m_World, this, blk);
}
// Call the "placed" hooks:
for (auto blk: a_Blocks)
{
pm->CallHookPlayerPlacedBlock(*this, blk);
}
return true;
}
void cPlayer::Detach()
{
super::Detach();

View File

@ -440,8 +440,27 @@ public:
Loads the m_Rank, m_Permissions, m_MsgPrefix, m_MsgSuffix and m_MsgNameColorCode members. */
void LoadRank(void);
/** Calls the block-placement hook and places the block in the world, unless refused by the hook.
If the hook prevents the placement, sends the current block at the specified coords back to the client.
Assumes that the block is in a currently loaded chunk.
Returns true if the block is successfully placed. */
bool PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
/** Sends the block in the specified range around the specified coord to the client
as a block change packet.
The blocks in range (a_BlockX - a_Range, a_BlockX + a_Range) are sent (NY-metric). */
void SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Range = 1);
// tolua_end
/** Calls the block placement hooks and places the blocks in the world.
First the "placing" hooks for all the blocks are called, then the blocks are placed, and finally
the "placed" hooks are called.
If the any of the "placing" hooks aborts, none of the blocks are placed and the function returns false.
Returns true if all the blocks are placed.
Assumes that all the blocks are in currently loaded chunks. */
bool PlaceBlocks(const sSetBlockVector & a_Blocks);
// cEntity overrides:
virtual bool IsCrouched (void) const { return m_IsCrouched; }
virtual bool IsSprinting(void) const { return m_IsSprinting; }

View File

@ -123,18 +123,18 @@ void cStructGenTrees::GenerateSingleTree(
// Check if the generated image fits the terrain. Only the logs are checked:
for (sSetBlockVector::const_iterator itr = TreeLogs.begin(); itr != TreeLogs.end(); ++itr)
{
if ((itr->ChunkX != a_ChunkX) || (itr->ChunkZ != a_ChunkZ))
if ((itr->m_ChunkX != a_ChunkX) || (itr->m_ChunkZ != a_ChunkZ))
{
// Outside the chunk
continue;
}
if (itr->y >= cChunkDef::Height)
if (itr->m_RelY >= cChunkDef::Height)
{
// Above the chunk, cut off (this shouldn't happen too often, we're limiting trees to y < 230)
continue;
}
BLOCKTYPE Block = a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z);
BLOCKTYPE Block = a_ChunkDesc.GetBlockType(itr->m_RelX, itr->m_RelY, itr->m_RelZ);
switch (Block)
{
CASE_TREE_ALLOWED_BLOCKS:
@ -167,14 +167,14 @@ void cStructGenTrees::ApplyTreeImage(
// Put the generated image into a_BlockTypes, push things outside this chunk into a_Blocks
for (sSetBlockVector::const_iterator itr = a_Image.begin(), end = a_Image.end(); itr != end; ++itr)
{
if ((itr->ChunkX == a_ChunkX) && (itr->ChunkZ == a_ChunkZ) && (itr->y < cChunkDef::Height))
if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkZ == a_ChunkZ) && (itr->m_RelY < cChunkDef::Height))
{
// Inside this chunk, integrate into a_ChunkDesc:
switch (a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z))
switch (a_ChunkDesc.GetBlockType(itr->m_RelX, itr->m_RelY, itr->m_RelZ))
{
case E_BLOCK_LEAVES:
{
if (itr->BlockType != E_BLOCK_LOG)
if (itr->m_BlockType != E_BLOCK_LOG)
{
break;
}
@ -182,7 +182,7 @@ void cStructGenTrees::ApplyTreeImage(
}
CASE_TREE_OVERWRITTEN_BLOCKS:
{
a_ChunkDesc.SetBlockTypeMeta(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta);
a_ChunkDesc.SetBlockTypeMeta(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta);
break;
}

View File

@ -403,17 +403,17 @@ void GetLargeAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a
for (auto itr : a_LogBlocks)
{
// Get the log's X and Z coordinates
int X = itr.ChunkX * 16 + itr.x;
int Z = itr.ChunkZ * 16 + itr.z;
int X = itr.GetX();
int Z = itr.GetZ();
a_OtherBlocks.push_back(sSetBlock(X, itr.y - 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE));
PushCoordBlocks(X, itr.y - 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
a_OtherBlocks.push_back(sSetBlock(X, itr.m_RelY - 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE));
PushCoordBlocks(X, itr.m_RelY - 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
for (int y = -1; y <= 1; y++)
{
PushCoordBlocks (X, itr.y + y, Z, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
PushCoordBlocks (X, itr.m_RelY + y, Z, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
}
PushCoordBlocks(X, itr.y + 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
a_OtherBlocks.push_back(sSetBlock(X, itr.y + 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE));
PushCoordBlocks(X, itr.m_RelY + 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE);
a_OtherBlocks.push_back(sSetBlock(X, itr.m_RelY + 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE));
}
// Trunk:

View File

@ -10,12 +10,14 @@ SET (SRCS
SET (HDRS
ItemArmor.h
ItemBed.h
ItemBigFlower.h
ItemBoat.h
ItemBow.h
ItemBrewingStand.h
ItemBucket.h
ItemCake.h
ItemCauldron.h
ItemChest.h
ItemCloth.h
ItemComparator.h
ItemDoor.h
@ -38,18 +40,21 @@ SET (HDRS
ItemPainting.h
ItemPickaxe.h
ItemPotion.h
ItemPumpkin.h
ItemRedstoneDust.h
ItemRedstoneRepeater.h
ItemSapling.h
ItemSeeds.h
ItemShears.h
ItemShovel.h
ItemSlab.h
ItemSign.h
ItemSpawnEgg.h
ItemString.h
ItemSugarcane.h
ItemSword.h
ItemThrowable.h)
ItemThrowable.h
)
if(NOT MSVC)
add_library(Items ${SRCS} ${HDRS})

View File

@ -24,30 +24,36 @@ public:
return true;
}
virtual bool GetPlacementBlockTypeMeta(
cWorld * a_World, cPlayer * a_Player,
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ,
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// Can only be placed on the floor:
if (a_BlockFace != BLOCK_FACE_TOP)
{
// Can only be placed on the floor
return false;
}
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
a_BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player->GetYaw());
// The "foot" block:
sSetBlockVector blks;
NIBBLETYPE BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player.GetYaw());
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta);
// Check if there is empty space for the foot section:
Vector3i Direction = cBlockBedHandler::MetaDataToDirection(a_BlockMeta);
if (a_World->GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR)
// Check if there is empty space for the "head" block:
// (Vanilla only allows beds to be placed into air)
Vector3i Direction = cBlockBedHandler::MetaDataToDirection(BlockMeta);
if (a_World.GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR)
{
return false;
}
blks.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08);
a_BlockType = E_BLOCK_BED;
return true;
// Place both bed blocks:
return a_Player.PlaceBlocks(blks);
}
} ;

56
src/Items/ItemBigFlower.h Normal file
View File

@ -0,0 +1,56 @@
// ItemBigFlower.h
// Declares the cItemBigFlower class representing the cItemHandler for big flowers
#pragma once
#include "ItemHandler.h"
class cItemBigFlowerHandler:
public cItemHandler
{
typedef cItemHandler super;
public:
cItemBigFlowerHandler(void):
super(E_BLOCK_BIG_FLOWER)
{
}
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// Can only be placed on the floor:
if (a_BlockFace != BLOCK_FACE_TOP)
{
return false;
}
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// Place both blocks atomically:
sSetBlockVector blks;
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BIG_FLOWER, a_EquippedItem.m_ItemDamage & 0x07);
if (a_BlockY < cChunkDef::Height - 1)
{
blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_BIG_FLOWER, (a_EquippedItem.m_ItemDamage & 0x07) | 0x08);
}
return a_Player.PlaceBlocks(blks);
}
};

167
src/Items/ItemChest.h Normal file
View File

@ -0,0 +1,167 @@
// ItemChest.h
// Declares the cItemChestHandler class representing the cItemHandler descendant responsible for chests
#pragma once
#include "ItemHandler.h"
#include "../Blocks/BlockChest.h"
class cItemChestHandler:
public cItemHandler
{
typedef cItemHandler super;
public:
cItemChestHandler(int a_ItemType):
super(a_ItemType)
{
}
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
if (a_BlockFace < 0)
{
// Clicked in air
return false;
}
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The clicked block is outside the world, ignore this call altogether (#128)
return false;
}
// Check if the block ignores build collision (water, grass etc.):
BLOCKTYPE ClickedBlock;
NIBBLETYPE ClickedBlockMeta;
a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);
if (
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() ||
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta)
)
{
cChunkInterface ChunkInterface(a_World.GetChunkMap());
BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ);
}
else
{
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The block is being placed outside the world, ignore this packet altogether (#128)
return false;
}
NIBBLETYPE PlaceMeta;
BLOCKTYPE PlaceBlock;
a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta);
// Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed.
// No need to do combinability (dblslab) checks, client will do that here.
if (
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() &&
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta)
)
{
// Tried to place a block *into* another?
// Happens when you place a block aiming at side of block with a torch on it or stem beside it
return false;
}
}
// Check that there is at most one single neighbor of the same chest type:
static const Vector3i CrossCoords[] =
{
{-1, 0, 0},
{ 0, 0, -1},
{ 1, 0, 0},
{ 0, 0, 1},
};
int NeighborIdx = -1;
for (size_t i = 0; i < ARRAYCOUNT(CrossCoords); i++)
{
if (a_World.GetBlock(a_BlockX + CrossCoords[i].x, a_BlockY, a_BlockZ + CrossCoords[i].z) != m_ItemType)
{
continue;
}
if (NeighborIdx >= 0)
{
// Can't place here, there are already two neighbors, this would form a 3-block chest
return false;
}
NeighborIdx = static_cast<int>(i);
// Check that this neighbor is a single chest:
int bx = a_BlockX + CrossCoords[i].x;
int bz = a_BlockZ + CrossCoords[i].z;
for (size_t j = 0; j < ARRAYCOUNT(CrossCoords); j++)
{
if (a_World.GetBlock(bx + CrossCoords[j].x, a_BlockY, bz + CrossCoords[j].z) == m_ItemType)
{
return false;
}
} // for j
} // for i
// If there's no chest neighbor, place the single block chest and bail out:
BLOCKTYPE ChestBlockType = static_cast<BLOCKTYPE>(m_ItemType);
if (NeighborIdx < 0)
{
NIBBLETYPE Meta = cBlockChestHandler::PlayerYawToMetaData(a_Player.GetYaw());
return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta);
}
// There is a neighbor to which we need to adjust
double yaw = a_Player.GetYaw();
if ((NeighborIdx == 0) || (NeighborIdx == 2))
{
// The neighbor is in the X axis, form a X-axis-aligned dblchest:
NIBBLETYPE Meta = ((yaw >= -90) && (yaw < 90)) ? E_META_CHEST_FACING_ZM : E_META_CHEST_FACING_ZP;
// Place the new chest:
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta))
{
return false;
}
// Adjust the existing chest:
a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta);
return true;
}
// The neighbor is in the Z axis, form a Z-axis-aligned dblchest:
NIBBLETYPE Meta = (yaw < 0) ? E_META_CHEST_FACING_XM : E_META_CHEST_FACING_XP;
// Place the new chest:
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta))
{
return false;
}
// Adjust the existing chest:
a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta);
return true;
}
private:
cItemChestHandler(const cItemChestHandler &) = delete;
};

View File

@ -3,6 +3,7 @@
#include "ItemHandler.h"
#include "../World.h"
#include "../Blocks/BlockDoor.h"
@ -18,27 +19,43 @@ public:
}
virtual bool IsPlaceable(void) override
{
return true;
}
virtual bool GetPlacementBlockTypeMeta(
cWorld * a_World, cPlayer * a_Player,
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ,
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// Vanilla only allows door placement while clicking on the top face of the block below the door:
if (a_BlockFace != BLOCK_FACE_NONE)
{
return false;
}
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// Door (bottom block) can be placed in Y range of [1, 254]:
if ((a_BlockY < 1) || (a_BlockY + 2 >= cChunkDef::Height))
{
return false;
}
// The door needs a compatible block below it:
if ((a_BlockY > 0) && cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)))
{
return false;
}
// Get the block type of the door to place:
BLOCKTYPE BlockType;
switch (m_ItemType)
{
case E_ITEM_WOODEN_DOOR: a_BlockType = E_BLOCK_WOODEN_DOOR; break;
case E_ITEM_IRON_DOOR: a_BlockType = E_BLOCK_IRON_DOOR; break;
case E_ITEM_SPRUCE_DOOR: a_BlockType = E_BLOCK_SPRUCE_DOOR; break;
case E_ITEM_BIRCH_DOOR: a_BlockType = E_BLOCK_BIRCH_DOOR; break;
case E_ITEM_JUNGLE_DOOR: a_BlockType = E_BLOCK_JUNGLE_DOOR; break;
case E_ITEM_DARK_OAK_DOOR: a_BlockType = E_BLOCK_DARK_OAK_DOOR; break;
case E_ITEM_ACACIA_DOOR: a_BlockType = E_BLOCK_ACACIA_DOOR; break;
case E_ITEM_WOODEN_DOOR: BlockType = E_BLOCK_WOODEN_DOOR; break;
case E_ITEM_IRON_DOOR: BlockType = E_BLOCK_IRON_DOOR; break;
case E_ITEM_SPRUCE_DOOR: BlockType = E_BLOCK_SPRUCE_DOOR; break;
case E_ITEM_BIRCH_DOOR: BlockType = E_BLOCK_BIRCH_DOOR; break;
case E_ITEM_JUNGLE_DOOR: BlockType = E_BLOCK_JUNGLE_DOOR; break;
case E_ITEM_DARK_OAK_DOOR: BlockType = E_BLOCK_DARK_OAK_DOOR; break;
case E_ITEM_ACACIA_DOOR: BlockType = E_BLOCK_ACACIA_DOOR; break;
default:
{
ASSERT(!"Unhandled door type");
@ -46,14 +63,47 @@ public:
}
}
cChunkInterface ChunkInterface(a_World->GetChunkMap());
bool Meta = BlockHandler(a_BlockType)->GetPlacementBlockTypeMeta(
ChunkInterface, a_Player,
a_BlockX, a_BlockY, a_BlockZ, a_BlockFace,
a_CursorX, a_CursorY, a_CursorZ,
a_BlockType, a_BlockMeta
);
return Meta;
// Check the two blocks that will get replaced by the door:
BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ);
BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ);
if (
!cBlockDoorHandler::CanReplaceBlock(LowerBlockType) ||
!cBlockDoorHandler::CanReplaceBlock(UpperBlockType))
{
return false;
}
// Get the coords of the neighboring blocks:
NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw());
Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta);
Vector3i LeftNeighborPos = RelDirToOutside;
LeftNeighborPos.TurnCCW();
LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ);
Vector3i RightNeighborPos = RelDirToOutside;
RightNeighborPos.TurnCW();
RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ);
// Decide whether the hinge is on the left (default) or on the right:
NIBBLETYPE UpperBlockMeta = 0x08;
if (
cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block
cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid
)
{
UpperBlockMeta = 0x09; // Upper block | hinge on right
}
// Set the blocks:
sSetBlockVector blks;
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta);
blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta);
return a_Player.PlaceBlocks(blks);
}
virtual bool IsPlaceable(void) override
{
return true;
}
} ;

View File

@ -55,25 +55,16 @@ public:
return false;
}
// Check plugins
if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta))
// Place the cocoa pod:
if (a_Player->PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta))
{
a_World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player);
a_Player->GetInventory().SendEquippedSlot();
return false;
a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f);
if (a_Player->IsGameModeSurvival())
{
a_Player->GetInventory().RemoveOneEquippedItem();
}
return true;
}
// Set block and broadcast place sound
a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta);
a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f);
// Remove one cocoa pod from the inventory
if (!a_Player->IsGameModeCreative())
{
a_Player->GetInventory().RemoveOneEquippedItem();
}
cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta);
return true;
}
return false;
}

View File

@ -6,39 +6,43 @@
#include "../Entities/Player.h"
#include "../FastRandom.h"
#include "../BlockInServerPluginInterface.h"
#include "../Chunk.h"
// Handlers:
#include "ItemArmor.h"
#include "ItemBed.h"
#include "ItemBigFlower.h"
#include "ItemBoat.h"
#include "ItemBow.h"
#include "ItemBrewingStand.h"
#include "ItemBucket.h"
#include "ItemCake.h"
#include "ItemCauldron.h"
#include "ItemChest.h"
#include "ItemCloth.h"
#include "ItemComparator.h"
#include "ItemDoor.h"
#include "ItemMilk.h"
#include "ItemDye.h"
#include "ItemEmptyMap.h"
#include "ItemFishingRod.h"
#include "ItemFlowerPot.h"
#include "ItemFood.h"
#include "ItemGoldenApple.h"
#include "ItemItemFrame.h"
#include "ItemHoe.h"
#include "ItemItemFrame.h"
#include "ItemLeaves.h"
#include "ItemLighter.h"
#include "ItemLilypad.h"
#include "ItemMap.h"
#include "ItemMilk.h"
#include "ItemMinecart.h"
#include "ItemMobHead.h"
#include "ItemMushroomSoup.h"
#include "ItemNetherWart.h"
#include "ItemPainting.h"
#include "ItemPickaxe.h"
#include "ItemPotion.h"
#include "ItemThrowable.h"
#include "ItemPumpkin.h"
#include "ItemRedstoneDust.h"
#include "ItemRedstoneRepeater.h"
#include "ItemSapling.h"
@ -46,11 +50,12 @@
#include "ItemShears.h"
#include "ItemShovel.h"
#include "ItemSign.h"
#include "ItemMobHead.h"
#include "ItemSlab.h"
#include "ItemSpawnEgg.h"
#include "ItemString.h"
#include "ItemSugarcane.h"
#include "ItemSword.h"
#include "ItemThrowable.h"
#include "../Blocks/BlockHandler.h"
@ -94,52 +99,58 @@ cItemHandler * cItemHandler::GetItemHandler(int a_ItemType)
cItemHandler *cItemHandler::CreateItemHandler(int a_ItemType)
cItemHandler * cItemHandler::CreateItemHandler(int a_ItemType)
{
switch (a_ItemType)
{
default: return new cItemHandler(a_ItemType);
// Single item per handler, alphabetically sorted:
case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType);
case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType);
case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType);
case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType);
case E_ITEM_BED: return new cItemBedHandler(a_ItemType);
case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType);
case E_BLOCK_CHEST: return new cItemChestHandler(a_ItemType);
case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType);
case E_BLOCK_HEAD: return new cItemMobHeadHandler(a_ItemType);
case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType);
case E_BLOCK_PUMPKIN: return new cItemPumpkinHandler;
case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType);
case E_BLOCK_STONE_SLAB: return new cItemSlabHandler(E_BLOCK_STONE_SLAB, E_BLOCK_DOUBLE_STONE_SLAB);
case E_BLOCK_TRAPPED_CHEST: return new cItemChestHandler(a_ItemType);
case E_BLOCK_WOODEN_SLAB: return new cItemSlabHandler(E_BLOCK_WOODEN_SLAB, E_BLOCK_DOUBLE_WOODEN_SLAB);
case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType);
case E_ITEM_BED: return new cItemBedHandler(a_ItemType);
case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType);
case E_ITEM_BOTTLE_O_ENCHANTING: return new cItemBottleOEnchantingHandler();
case E_ITEM_BOW: return new cItemBowHandler();
case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType);
case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType);
case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType);
case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType);
case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType);
case E_ITEM_EGG: return new cItemEggHandler();
case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler();
case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler();
case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType);
case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler();
case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType);
case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType);
case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType);
case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler();
case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType);
case E_ITEM_MAP: return new cItemMapHandler();
case E_ITEM_MILK: return new cItemMilkHandler();
case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType);
case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType);
case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType);
case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType);
case E_ITEM_POTIONS: return new cItemPotionHandler();
case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType);
case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType);
case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType);
case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType);
case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType);
case E_ITEM_SNOWBALL: return new cItemSnowballHandler();
case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType);
case E_ITEM_STRING: return new cItemStringHandler(a_ItemType);
case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType);
case E_ITEM_BOW: return new cItemBowHandler();
case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType);
case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType);
case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType);
case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType);
case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType);
case E_ITEM_EGG: return new cItemEggHandler();
case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler();
case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler();
case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType);
case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler();
case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType);
case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType);
case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType);
case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler();
case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType);
case E_ITEM_MAP: return new cItemMapHandler();
case E_ITEM_MILK: return new cItemMilkHandler();
case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType);
case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType);
case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType);
case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType);
case E_ITEM_POTIONS: return new cItemPotionHandler();
case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType);
case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType);
case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType);
case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType);
case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType);
case E_ITEM_SNOWBALL: return new cItemSnowballHandler();
case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType);
case E_ITEM_STRING: return new cItemStringHandler(a_ItemType);
case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType);
case E_ITEM_WOODEN_HOE:
case E_ITEM_STONE_HOE:
@ -297,6 +308,108 @@ cItemHandler::cItemHandler(int a_ItemType)
bool cItemHandler::OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
)
{
if (a_BlockFace < 0)
{
// Clicked in air
return false;
}
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The clicked block is outside the world, ignore this call altogether (#128)
return false;
}
BLOCKTYPE ClickedBlock;
NIBBLETYPE ClickedBlockMeta;
a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);
// Check if the block ignores build collision (water, grass etc.):
if (
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() ||
BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta)
)
{
cChunkInterface ChunkInterface(a_World.GetChunkMap());
BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ);
}
else
{
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
{
// The block is being placed outside the world, ignore this packet altogether (#128)
return false;
}
NIBBLETYPE PlaceMeta;
BLOCKTYPE PlaceBlock;
a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta);
// Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed.
// No need to do combinability (dblslab) checks, client will do that here.
if (
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() &&
!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta)
)
{
// Tried to place a block *into* another?
// Happens when you place a block aiming at side of block with a torch on it or stem beside it
return false;
}
}
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
if (!GetPlacementBlockTypeMeta(&a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
{
// Handler refused the placement, send that information back to the client:
a_World.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, &a_Player);
a_Player.GetInventory().SendEquippedSlot();
return false;
}
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta))
{
// The placement failed, the block has already been re-sent, re-send inventory:
a_Player.GetInventory().SendEquippedSlot();
return false;
}
AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType);
float Volume = 1.0f, Pitch = 0.8f;
if (PlaceSound == "dig.metal")
{
Pitch = 1.2f;
PlaceSound = "dig.stone";
}
else if (PlaceSound == "random.anvil_land")
{
Volume = 0.65f;
}
a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch);
// Remove the "placed" item:
if (a_Player.IsGameModeSurvival())
{
a_Player.GetInventory().RemoveOneEquippedItem();
}
return true;
}
bool cItemHandler::OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir)
{
UNUSED(a_World);

View File

@ -31,10 +31,35 @@ public:
/** Force virtual destructor */
virtual ~cItemHandler() {}
/** Called when the player tries to place the item (right mouse button, IsPlaceable() == true).
The default handler uses GetPlacementBlockTypeMeta and places the returned block.
Override this function for advanced behavior such as placing multiple blocks.
If the block placement is refused inside this call, it will automatically revert the client-side changes.
Returns true if the placement succeeded, false if the placement was aborted for any reason. */
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
);
/** Called when the player right-clicks with this item and IsPlaceable() == true, and OnPlace() is not overridden.
This function should provide the block type and meta for the placed block, or refuse the placement.
Returns true to allow placement, false to refuse. */
virtual bool GetPlacementBlockTypeMeta(
cWorld * a_World, cPlayer * a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ,
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
);
/** Called when the player tries to use the item (right mouse button). Return false to make the item unusable. DEFAULT: False */
virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir);
/** Called when the client sends the SHOOT status in the lclk packet */
virtual void OnItemShoot(cPlayer *, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace)
{
@ -106,18 +131,8 @@ public:
/** Can the anvil repair this item, when a_Item is the second input? */
virtual bool CanRepairWithRawMaterial(short a_ItemType);
/** Called before a block is placed into a world.
The handler should return true to allow placement, false to refuse.
Also, the handler should set a_BlockType and a_BlockMeta to correct values for the newly placed block.
*/
virtual bool GetPlacementBlockTypeMeta(
cWorld * a_World, cPlayer * a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ,
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
);
/** Returns whether this tool/item can harvest a specific block (e.g. wooden pickaxe can harvest stone, but wood can't) DEFAULT: False */
/** Returns whether this tool / item can harvest a specific block (e.g. iron pickaxe can harvest diamond ore, but wooden one can't).
Defaults to false unless overridden. */
virtual bool CanHarvestBlock(BLOCKTYPE a_BlockType);
static cItemHandler * GetItemHandler(int a_ItemType);

View File

@ -3,6 +3,7 @@
#include "ItemHandler.h"
#include "../World.h"
#include "../BlockEntities/MobHeadEntity.h"
@ -18,6 +19,266 @@ public:
}
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// Cannot place a head at "no face" and from the bottom:
if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockFace == BLOCK_FACE_BOTTOM))
{
return true;
}
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// If the placed head is a wither, try to spawn the wither first:
if (a_EquippedItem.m_ItemDamage == E_META_HEAD_WITHER)
{
if (TrySpawnWitherAround(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ))
{
return true;
}
// Wither not created, proceed with regular head placement
}
return PlaceRegularHead(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
}
/** Places a regular head block with no mob spawning checking. */
bool PlaceRegularHead(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace
)
{
// Place the block:
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_HEAD, static_cast<NIBBLETYPE>(a_EquippedItem.m_ItemType)))
{
return false;
}
// Use a callback to set the properties of the mob head block entity:
class cCallback : public cBlockEntityCallback
{
cPlayer & m_Player;
eMobHeadType m_HeadType;
NIBBLETYPE m_BlockMeta;
virtual bool Item(cBlockEntity * a_BlockEntity)
{
if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD)
{
return false;
}
cMobHeadEntity * MobHeadEntity = static_cast<cMobHeadEntity *>(a_BlockEntity);
int Rotation = 0;
if (m_BlockMeta == 1)
{
Rotation = FloorC(m_Player.GetYaw() * 16.0f / 360.0f + 0.5f) & 0x0f;
}
MobHeadEntity->SetType(m_HeadType);
MobHeadEntity->SetRotation(static_cast<eMobHeadRotation>(Rotation));
MobHeadEntity->GetWorld()->BroadcastBlockEntity(MobHeadEntity->GetPosX(), MobHeadEntity->GetPosY(), MobHeadEntity->GetPosZ());
return false;
}
public:
cCallback (cPlayer & a_CBPlayer, eMobHeadType a_HeadType, NIBBLETYPE a_BlockMeta) :
m_Player(a_CBPlayer),
m_HeadType(a_HeadType),
m_BlockMeta(a_BlockMeta)
{}
};
cCallback Callback(a_Player, static_cast<eMobHeadType>(a_EquippedItem.m_ItemType), static_cast<NIBBLETYPE>(a_BlockFace));
a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback);
return true;
}
/** Spawns a wither if the wither skull placed at the specified coords completes wither's spawning formula.
Returns true if the wither was created. */
bool TrySpawnWitherAround(
cWorld & a_World, cPlayer & a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ
)
{
// No wither can be created at Y < 2 - not enough space for the formula:
if (a_BlockY < 2)
{
return false;
}
// Check for all relevant wither locations:
static const Vector3i RelCoords[] =
{
{ 0, 0, 0},
{ 1, 0, 0},
{-1, 0, 0},
{ 0, 0, 1},
{ 0, 0, -1},
};
for (size_t i = 0; i < ARRAYCOUNT(RelCoords); ++i)
{
if (TrySpawnWitherAt(
a_World, a_Player,
a_BlockX, a_BlockY, a_BlockZ,
RelCoords[i].x, RelCoords[i].z
))
{
return true;
}
} // for i - Coords[]
return false;
}
/** Tries to spawn a wither at the specified offset from the placed head block.
PlacedHead coords are used to override the block query - at those coords the block is not queried from the world,
but assumed to be a head instead.
Offset is used to shift the image around the X and Z axis.
Returns true iff the wither was created successfully. */
bool TrySpawnWitherAt(
cWorld & a_World, cPlayer & a_Player,
int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ,
int a_OffsetX, int a_OffsetZ
)
{
// Image for the wither at the X axis:
static const sSetBlock ImageWitherX[] =
{
{-1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{ 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{ 1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{-1, -1, 0, E_BLOCK_SOULSAND, 0},
{ 0, -1, 0, E_BLOCK_SOULSAND, 0},
{ 1, -1, 0, E_BLOCK_SOULSAND, 0},
{-1, -2, 0, E_BLOCK_AIR, 0},
{ 0, -2, 0, E_BLOCK_SOULSAND, 0},
{ 1, -2, 0, E_BLOCK_AIR, 0},
};
// Image for the wither at the Z axis:
static const sSetBlock ImageWitherZ[] =
{
{ 0, 0, -1, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{ 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{ 0, 0, 1, E_BLOCK_HEAD, E_META_HEAD_WITHER},
{ 0, -1, -1, E_BLOCK_SOULSAND, 0},
{ 0, -1, 0, E_BLOCK_SOULSAND, 0},
{ 0, -1, 1, E_BLOCK_SOULSAND, 0},
{ 0, -2, -1, E_BLOCK_AIR, 0},
{ 0, -2, 0, E_BLOCK_SOULSAND, 0},
{ 0, -2, 1, E_BLOCK_AIR, 0},
};
// Try to spawn the wither from each image:
return (
TrySpawnWitherFromImage(
a_World, a_Player, ImageWitherX, ARRAYCOUNT(ImageWitherX),
a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ,
a_OffsetX, a_OffsetZ
) ||
TrySpawnWitherFromImage(
a_World, a_Player, ImageWitherZ, ARRAYCOUNT(ImageWitherZ),
a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ,
a_OffsetX, a_OffsetZ
)
);
}
/** Tries to spawn a wither from the specified image at the specified offset from the placed head block.
PlacedHead coords are used to override the block query - at those coords the block is not queried from the world,
but assumed to be a head instead.
Offset is used to shift the image around the X and Z axis.
Returns true iff the wither was created successfully. */
bool TrySpawnWitherFromImage(
cWorld & a_World, cPlayer & a_Player, const sSetBlock * a_Image, size_t a_ImageCount,
int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ,
int a_OffsetX, int a_OffsetZ
)
{
// Check each block individually; simultaneously build the SetBlockVector for clearing the blocks:
sSetBlockVector AirBlocks;
AirBlocks.reserve(a_ImageCount);
for (size_t i = 0; i < a_ImageCount; i++)
{
// Get the absolute coords of the image:
int BlockX = a_PlacedHeadX + a_OffsetX + a_Image[i].m_RelX;
int BlockY = a_PlacedHeadY + a_Image[i].m_RelY;
int BlockZ = a_PlacedHeadZ + a_OffsetZ + a_Image[i].m_RelZ;
// If the query is for the placed head, short-circuit-evaluate it:
if ((BlockX == a_PlacedHeadX) && (BlockY == a_PlacedHeadY) && (BlockZ == a_PlacedHeadZ))
{
if ((a_Image[i].m_BlockType != E_BLOCK_HEAD) || (a_Image[i].m_BlockMeta != E_META_HEAD_WITHER))
{
return false; // Didn't match
}
continue; // Matched, continue checking the rest of the image
}
// Query the world block:
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
if (!a_World.GetBlockTypeMeta(BlockX, BlockY, BlockZ, BlockType, BlockMeta))
{
// Cannot query block, assume unloaded chunk, fail to spawn the wither
return false;
}
// Compare the world block:
if ((BlockType != a_Image[i].m_BlockType) || (BlockMeta != a_Image[i].m_BlockMeta))
{
return false; // Didn't match
}
// Matched, continue checking
} // for i - a_Image
// All image blocks matched, try place the wither:
if (!a_Player.PlaceBlocks(AirBlocks))
{
return false;
}
// Spawn the wither:
int BlockX = a_PlacedHeadX + a_OffsetX;
int BlockZ = a_PlacedHeadZ + a_OffsetZ;
a_World.SpawnMob(static_cast<double>(BlockX) + 0.5, a_PlacedHeadY - 2, static_cast<double>(BlockZ) + 0.5, mtWither);
AwardSpawnWitherAchievement(a_World, BlockX, a_PlacedHeadY - 2, BlockZ);
return true;
}
/** Awards the achievement to all players close to the specified point. */
void AwardSpawnWitherAchievement(cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ)
{
class cPlayerCallback : public cPlayerListCallback
{
Vector3f m_Pos;
virtual bool Item(cPlayer * a_Player)
{
// If player is close, award achievement:
double Dist = (a_Player->GetPosition() - m_Pos).Length();
if (Dist < 50.0)
{
a_Player->AwardAchievement(achSpawnWither);
}
return false;
}
public:
cPlayerCallback(const Vector3f & a_Pos) : m_Pos(a_Pos) {}
} PlayerCallback(Vector3f(static_cast<float>(a_BlockX), static_cast<float>(a_BlockY), static_cast<float>(a_BlockZ)));
a_World.ForEachPlayer(PlayerCallback);
}
virtual bool IsPlaceable(void) override
{
return true;

156
src/Items/ItemPumpkin.h Normal file
View File

@ -0,0 +1,156 @@
// ItemPumpkin.h
// Declares the cItemPumpkinHandler class representing the pumpkin block in its item form
#pragma once
#include "ItemHandler.h"
class cItemPumpkinHandler:
public cItemHandler
{
typedef cItemHandler super;
public:
cItemPumpkinHandler(void):
super(E_BLOCK_PUMPKIN)
{
}
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// First try spawning a snow golem or an iron golem:
int PlacedBlockX = a_BlockX;
int PlacedBlockY = a_BlockY;
int PlacedBlockZ = a_BlockZ;
AddFaceDirection(PlacedBlockX, PlacedBlockY, PlacedBlockZ, a_BlockFace);
if (TrySpawnGolem(a_World, a_Player, PlacedBlockX, PlacedBlockY, PlacedBlockZ))
{
// The client thinks that they placed the pumpkin, let them know it's been replaced:
a_Player.SendBlocksAround(PlacedBlockX, PlacedBlockY, PlacedBlockZ);
return true;
}
// No golem at these coords, place the block normally:
return super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
}
/** Spawns a snow / iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin.
Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. */
bool TrySpawnGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ)
{
// A golem can't form with a pumpkin below level 2 or above level 255
if ((a_BlockY < 2) || (a_BlockY >= cChunkDef::Height))
{
return false;
}
// Decide which golem to try spawning based on the block below the placed pumpkin:
switch (a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))
{
case E_BLOCK_SNOW_BLOCK: return TrySpawnSnowGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ);
case E_BLOCK_IRON_BLOCK: return TrySpawnIronGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ);
default:
{
// No golem here
return false;
}
}
}
/** Spawns a snow golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin.
Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched.
Assumes that the block below the specified block has already been checked and is a snow block. */
bool TrySpawnSnowGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ)
{
// Need one more snow block 2 blocks below the pumpkin:
if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_SNOW_BLOCK)
{
return false;
}
// Try to place air blocks where the original recipe blocks were:
sSetBlockVector AirBlocks;
AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head
AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso
AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs
if (!a_Player.PlaceBlocks(AirBlocks))
{
return false;
}
// Spawn the golem:
a_World.SpawnMob(static_cast<double>(a_BlockX) + 0.5, a_BlockY - 2, static_cast<double>(a_BlockZ) + 0.5, mtSnowGolem);
return true;
}
/** Spawns an iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin.
Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched.
Assumes that the block below the specified block has already been checked and is an iron block. */
bool TrySpawnIronGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ)
{
// Need one more iron block 2 blocks below the pumpkin:
if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_IRON_BLOCK)
{
return false;
}
// Check the two arm directions (X, Z) using a loop over two sets of offset vectors:
static const Vector3i ArmOffsets[] =
{
{1, 0, 0},
{0, 0, 1},
};
for (size_t i = 0; i < ARRAYCOUNT(ArmOffsets); i++)
{
// If the arm blocks don't match, bail out of this loop repetition:
if (
(a_World.GetBlock(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK) ||
(a_World.GetBlock(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK)
)
{
continue;
}
// Try to place air blocks where the original recipe blocks were:
sSetBlockVector AirBlocks;
AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head
AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso
AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs
AirBlocks.emplace_back(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm
AirBlocks.emplace_back(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm
if (!a_Player.PlaceBlocks(AirBlocks))
{
return false;
}
// Spawn the golem:
a_World.SpawnMob(static_cast<double>(a_BlockX) + 0.5, a_BlockY - 2, static_cast<double>(a_BlockZ) + 0.5, mtIronGolem);
return true;
} // for i - ArmOffsets[]
// Neither arm offset matched, this thing is not a complete golem
return false;
}
};

View File

@ -27,7 +27,20 @@ public:
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
) override
{
if (!cBlockInfo::FullyOccupiesVoxel(a_World->GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) // Some solid blocks, such as cocoa beans, are not suitable for dust
// Check if coords are out of range:
if ((a_BlockY <= 0) || (a_BlockY >= cChunkDef::Height))
{
return false;
}
// Check the block below, if it supports dust on top of it:
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
if (!a_World->GetBlockTypeMeta(a_BlockX, a_BlockY - 1, a_BlockZ, BlockType, BlockMeta))
{
return false;
}
if (!IsBlockTypeUnderSuitable(BlockType, BlockMeta))
{
return false;
}
@ -36,6 +49,28 @@ public:
a_BlockMeta = 0;
return true;
}
/** Returns true if the specified block type / meta is suitable to have redstone dust on top of it. */
static bool IsBlockTypeUnderSuitable(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
if (cBlockInfo::FullyOccupiesVoxel(a_BlockType))
{
return true;
}
switch (a_BlockType)
{
case E_BLOCK_NEW_STONE_SLAB:
case E_BLOCK_WOODEN_SLAB:
case E_BLOCK_STONE_SLAB:
{
// Slabs can support redstone if they're upside down:
return ((a_BlockMeta & 0x08) != 0);
}
}
return false;
}
} ;

View File

@ -13,13 +13,33 @@
class cItemSignHandler :
public cItemHandler
{
typedef cItemHandler super;
public:
cItemSignHandler(int a_ItemType) :
cItemHandler(a_ItemType)
super(a_ItemType)
{
}
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
)
{
// If the regular placement doesn't work, do no further processing:
if (!super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
{
return false;
}
// After successfully placing the sign, open the sign editor for the player:
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
a_Player.GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ);
return true;
}
virtual bool IsPlaceable(void) override
{
return true;

93
src/Items/ItemSlab.h Normal file
View File

@ -0,0 +1,93 @@
// ItemSlab.h
// Declares the cItemSlabHandler responsible for handling slabs, when in their item form.
#pragma once
#include "ItemHandler.h"
#include "../Blocks/BlockSlab.h"
class cItemSlabHandler:
public cItemHandler
{
typedef cItemHandler super;
public:
/** Creates a new handler for the specified slab item type.
Sets the handler to use the specified doubleslab block type for combining self into doubleslabs. */
cItemSlabHandler(int a_ItemType, BLOCKTYPE a_DoubleSlabBlockType):
super(a_ItemType),
m_DoubleSlabBlockType(a_DoubleSlabBlockType)
{
}
// cItemHandler overrides:
virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ
) override
{
// Special slab handling - placing a slab onto another slab produces a dblslab instead:
BLOCKTYPE ClickedBlockType;
NIBBLETYPE ClickedBlockMeta;
a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlockType, ClickedBlockMeta);
if (
(ClickedBlockType == m_ItemType) && // Placing the same slab material
(ClickedBlockMeta == a_EquippedItem.m_ItemDamage) // Placing the same slab sub-kind (and existing slab is single)
)
{
// If clicking the top side of a bottom-half slab, combine into a doubleslab:
if (
(a_BlockFace == BLOCK_FACE_TOP) &&
((ClickedBlockMeta & 0x08) == 0)
)
{
return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07);
}
// If clicking the bottom side of a top-half slab, combine into a doubleslab:
if (
(a_BlockFace == BLOCK_FACE_BOTTOM) &&
((ClickedBlockMeta & 0x08) != 0)
)
{
return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07);
}
}
// The slabs didn't combine, use the default handler to place the slab:
bool res = super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
/*
The client has a bug when a slab replaces snow and there's a slab above it.
The client then combines the slab above, rather than replacing the snow.
We send the block above the currently placed block back to the client to fix the bug.
Ref.: http://forum.mc-server.org/showthread.php?tid=434&pid=17388#pid17388
*/
if ((a_BlockFace == BLOCK_FACE_TOP) && (a_BlockY < cChunkDef::Height - 1))
{
a_Player.SendBlocksAround(a_BlockX, a_BlockY + 1, a_BlockZ, 1);
}
return res;
}
protected:
/** The block type to use when the slab combines into a doubleslab block. */
BLOCKTYPE m_DoubleSlabBlockType;
};

View File

@ -228,8 +228,8 @@ void cProtocol172::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV
Pkt.WriteInt((int)a_Changes.size() * 4);
for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr)
{
unsigned int Coords = itr->y | (itr->z << 8) | (itr->x << 12);
unsigned int Blocks = itr->BlockMeta | (itr->BlockType << 4);
unsigned int Coords = itr->m_RelY | (itr->m_RelZ << 8) | (itr->m_RelX << 12);
unsigned int Blocks = itr->m_BlockMeta | (itr->m_BlockType << 4);
Pkt.WriteInt((Coords << 16) | Blocks);
} // for itr - a_Changes[]
}

View File

@ -207,9 +207,9 @@ void cProtocol180::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV
Pkt.WriteVarInt((UInt32)a_Changes.size());
for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr)
{
short Coords = (short) (itr->y | (itr->z << 8) | (itr->x << 12));
short Coords = (short) (itr->m_RelY | (itr->m_RelZ << 8) | (itr->m_RelX << 12));
Pkt.WriteShort(Coords);
Pkt.WriteVarInt((itr->BlockType & 0xFFF) << 4 | (itr->BlockMeta & 0xF));
Pkt.WriteVarInt((itr->m_BlockType & 0xFFF) << 4 | (itr->m_BlockMeta & 0xF));
} // for itr - a_Changes[]
}

View File

@ -307,6 +307,22 @@ public:
return (a_X - x) / (a_OtherEnd.x - x);
}
/** Rotates the vector 90 degrees clockwise around the vertical axis.
Note that this is specific to minecraft's axis ordering, which is X+ left, Z+ down. */
inline void TurnCW(void)
{
std::swap(x, z);
x = -x;
}
/** Rotates the vector 90 degrees counterclockwise around the vertical axis.
Note that this is specific to minecraft's axis ordering, which is X+ left, Z+ down. */
inline void TurnCCW(void)
{
std::swap(x, z);
z = -z;
}
/** The max difference between two coords for which the coords are assumed equal. */
static const double EPS;

View File

@ -1463,7 +1463,7 @@ void cWorld::GrowTreeImage(const sSetBlockVector & a_Blocks)
sSetBlockVector b2;
for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr)
{
if (itr->BlockType == E_BLOCK_LOG)
if (itr->m_BlockType == E_BLOCK_LOG)
{
b2.push_back(*itr);
}
@ -1478,7 +1478,7 @@ void cWorld::GrowTreeImage(const sSetBlockVector & a_Blocks)
// Check that at each log's coord there's an block allowed to be overwritten:
for (sSetBlockVector::const_iterator itr = b2.begin(); itr != b2.end(); ++itr)
{
switch (itr->BlockType)
switch (itr->m_BlockType)
{
CASE_TREE_ALLOWED_BLOCKS:
{
@ -1926,6 +1926,15 @@ void cWorld::SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTicks,
void cWorld::SetBlocks(const sSetBlockVector & a_Blocks)
{
m_ChunkMap->SetBlocks(a_Blocks);
}
void cWorld::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType)
{
m_ChunkMap->ReplaceBlocks(a_Blocks, a_FilterBlockType);

View File

@ -491,6 +491,11 @@ public:
// tolua_end
/** Performs the specified single-block set operations simultaneously, as if SetBlock() was called for each item.
Is more efficient than calling SetBlock() multiple times.
If the chunk for any of the blocks is not loaded, the set operation is ignored silently. */
void SetBlocks(const sSetBlockVector & a_Blocks);
/** Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType */
void ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType);