1
0

Refactored block placement workflow.

Multi-blocks can now use the default OnPlayerPlaced() callback in cItemHandler.
This commit is contained in:
Mattes D 2015-06-21 19:49:22 +02:00
parent 4ce8bebfd8
commit 3889b2cac2
7 changed files with 156 additions and 85 deletions

View File

@ -25,10 +25,11 @@ public:
} }
virtual bool OnPlayerPlace( virtual bool GetBlocksToPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ int a_CursorX, int a_CursorY, int a_CursorZ,
sSetBlockVector & a_BlocksToPlace
) override ) override
{ {
// Can only be placed on the floor: // Can only be placed on the floor:
@ -36,12 +37,10 @@ public:
{ {
return false; return false;
} }
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// The "foot" block: // The "foot" block:
sSetBlockVector blks;
NIBBLETYPE BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player.GetYaw()); NIBBLETYPE BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player.GetYaw());
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta); a_BlocksToPlace.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta);
// Check if there is empty space for the "head" block: // Check if there is empty space for the "head" block:
// (Vanilla only allows beds to be placed into air) // (Vanilla only allows beds to be placed into air)
@ -50,10 +49,8 @@ public:
{ {
return false; return false;
} }
blks.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08); a_BlocksToPlace.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08);
return true;
// Place both bed blocks:
return a_Player.PlaceBlocks(blks);
} }
} ; } ;

View File

@ -27,10 +27,11 @@ public:
} }
virtual bool OnPlayerPlace( virtual bool GetBlocksToPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ int a_CursorX, int a_CursorY, int a_CursorZ,
sSetBlockVector & a_BlocksToSet
) override ) override
{ {
// Can only be placed on the floor: // Can only be placed on the floor:
@ -38,16 +39,14 @@ public:
{ {
return false; return false;
} }
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// Place both blocks atomically: AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
sSetBlockVector blks; a_BlocksToSet.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BIG_FLOWER, a_EquippedItem.m_ItemDamage & 0x07);
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BIG_FLOWER, a_EquippedItem.m_ItemDamage & 0x07);
if (a_BlockY < cChunkDef::Height - 1) 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); a_BlocksToSet.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_BIG_FLOWER, (a_EquippedItem.m_ItemDamage & 0x07) | 0x08);
} }
return a_Player.PlaceBlocks(blks); return true;
} }
}; };

View File

@ -27,6 +27,8 @@ public:
} }
/** We need an OnPlayerPlace override because we're processing neighbor chests and changing their metas,
the parent class cannot do that. */
virtual bool OnPlayerPlace( virtual bool OnPlayerPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
@ -118,34 +120,32 @@ public:
} // for j } // for j
} // for i } // for i
// If there's no chest neighbor, place the single block chest and bail out: // Get the meta of the placed chest; take existing neighbors into account:
BLOCKTYPE ChestBlockType = static_cast<BLOCKTYPE>(m_ItemType); BLOCKTYPE ChestBlockType = static_cast<BLOCKTYPE>(m_ItemType);
if (NeighborIdx < 0) NIBBLETYPE Meta;
auto yaw = a_Player.GetYaw();
switch (NeighborIdx)
{ {
NIBBLETYPE Meta = cBlockChestHandler::PlayerYawToMetaData(a_Player.GetYaw()); case 0:
return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta); case 2:
}
// 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; // The neighbor is in the X axis, form a X-axis-aligned dblchest:
Meta = ((yaw >= -90) && (yaw < 90)) ? E_META_CHEST_FACING_ZM : E_META_CHEST_FACING_ZP;
break;
} }
case 1:
// Adjust the existing chest: case 3:
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:
} Meta = (yaw < 0) ? E_META_CHEST_FACING_XM : E_META_CHEST_FACING_XP;
break;
// 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; default:
{
Meta = cBlockChestHandler::PlayerYawToMetaData(yaw);
break;
}
} // switch (NeighborIdx)
// Place the new chest: // Place the new chest:
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta)) if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta))
@ -153,8 +153,31 @@ public:
return false; return false;
} }
// Adjust the existing chest: // Adjust the existing chest, if any:
a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta); if (NeighborIdx > 0)
{
a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta);
}
// Play the placement sound:
AString PlaceSound = cBlockInfo::GetPlaceSound(ChestBlockType);
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; return true;
} }

View File

@ -20,10 +20,11 @@ public:
} }
virtual bool OnPlayerPlace( virtual bool GetBlocksToPlace(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ int a_CursorX, int a_CursorY, int a_CursorZ,
sSetBlockVector & a_BlocksToSet
) override ) override
{ {
// Vanilla only allows door placement while clicking on the top face of the block below the door: // Vanilla only allows door placement while clicking on the top face of the block below the door:
@ -107,10 +108,9 @@ public:
} }
// Set the blocks: // Set the blocks:
sSetBlockVector blks; a_BlocksToSet.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta);
blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta); a_BlocksToSet.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta);
blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta); return true;
return a_Player.PlaceBlocks(blks);
} }

View File

@ -367,37 +367,51 @@ bool cItemHandler::OnPlayerPlace(
return false; return false;
} }
} }
BLOCKTYPE BlockType; // Get all the blocks to place:
NIBBLETYPE BlockMeta; sSetBlockVector blocks;
if (!GetPlacementBlockTypeMeta(&a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) if (!GetBlocksToPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, blocks))
{ {
// Handler refused the placement, send that information back to the client: // Handler refused the placement, send that information back to the client:
for (const auto & blk: blocks)
{
a_World.SendBlockTo(blk.GetX(), blk.GetY(), blk.GetZ(), &a_Player);
}
a_World.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, &a_Player); a_World.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, &a_Player);
a_Player.GetInventory().SendEquippedSlot(); a_Player.GetInventory().SendEquippedSlot();
return false; return false;
} }
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta)) // Try to place the blocks:
if (!a_Player.PlaceBlocks(blocks))
{ {
// The placement failed, the block has already been re-sent, re-send inventory: // The placement failed, the blocks have already been re-sent, re-send inventory:
a_Player.GetInventory().SendEquippedSlot(); a_Player.GetInventory().SendEquippedSlot();
return false; return false;
} }
AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType); // Play the placement sound for the main block:
float Volume = 1.0f, Pitch = 0.8f; for (const auto & blk: blocks)
if (PlaceSound == "dig.metal")
{ {
Pitch = 1.2f; // Find the main block by comparing the coords:
PlaceSound = "dig.stone"; if ((blk.GetX() != a_BlockX) || (blk.GetY() != a_BlockY) || (blk.GetZ() != a_BlockZ))
} {
else if (PlaceSound == "random.anvil_land") continue;
{ }
Volume = 0.65f; AString PlaceSound = cBlockInfo::GetPlaceSound(blk.m_BlockType);
} float Volume = 1.0f, Pitch = 0.8f;
if (PlaceSound == "dig.metal")
a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); {
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);
break;
} // for blk - blocks[]
// Remove the "placed" item: // Remove the "placed" item:
if (a_Player.IsGameModeSurvival()) if (a_Player.IsGameModeSurvival())
@ -411,6 +425,27 @@ bool cItemHandler::OnPlayerPlace(
bool cItemHandler::GetBlocksToPlace(
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,
sSetBlockVector & a_BlocksToSet
)
{
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))
{
return false;
}
a_BlocksToSet.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
return true;
}
bool cItemHandler::OnItemUse( bool cItemHandler::OnItemUse(
cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace

View File

@ -35,8 +35,9 @@ public:
/** Called when the player tries to place the item (right mouse button, IsPlaceable() == true). /** Called when the player tries to place the item (right mouse button, IsPlaceable() == true).
The default handler uses GetPlacementBlockTypeMeta and places the returned block. The block coords are for the block that has been clicked.
Override this function for advanced behavior such as placing multiple blocks. The default handler uses GetBlocksToPlace() and places the returned blocks.
Override if the item needs advanced processing, such as spawning a mob based on the blocks being placed.
If the block placement is refused inside this call, it will automatically revert the client-side changes. 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. */ Returns true if the placement succeeded, false if the placement was aborted for any reason. */
virtual bool OnPlayerPlace( virtual bool OnPlayerPlace(
@ -46,7 +47,20 @@ public:
); );
/** Called when the player right-clicks with this item and IsPlaceable() == true, and OnPlace() is not overridden. /** Called from OnPlayerPlace() to determine the blocks that the current placement operation should set.
The block coords are where the new (main) block should be placed.
The default handler uses GetPlacementBlockTypeMeta() and provides that as the single block at the specified coords.
Returns true if the placement succeeded, false if the placement was aborted for any reason.
If aborted, the server then sends all original blocks in the coords provided in a_BlocksToSet to the client. */
virtual bool GetBlocksToPlace(
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,
sSetBlockVector & a_BlocksToSet
);
/** Called when the player right-clicks with this item and IsPlaceable() == true, and OnPlayerPlace() is not overridden.
This function should provide the block type and meta for the placed block, or refuse the placement. 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. */ Returns true to allow placement, false to refuse. */
virtual bool GetPlacementBlockTypeMeta( virtual bool GetPlacementBlockTypeMeta(

View File

@ -12,9 +12,11 @@
class cItemMobHeadHandler : class cItemMobHeadHandler :
public cItemHandler public cItemHandler
{ {
typedef cItemHandler super;
public: public:
cItemMobHeadHandler(int a_ItemType) : cItemMobHeadHandler(int a_ItemType) :
cItemHandler(a_ItemType) super(a_ItemType)
{ {
} }
@ -30,34 +32,36 @@ public:
{ {
return true; return true;
} }
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); auto placedX = a_BlockX, placedY = a_BlockY, placedZ = a_BlockZ;
AddFaceDirection(placedY, placedY, placedZ, a_BlockFace);
// If the placed head is a wither, try to spawn the wither first: // If the placed head is a wither, try to spawn the wither first:
if (a_EquippedItem.m_ItemDamage == E_META_HEAD_WITHER) if (a_EquippedItem.m_ItemDamage == E_META_HEAD_WITHER)
{ {
if (TrySpawnWitherAround(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ)) if (TrySpawnWitherAround(a_World, a_Player, placedX, placedY, placedZ))
{ {
return true; return true;
} }
// Wither not created, proceed with regular head placement // Wither not created, proceed with regular head placement
} }
return PlaceRegularHead(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); cItem itemCopy(a_EquippedItem); // Make a copy in case this is the player's last head item and OnPlayerPlace removes it
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;
}
RegularHeadPlaced(a_World, a_Player, itemCopy, placedX, placedY, placedZ, a_BlockFace);
return true;
} }
/** Places a regular head block with no mob spawning checking. */ /** Called after placing a regular head block with no mob spawning.
bool PlaceRegularHead( Adjusts the mob head entity based on the equipped item's data. */
void RegularHeadPlaced(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace 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, BlockFaceToBlockMeta(a_BlockFace)))
{
return false;
}
// Use a callback to set the properties of the mob head block entity: // Use a callback to set the properties of the mob head block entity:
class cCallback : public cBlockEntityCallback class cCallback : public cBlockEntityCallback
{ {
@ -71,7 +75,7 @@ public:
{ {
return false; return false;
} }
cMobHeadEntity * MobHeadEntity = static_cast<cMobHeadEntity *>(a_BlockEntity); auto MobHeadEntity = static_cast<cMobHeadEntity *>(a_BlockEntity);
int Rotation = 0; int Rotation = 0;
if (m_BlockMeta == 1) if (m_BlockMeta == 1)
@ -94,7 +98,6 @@ public:
}; };
cCallback Callback(a_Player, static_cast<eMobHeadType>(a_EquippedItem.m_ItemDamage), static_cast<NIBBLETYPE>(a_BlockFace)); cCallback Callback(a_Player, static_cast<eMobHeadType>(a_EquippedItem.m_ItemDamage), static_cast<NIBBLETYPE>(a_BlockFace));
a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback); a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback);
return true;
} }
@ -340,7 +343,7 @@ public:
) override ) override
{ {
a_BlockType = E_BLOCK_HEAD; a_BlockType = E_BLOCK_HEAD;
a_BlockMeta = (NIBBLETYPE)(a_Player->GetEquippedItem().m_ItemDamage & 0x0f); a_BlockMeta = BlockFaceToBlockMeta(a_BlockFace);
return true; return true;
} }
} ; } ;