1
0
Fork 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,
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
{
// Can only be placed on the floor:
@ -36,12 +37,10 @@ public:
{
return false;
}
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
// 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);
a_BlocksToPlace.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta);
// Check if there is empty space for the "head" block:
// (Vanilla only allows beds to be placed into air)
@ -50,10 +49,8 @@ public:
{
return false;
}
blks.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08);
// Place both bed blocks:
return a_Player.PlaceBlocks(blks);
a_BlocksToPlace.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08);
return true;
}
} ;

View File

@ -27,10 +27,11 @@ public:
}
virtual bool OnPlayerPlace(
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
int a_CursorX, int a_CursorY, int a_CursorZ,
sSetBlockVector & a_BlocksToSet
) override
{
// Can only be placed on the floor:
@ -38,16 +39,14 @@ public:
{
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);
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
a_BlocksToSet.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);
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(
cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
@ -118,34 +120,32 @@ public:
} // for j
} // 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);
if (NeighborIdx < 0)
NIBBLETYPE Meta;
auto yaw = a_Player.GetYaw();
switch (NeighborIdx)
{
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))
case 0:
case 2:
{
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;
}
// 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;
case 1:
case 3:
{
// 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;
}
default:
{
Meta = cBlockChestHandler::PlayerYawToMetaData(yaw);
break;
}
} // switch (NeighborIdx)
// Place the new chest:
if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta))
@ -153,8 +153,31 @@ public:
return false;
}
// Adjust the existing chest:
a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta);
// Adjust the existing chest, if any:
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;
}

View File

@ -20,10 +20,11 @@ public:
}
virtual bool OnPlayerPlace(
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
int a_CursorX, int a_CursorY, int a_CursorZ,
sSetBlockVector & a_BlocksToSet
) override
{
// 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:
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);
a_BlocksToSet.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta);
a_BlocksToSet.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta);
return true;
}

View File

@ -367,37 +367,51 @@ bool cItemHandler::OnPlayerPlace(
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))
// Get all the blocks to place:
sSetBlockVector blocks;
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:
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_Player.GetInventory().SendEquippedSlot();
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();
return false;
}
AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType);
float Volume = 1.0f, Pitch = 0.8f;
if (PlaceSound == "dig.metal")
// Play the placement sound for the main block:
for (const auto & blk: blocks)
{
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);
// Find the main block by comparing the coords:
if ((blk.GetX() != a_BlockX) || (blk.GetY() != a_BlockY) || (blk.GetZ() != a_BlockZ))
{
continue;
}
AString PlaceSound = cBlockInfo::GetPlaceSound(blk.m_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);
break;
} // for blk - blocks[]
// Remove the "placed" item:
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(
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

View File

@ -35,8 +35,9 @@ public:
/** 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.
The block coords are for the block that has been clicked.
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.
Returns true if the placement succeeded, false if the placement was aborted for any reason. */
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.
Returns true to allow placement, false to refuse. */
virtual bool GetPlacementBlockTypeMeta(

View File

@ -12,9 +12,11 @@
class cItemMobHeadHandler :
public cItemHandler
{
typedef cItemHandler super;
public:
cItemMobHeadHandler(int a_ItemType) :
cItemHandler(a_ItemType)
super(a_ItemType)
{
}
@ -30,34 +32,36 @@ public:
{
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 (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;
}
// 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. */
bool PlaceRegularHead(
/** Called after placing a regular head block with no mob spawning.
Adjusts the mob head entity based on the equipped item's data. */
void RegularHeadPlaced(
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, BlockFaceToBlockMeta(a_BlockFace)))
{
return false;
}
// Use a callback to set the properties of the mob head block entity:
class cCallback : public cBlockEntityCallback
{
@ -71,7 +75,7 @@ public:
{
return false;
}
cMobHeadEntity * MobHeadEntity = static_cast<cMobHeadEntity *>(a_BlockEntity);
auto MobHeadEntity = static_cast<cMobHeadEntity *>(a_BlockEntity);
int Rotation = 0;
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));
a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback);
return true;
}
@ -340,7 +343,7 @@ public:
) override
{
a_BlockType = E_BLOCK_HEAD;
a_BlockMeta = (NIBBLETYPE)(a_Player->GetEquippedItem().m_ItemDamage & 0x0f);
a_BlockMeta = BlockFaceToBlockMeta(a_BlockFace);
return true;
}
} ;