Added PlaceEvent and MultiPlaceEvent which fires before placing a block.

Before calling "ItemStack.tryPlaceItemInWorld", a recording flag is turned on for
setBlock to capture a blocksnapshot for each block that attempts to be placed.

If 1 block is captured, a "BlockEvent.PlaceEvent" is fired to notify mods.
If 2 or more blocks are captured, a "BlockEvent.PlaceEvent" is fired first with the first block
captured followed by a "BlockEvent.MultiPlaceEvent" with all captured blocks. This extra event
is required for items that have the ability to place 2 or more blocks such as a BlockBed.

If either event is cancelled, the recorded block snapshot(s), item stacksize, and item meta will
revert back to the captured snapshot(s).
If the events are not cancelled, a notification will be sent to clients and block physics will be updated.

What this means for mods is Forge will be able to capture all player block placement automatically and fire
a PlaceEvent and/or MultiPlaceEvent.
If for whatever reason your mod does not use the standard placement methods then you will need to fire the
appropriate placement events in order to notify mods/servers.

This commit also includes a new utility class called BlockSnapshot which is serializable. This new class is used in conjunction with
both PlaceEvent and MultiPlaceEvent in order to record a snapshot of block space before it is altered. This
allows us to restore the block(s) if an event is cancelled. The class also provides the ability to restore a snapshot
to any location using the restoreToLocation method. This should be helpful to many mods that are looking to be able
to capture block data then restore it to back to any location required.
This commit is contained in:
bloodshot committed 2014-10-02 11:33:25 -04:00
1 parent 9ccf454b88
commit 37f91681d9
9 files changed
+719 -72

No files matched your search

@@ -16,15 +16,19 @@ import net.minecraft.init.Items;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.ContainerRepair;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemAxe;
import net.minecraft.item.ItemBucket;
import net.minecraft.item.ItemPickaxe;
import net.minecraft.item.ItemSpade;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S23PacketBlockChange;
import net.minecraft.stats.StatList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityNote;
import net.minecraft.util.AxisAlignedBB;
@@ -37,6 +41,7 @@ import net.minecraft.util.WeightedRandom;
import net.minecraft.world.World;
import net.minecraft.world.WorldSettings.GameType;
import net.minecraftforge.event.AnvilUpdateEvent;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
@@ -430,6 +435,100 @@ public class ForgeHooks
return event;
}
public static boolean onPlaceItemIntoWorld(ItemStack itemstack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ)
{
// handle all placement events here
int meta = itemstack.getItemDamage();
int size = itemstack.stackSize;
NBTTagCompound nbt = null;
if (itemstack.getTagCompound() != null)
{
nbt = (NBTTagCompound)itemstack.getTagCompound().copy();
}
if (!(itemstack.getItem() instanceof ItemBucket)) // if not bucket
{
world.captureBlockSnapshots = true;
}
boolean flag = itemstack.getItem().onItemUse(itemstack, player, world, x, y, z, side, hitX, hitY, hitZ);
world.captureBlockSnapshots = false;
if (flag)
{
// save new item data
int newMeta = itemstack.getItemDamage();
int newSize = itemstack.stackSize;
NBTTagCompound newNBT = null;
if (itemstack.getTagCompound() != null)
{
newNBT = (NBTTagCompound)itemstack.getTagCompound().copy();
}
net.minecraftforge.event.world.BlockEvent.PlaceEvent placeEvent = null;
List<net.minecraftforge.common.util.BlockSnapshot> blockSnapshots = (List<net.minecraftforge.common.util.BlockSnapshot>) world.capturedBlockSnapshots.clone();
world.capturedBlockSnapshots.clear();
// make sure to set pre-placement item data for event
itemstack.setItemDamage(meta);
itemstack.stackSize = size;
if (nbt != null)
{
itemstack.setTagCompound(nbt);
}
if (blockSnapshots.size() > 1)
{
placeEvent = ForgeEventFactory.onPlayerMultiBlockPlace(player, blockSnapshots, net.minecraftforge.common.util.ForgeDirection.getOrientation(side));
}
else if (blockSnapshots.size() == 1)
{
placeEvent = ForgeEventFactory.onPlayerBlockPlace(player, blockSnapshots.get(0), net.minecraftforge.common.util.ForgeDirection.getOrientation(side));
}
if (placeEvent != null && (placeEvent.isCanceled()))
{
flag = false; // cancel placement
// revert back all captured blocks
for (net.minecraftforge.common.util.BlockSnapshot blocksnapshot : blockSnapshots)
{
world.restoringBlockSnapshots = true;
blocksnapshot.restore(true, false);
world.restoringBlockSnapshots = false;
}
}
else
{
// Change the stack to its new content
itemstack.setItemDamage(newMeta);
itemstack.stackSize = newSize;
if (nbt != null)
{
itemstack.setTagCompound(newNBT);
}
for (net.minecraftforge.common.util.BlockSnapshot blocksnapshot : blockSnapshots)
{
int blockX = blocksnapshot.x;
int blockY = blocksnapshot.y;
int blockZ = blocksnapshot.z;
int metadata = world.getBlockMetadata(blockX, blockY, blockZ);
int updateFlag = blocksnapshot.flag;
Block oldBlock = blocksnapshot.replacedBlock;
Block newBlock = world.getBlock(blockX, blockY, blockZ);
if (newBlock != null && !(newBlock.hasTileEntity(metadata))) // Containers get placed automatically
{
newBlock.onBlockAdded(world, blockX, blockY, blockZ);
}
world.markAndNotifyBlock(blockX, blockY, blockZ, null, oldBlock, newBlock, updateFlag);
}
player.addStat(StatList.objectUseStats[Item.getIdFromItem(itemstack.getItem())], 1);
}
}
world.capturedBlockSnapshots.clear();
return flag;
}
public static boolean onAnvilChange(ContainerRepair container, ItemStack left, ItemStack right, IInventory outputSlot, String name, int baseCost)
{
AnvilUpdateEvent e = new AnvilUpdateEvent(left, right, name, baseCost);
@@ -0,0 +1,312 @@
package net.minecraftforge.common.util;
import java.io.Serializable;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.DimensionManager;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
/**
* Represents a captured snapshot of a block which will not change
* automatically.
* <p>
* Unlike Block, which only one object can exist per coordinate, BlockSnapshot
* can exist multiple times for any given Block.
*/
@SuppressWarnings("serial")
public class BlockSnapshot implements Serializable
{
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("forge.debugBlockSnapshot", "false"));
public final int x;
public final int y;
public final int z;
public final int dimId;
public transient Block replacedBlock;
public final int meta;
public int flag;
private final NBTTagCompound nbt;
public transient World world;
public final UniqueIdentifier blockIdentifier;
public BlockSnapshot(World world, int x, int y, int z, Block block, int meta)
{
this.world = world;
this.dimId = world.provider.dimensionId;
this.x = x;
this.y = y;
this.z = z;
this.replacedBlock = block;
this.blockIdentifier = GameRegistry.findUniqueIdentifierFor(block);
this.meta = meta;
this.flag = 3;
TileEntity te = world.getTileEntity(x, y, z);
if (te != null)
{
nbt = new NBTTagCompound();
te.writeToNBT(nbt);
}
else nbt = null;
if (DEBUG)
{
System.out.printf("Created BlockSnapshot - [World: %s ][Location: %d,%d,%d ][Block: %s ][Meta: %d ]", world.getWorldInfo().getWorldName(), x, y, z, block, meta);
}
}
public BlockSnapshot(World world, int x, int y, int z, Block block, int meta, NBTTagCompound nbt)
{
this.world = world;
this.dimId = world.provider.dimensionId;
this.x = x;
this.y = y;
this.z = z;
this.replacedBlock = block;
this.blockIdentifier = GameRegistry.findUniqueIdentifierFor(block);
this.meta = meta;
this.flag = 3;
this.nbt = nbt;
if (DEBUG)
{
System.out.printf("Created BlockSnapshot - [World: %s ][Location: %d,%d,%d ][Block: %s ][Meta: %d ]", world.getWorldInfo().getWorldName(), x, y, z, block, meta);
}
}
public BlockSnapshot(World world, int x, int y, int z, Block block, int meta, int flag)
{
this(world, x, y, z, block, meta);
this.flag = flag;
}
/**
* Raw constructor designed for serialization usages.
*/
public BlockSnapshot(int dimension, int x, int y, int z, String modid, String blockName, int meta, int flag, NBTTagCompound nbt)
{
this.dimId = dimension;
this.x = x;
this.y = y;
this.z = z;
this.meta = meta;
this.flag = flag;
this.blockIdentifier = new UniqueIdentifier(modid + ":" + blockName);
this.nbt = nbt;
}
public static BlockSnapshot getBlockSnapshot(World world, int x, int y, int z)
{
return new BlockSnapshot(world, x, y, z, world.getBlock(x, y, z), world.getBlockMetadata(x, y,z));
}
public static BlockSnapshot getBlockSnapshot(World world, int x, int y, int z, int flag)
{
return new BlockSnapshot(world, x, y, z, world.getBlock(x, y, z), world.getBlockMetadata(x, y,z), flag);
}
public static BlockSnapshot readFromNBT(NBTTagCompound tag)
{
NBTTagCompound nbt = tag.getBoolean("hasTE") ? null : tag.getCompoundTag("tileEntity");
return new BlockSnapshot(
tag.getInteger("dimension"),
tag.getInteger("posX"),
tag.getInteger("posY"),
tag.getInteger("posZ"),
tag.getString("blockMod"),
tag.getString("blockName"),
tag.getInteger("metadata"),
tag.getInteger("flag"),
nbt);
}
public Block getCurrentBlock()
{
return world.getBlock(x, y, z);
}
public World getWorld()
{
if (this.world == null)
{
this.world = DimensionManager.getWorld(dimId);
}
return this.world;
}
public Block getReplacedBlock()
{
if (this.replacedBlock == null)
{
this.replacedBlock = GameRegistry.findBlock(this.blockIdentifier.modId, this.blockIdentifier.name);
}
return this.replacedBlock;
}
public TileEntity getTileEntity()
{
if (nbt != null)
return TileEntity.createAndLoadEntity(nbt);
else return null;
}
public boolean restore()
{
return restore(false);
}
public boolean restore(boolean force)
{
return restore(force, true);
}
public boolean restore(boolean force, boolean applyPhysics)
{
if (getCurrentBlock() != getReplacedBlock() || world.getBlockMetadata(x & 15, y, z & 15) != meta)
{
if (force)
{
world.setBlock(x, y, z, getReplacedBlock(), meta, applyPhysics ? 3 : 2);
}
else
{
return false;
}
}
world.setBlockMetadataWithNotify(x, y, z, meta, applyPhysics ? 3 : 2);
world.markBlockForUpdate(x, y, z);
TileEntity te = null;
if (nbt != null)
{
te = world.getTileEntity(x, y, z);
if (te != null)
{
te.readFromNBT(nbt);
}
}
if (DEBUG)
{
System.out.printf("Restored BlockSnapshot with data [World: %s ][Location: %d,%d,%d ][Meta: %d ][Block: %s ][TileEntity: %s ][force: %s ][applyPhysics: %s]", world.getWorldInfo().getWorldName(), x, y, z, meta, getReplacedBlock(), te, force, applyPhysics);
}
return true;
}
public boolean restoreToLocation(World world, int x, int y, int z, boolean force, boolean applyPhysics)
{
if (getCurrentBlock() != getReplacedBlock() || world.getBlockMetadata(x & 15, y, z & 15) != meta)
{
if (force)
{
world.setBlock(x, y, z, getReplacedBlock(), meta, applyPhysics ? 3 : 2);
}
else
{
return false;
}
}
world.setBlockMetadataWithNotify(x, y, z, meta, applyPhysics ? 3 : 2);
world.markBlockForUpdate(x, y, z);
TileEntity te = null;
if (nbt != null)
{
te = world.getTileEntity(x, y, z);
if (te != null)
{
te.readFromNBT(nbt);
}
}
if (DEBUG)
{
System.out.printf("Restored BlockSnapshot with data [World: %s ][Location: %d,%d,%d ][Meta: %d ][Block: %s ][TileEntity: %s ][force: %s ][applyPhysics: %s]", world.getWorldInfo().getWorldName(), x, y, z, meta, getReplacedBlock(), te, force, applyPhysics);
}
return true;
}
public void writeToNBT(NBTTagCompound compound)
{
compound.setString("blockMod", blockIdentifier.modId);
compound.setString("blockName", blockIdentifier.name);
compound.setInteger("posX", x);
compound.setInteger("posY", y);
compound.setInteger("posZ", z);
compound.setInteger("flag", flag);
compound.setInteger("dimension", dimId);
compound.setInteger("metadata", meta);
compound.setBoolean("hasTE", nbt != null);
if (nbt != null)
{
compound.setTag("tileEntity", nbt);
}
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final BlockSnapshot other = (BlockSnapshot) obj;
if (this.x != other.x)
{
return false;
}
if (this.y != other.y)
{
return false;
}
if (this.z != other.z)
{
return false;
}
if (this.meta != other.meta)
{
return false;
}
if (this.dimId != other.dimId)
{
return false;
}
if (this.nbt != other.nbt && (this.nbt == null || !this.nbt.equals(other.nbt)))
{
return false;
}
if (this.world != other.world && (this.world == null || !this.world.equals(other.world)))
{
return false;
}
if (this.blockIdentifier != other.blockIdentifier && (this.blockIdentifier == null || !this.blockIdentifier.equals(other.blockIdentifier)))
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 7;
hash = 73 * hash + this.x;
hash = 73 * hash + this.y;
hash = 73 * hash + this.z;
hash = 73 * hash + this.meta;
hash = 73 * hash + this.dimId;
hash = 73 * hash + (this.nbt != null ? this.nbt.hashCode() : 0);
hash = 73 * hash + (this.world != null ? this.world.hashCode() : 0);
hash = 73 * hash + (this.blockIdentifier != null ? this.blockIdentifier.hashCode() : 0);
return hash;
}
}
@@ -14,6 +14,7 @@ import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
@@ -21,6 +22,8 @@ import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.storage.IPlayerFileData;
import net.minecraft.world.storage.SaveHandler;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.entity.EntityStruckByLightningEvent;
import net.minecraftforge.event.entity.living.LivingPackSizeEvent;
import net.minecraftforge.event.entity.living.LivingSpawnEvent;
@@ -33,10 +36,31 @@ import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action;
import net.minecraftforge.event.entity.player.PlayerUseItemEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.BlockEvent.MultiPlaceEvent;
import net.minecraftforge.event.world.BlockEvent.PlaceEvent;
import net.minecraftforge.event.world.WorldEvent;
public class ForgeEventFactory
{
public static MultiPlaceEvent onPlayerMultiBlockPlace(EntityPlayer player, List<BlockSnapshot> blockSnapshots, ForgeDirection direction)
{
Block placedAgainst = blockSnapshots.get(0).world.getBlock(blockSnapshots.get(0).x + direction.getOpposite().offsetX, blockSnapshots.get(0).y + direction.getOpposite().offsetY, blockSnapshots.get(0).z + direction.getOpposite().offsetZ);
MultiPlaceEvent event = new MultiPlaceEvent(blockSnapshots, placedAgainst, player);
MinecraftForge.EVENT_BUS.post(event);
return event;
}
public static PlaceEvent onPlayerBlockPlace(EntityPlayer player, BlockSnapshot blockSnapshot, ForgeDirection direction)
{
Block placedAgainst = blockSnapshot.world.getBlock(blockSnapshot.x + direction.getOpposite().offsetX, blockSnapshot.y + direction.getOpposite().offsetY, blockSnapshot.z + direction.getOpposite().offsetZ);
PlaceEvent event = new PlaceEvent(blockSnapshot, placedAgainst, player);
MinecraftForge.EVENT_BUS.post(event);
return event;
}
public static boolean doPlayerHarvestCheck(EntityPlayer player, Block block, boolean success)
{
PlayerEvent.HarvestCheck event = new PlayerEvent.HarvestCheck(player, block, success);
@@ -1,17 +1,24 @@
package net.minecraftforge.event.world;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.ImmutableList;
import cpw.mods.fml.common.eventhandler.Cancelable;
import cpw.mods.fml.common.eventhandler.Event;
import net.minecraft.block.Block;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.util.BlockSnapshot;
public class BlockEvent extends Event {
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("forge.debugBlockEvent", "false"));
public final int x;
public final int y;
public final int z;
@@ -111,4 +118,63 @@ public class BlockEvent extends Event {
this.exp = exp;
}
}
/**
* Called when a block is placed by a player.
*
* If a Block Place event is cancelled, the block will not be placed.
*/
@Cancelable
public static class PlaceEvent extends BlockEvent {
public final EntityPlayer player;
public final ItemStack itemInHand;
public final BlockSnapshot blockSnapshot;
public final Block placedBlock;
public final Block placedAgainst;
public PlaceEvent(BlockSnapshot blockSnapshot, Block placedAgainst, EntityPlayer player) {
super(blockSnapshot.x, blockSnapshot.y, blockSnapshot.z, blockSnapshot.world, blockSnapshot.getCurrentBlock(), blockSnapshot.meta);
this.player = player;
this.itemInHand = player.getCurrentEquippedItem();
this.blockSnapshot = blockSnapshot;
this.placedBlock = blockSnapshot.getCurrentBlock();
this.placedAgainst = placedAgainst;
if (DEBUG)
{
System.out.printf("Created PlaceEvent - [PlacedBlock: %s ][PlacedAgainst: %s ][ItemStack: %s ][Player: %s ]\n", placedBlock, placedAgainst, player.getCurrentEquippedItem(), player);
}
}
}
/**
* Fired when a single block placement action of a player triggers the
* creation of multiple blocks(e.g. placing a bed block). The block returned
* by {@link #block} and its related methods is the block where
* the placed block would exist if the placement only affected a single
* block.
*/
@Cancelable
public static class MultiPlaceEvent extends PlaceEvent {
private final List<BlockSnapshot> blockSnapshots;
public MultiPlaceEvent(List<BlockSnapshot> blockSnapshots, Block placedAgainst, EntityPlayer player) {
super(blockSnapshots.get(0), placedAgainst, player);
this.blockSnapshots = ImmutableList.copyOf(blockSnapshots);
if (DEBUG)
{
System.out.printf("Created MultiPlaceEvent - [PlacedAgainst: %s ][ItemInHand: %s ][Player: %s ]\n", placedAgainst, this.itemInHand, player);
}
}
/**
* Gets a list of blocksnapshots for all blocks which were replaced by the
* placement of the new blocks. Most of these blocks will just be of type AIR.
*
* @return immutable list of replaced BlockSnapshots
*/
public List<BlockSnapshot> getReplacedBlockSnapshots() {
return blockSnapshots;
}
}
}