/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.event.world;
import java.util.EnumSet;
import java.util.List;
import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Enchantments;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.Event.HasResult;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class BlockEvent extends Event
{
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("forge.debugBlockEvent", "false"));
private final World world;
private final BlockPos pos;
private final IBlockState state;
public BlockEvent(World world, BlockPos pos, IBlockState state)
{
this.pos = pos;
this.world = world;
this.state = state;
}
public World getWorld()
{
return world;
}
public BlockPos getPos()
{
return pos;
}
public IBlockState getState()
{
return state;
}
/**
* Fired when a block is about to drop it's harvested items. The {@link #drops} array can be amended, as can the {@link #dropChance}.
* <strong>Note well:</strong> the {@link #harvester} player field is null in a variety of scenarios. Code expecting null.
*
* The {@link #dropChance} is used to determine which items in this array will actually drop, compared to a random number. If you wish, you
* can pre-filter yourself, and set {@link #dropChance} to 1.0f to always drop the contents of the {@link #drops} array.
*
* {@link #isSilkTouching} is set if this is considered a silk touch harvesting operation, vs a normal harvesting operation. Act accordingly.
*
* @author cpw
*/
public static class HarvestDropsEvent extends BlockEvent
{
private final int fortuneLevel;
private final List<ItemStack> drops;
private final boolean isSilkTouching;
private float dropChance; // Change to e.g. 1.0f, if you manipulate the list and want to guarantee it always drops
private final EntityPlayer harvester; // May be null for non-player harvesting such as explosions or machines
public HarvestDropsEvent(World world, BlockPos pos, IBlockState state, int fortuneLevel, float dropChance, List<ItemStack> drops, EntityPlayer harvester, boolean isSilkTouching)
{
super(world, pos, state);
this.fortuneLevel = fortuneLevel;
this.setDropChance(dropChance);
this.drops = drops;
this.isSilkTouching = isSilkTouching;
this.harvester = harvester;
}
public int getFortuneLevel() { return fortuneLevel; }
public List<ItemStack> getDrops() { return drops; }
public boolean isSilkTouching() { return isSilkTouching; }
public float getDropChance() { return dropChance; }
public void setDropChance(float dropChance) { this.dropChance = dropChance; }
public EntityPlayer getHarvester() { return harvester; }
}
/**
* Event that is fired when an Block is about to be broken by a player
* Canceling this event will prevent the Block from being broken.
*/
@Cancelable
public static class BreakEvent extends BlockEvent
{
/** Reference to the Player who broke the block. If no player is available, use a EntityFakePlayer */
private final EntityPlayer player;
private int exp;
public BreakEvent(World world, BlockPos pos, IBlockState state, EntityPlayer player)
{
super(world, pos, state);
this.player = player;
if (state == null || !ForgeHooks.canHarvestBlock(state.getBlock(), player, world, pos) || // Handle empty block or player unable to break block scenario
(state.getBlock().canSilkHarvest(world, pos, world.getBlockState(pos), player) && EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, player.getHeldItemMainhand()) > 0)) // If the block is being silk harvested, the exp dropped is 0
{
this.exp = 0;
}
else
{
int bonusLevel = EnchantmentHelper.getEnchantmentLevel(Enchantments.FORTUNE, player.getHeldItemMainhand());
this.exp = state.getBlock().getExpDrop(state, world, pos, bonusLevel);
}
}
public EntityPlayer getPlayer()
{
return player;
}
/**
* Get the experience dropped by the block after the event has processed
*
* @return The experience to drop or 0 if the event was canceled
*/
public int getExpToDrop()
{
return this.isCanceled() ? 0 : exp;
}
/**
* Set the amount of experience dropped by the block after the event has processed
*
* @param exp 1 or higher to drop experience, else nothing will drop
*/
public void setExpToDrop(int exp)
{
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
{
private final EntityPlayer player;
private final BlockSnapshot blockSnapshot;
private final IBlockState placedBlock;
private final IBlockState placedAgainst;
private final EnumHand hand;
@Deprecated
public PlaceEvent(BlockSnapshot blockSnapshot, IBlockState placedAgainst, EntityPlayer player)
{
this(blockSnapshot, placedAgainst, player, EnumHand.MAIN_HAND);
}
public PlaceEvent(@Nonnull BlockSnapshot blockSnapshot, @Nonnull IBlockState placedAgainst, @Nonnull EntityPlayer player, @Nonnull EnumHand hand) {
super(blockSnapshot.getWorld(), blockSnapshot.getPos(), blockSnapshot.getCurrentBlock());
this.player = player;
this.blockSnapshot = blockSnapshot;
this.placedBlock = blockSnapshot.getCurrentBlock();
this.placedAgainst = placedAgainst;
this.hand = hand;
if (DEBUG)
{
System.out.printf("Created PlaceEvent - [PlacedBlock: %s ][PlacedAgainst: %s ][ItemStack: %s ][Player: %s ][Hand: %s]\n", getPlacedBlock(), placedAgainst, player.getHeldItem(hand), player, hand);
}
}
public EntityPlayer getPlayer() { return player; }
@Nonnull
@Deprecated
public ItemStack getItemInHand() { return player.getHeldItem(hand); }
public BlockSnapshot getBlockSnapshot() { return blockSnapshot; }
public IBlockState getPlacedBlock() { return placedBlock; }
public IBlockState getPlacedAgainst() { return placedAgainst; }
public EnumHand getHand() { return hand; }
}
/**
* 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 #state} 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;
@Deprecated
public MultiPlaceEvent(List<BlockSnapshot> blockSnapshots, IBlockState placedAgainst, EntityPlayer player)
{
this(blockSnapshots, placedAgainst, player, EnumHand.MAIN_HAND);
}
public MultiPlaceEvent(@Nonnull List<BlockSnapshot> blockSnapshots, @Nonnull IBlockState placedAgainst, @Nonnull EntityPlayer player, @Nonnull EnumHand hand) {
super(blockSnapshots.get(0), placedAgainst, player, hand);
this.blockSnapshots = ImmutableList.copyOf(blockSnapshots);
if (DEBUG)
{
System.out.printf("Created MultiPlaceEvent - [PlacedAgainst: %s ][ItemInHand: %s ][Player: %s ]\n", placedAgainst, player.getHeldItem(hand), 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;
}
}
/**
* Fired when a physics update occurs on a block. This event acts as
* a way for mods to detect physics updates, in the same way a BUD switch
* does. This event is only called on the server.
*/
@Cancelable
public static class NeighborNotifyEvent extends BlockEvent
{
private final EnumSet<EnumFacing> notifiedSides;
private final boolean forceRedstoneUpdate;
public NeighborNotifyEvent(World world, BlockPos pos, IBlockState state, EnumSet<EnumFacing> notifiedSides, boolean forceRedstoneUpdate)
{
super(world, pos, state);
this.notifiedSides = notifiedSides;
this.forceRedstoneUpdate = forceRedstoneUpdate;
}
/**
* Gets a list of directions from the base block that updates will occur upon.
*
* @return list of notified directions
*/
public EnumSet<EnumFacing> getNotifiedSides()
{
return notifiedSides;
}
/**
* Get if redstone update was forced during setBlock call (0x16 to flags)
* @return if the flag was set
*/
public boolean getForceRedstoneUpdate()
{
return forceRedstoneUpdate;
}
}
/**
* Fired to check whether a non-source block can turn into a source block.
* A result of ALLOW causes a source block to be created even if the liquid
* usually doesn't do that (like lava), and a result of DENY prevents creation
* even if the liquid usually does do that (like water).
*/
@HasResult
public static class CreateFluidSourceEvent extends BlockEvent
{
public CreateFluidSourceEvent(World world, BlockPos pos, IBlockState state)
{
super(world, pos, state);
}
}
/**
* Fired when a crop block grows. See subevents.
*
*/
public static class CropGrowEvent extends BlockEvent
{
public CropGrowEvent(World world, BlockPos pos, IBlockState state)
{
super(world, pos, state);
}
/**
* Fired when any "growing age" blocks (for example cacti, chorus plants, or crops
* in vanilla) attempt to advance to the next growth age state during a random tick.<br>
* <br>
* {@link Result#DEFAULT} will pass on to the vanilla growth mechanics.<br>
* {@link Result#ALLOW} will force the plant to advance a growth stage.<br>
* {@link Result#DENY} will prevent the plant from advancing a growth stage.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
*/
@HasResult
public static class Pre extends CropGrowEvent
{
public Pre(World world, BlockPos pos, IBlockState state)
{
super(world, pos, state);
}
}
/**
* Fired when "growing age" blocks (for example cacti, chorus plants, or crops
* in vanilla) have successfully grown. The block's original state is available,
* in addition to its new state.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
*/
public static class Post extends CropGrowEvent
{
private final IBlockState originalState;
public Post(World world, BlockPos pos, IBlockState original, IBlockState state)
{
super(world, pos, state);
originalState = original;
}
public IBlockState getOriginalState()
{
return originalState;
}
}
}
}