/** Copyright (C) <2017> <coolAlias> This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such, you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package zeldaswordskills.block; import java.util.Random; import net.minecraft.block.Block; import net.minecraft.block.BlockCrops; import net.minecraft.block.material.Material; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.state.BlockState; import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.block.statemap.IStateMapper; import net.minecraft.client.renderer.block.statemap.StateMap; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IProjectile; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.world.Explosion; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.common.EnumPlantType; import net.minecraftforge.fml.common.eventhandler.Event.Result; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.api.block.IBoomerangBlock; import zeldaswordskills.api.block.IExplodable; import zeldaswordskills.api.block.IQuakeBlock; import zeldaswordskills.api.block.IWhipBlock; import zeldaswordskills.api.entity.BombType; import zeldaswordskills.api.entity.CustomExplosion; import zeldaswordskills.api.entity.IEntityBomb; import zeldaswordskills.entity.projectile.EntityBomb; import zeldaswordskills.entity.projectile.EntityBoomerang; import zeldaswordskills.entity.projectile.EntityWhip; import zeldaswordskills.item.ZSSItems; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.TargetUtils; public class BlockBombFlower extends BlockCrops implements IBoomerangBlock, ICustomStateMapper, IExplodable, IQuakeBlock, IWhipBlock { /** Uses bit 8 (true) to flag this block for an explosion next update tick */ public static final PropertyBool EXPLODE = PropertyBool.create("explode"); public BlockBombFlower() { super(); setCreativeTab(null); // no Creative Tab setDefaultState(blockState.getBaseState().withProperty(AGE, Integer.valueOf(0)).withProperty(EXPLODE, Boolean.valueOf(false))); } @Override public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos) { return EnumPlantType.Cave; } @Override protected Item getSeed() { return ZSSItems.bombFlowerSeed; } @Override protected Item getCrop() { return ZSSItems.bombFlowerSeed; } @Override public boolean canDropFromExplosion(Explosion explosion) { return false; } @Override public boolean canPlaceBlockAt(World world, BlockPos pos) { return super.canPlaceBlockAt(world, pos) && canBlockStay(world, pos, world.getBlockState(pos)); } @Override protected boolean canPlaceBlockOn(Block block) { return block.getMaterial() == Material.rock; } @Override public boolean canBlockStay(World world, BlockPos pos, IBlockState state) { Block soil = world.getBlockState(pos.down()).getBlock(); return canPlaceBlockOn(soil) && hasLava(world, pos.down()); } private boolean hasLava(World world, BlockPos pos) { boolean hasLava = ( world.getBlockState(pos.north()).getBlock().getMaterial() == Material.lava || world.getBlockState(pos.south()).getBlock().getMaterial() == Material.lava || world.getBlockState(pos.east()).getBlock().getMaterial() == Material.lava || world.getBlockState(pos.west()).getBlock().getMaterial() == Material.lava); return hasLava; } @Override public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing face, float hitX, float hitY, float hitZ) { if (player.getHeldItem() != null || state.getValue(AGE).intValue() != 7) { return false; // this lets bonemeal do its thing } else if (!world.isRemote) { player.setCurrentItemOrArmor(0, new ItemStack(ZSSItems.bomb,1,BombType.BOMB_FLOWER.ordinal())); world.setBlockState(pos, getDefaultState()); } return true; } @Override public void onBlockClicked(World world, BlockPos pos, EntityPlayer player) { if (world.getBlockState(pos).getValue(AGE).intValue() == 7) { if (PlayerUtils.isHoldingWeapon(player)) { createExplosion(world, pos, true); } else { disperseSeeds(world, pos, true); } } } @Override public void onBlockExploded(World world, BlockPos pos, Explosion explosion) { IBlockState state = world.getBlockState(pos); if (state.getValue(EXPLODE).booleanValue()) { // do nothing, these will explode on their own next update tick } else if (state.getValue(AGE).intValue() == 7) { world.setBlockState(pos, state.withProperty(EXPLODE, Boolean.valueOf(true)), 2); world.scheduleUpdate(pos, this, 5); } else { Entity exploder = (explosion instanceof CustomExplosion ? ((CustomExplosion) explosion).exploder : null); if (!(exploder instanceof IEntityBomb) || ((IEntityBomb) exploder).getType() != BombType.BOMB_FLOWER) { super.onBlockExploded(world, pos, explosion); } } } @Override public boolean onBoomerangCollided(World world, BlockPos pos, IBlockState state, EntityBoomerang boomerang) { if (!world.isRemote && state.getValue(AGE).intValue() == 7) { boolean captured = false; world.setBlockState(pos, getDefaultState()); EntityItem bomb = new EntityItem(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, new ItemStack(ZSSItems.bomb, 1, BombType.BOMB_FLOWER.ordinal())); world.spawnEntityInWorld(bomb); if (boomerang.captureItem(bomb)) { captured = true; // stop explosion from happening } else { bomb.setDead(); } if (!captured) { createExplosion(world, pos, true); } } return false; } @Override public void onEntityCollidedWithBlock(World world, BlockPos pos, Entity entity) { this.onEntityCollidedWithBlock(world, pos, world.getBlockState(pos), entity); } @Override public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity) { if (state.getValue(AGE).intValue() == 7 && entity instanceof IProjectile) { createExplosion(world, pos, true); } } @Override public void handleQuakeEffect(World world, BlockPos pos, IBlockState state, EntityPlayer player) { if (state.getValue(AGE).intValue() == 7) { // TODO call disperseSeeds instead? schedule an update tick? createExplosion(world, pos, true); } } @Override public boolean canBreakBlock(WhipType whip, EntityLivingBase thrower, World world, BlockPos pos, EnumFacing face) { return getGrowthStage(world.getBlockState(pos).getValue(AGE).intValue()) < 2; } @Override public boolean canGrabBlock(WhipType whip, EntityLivingBase thrower, World world, BlockPos pos, EnumFacing face) { return world.getBlockState(pos).getValue(AGE).intValue() == 7; } @Override public Result shouldSwing(EntityWhip whip, World world, BlockPos pos, int ticksInGround) { if (ticksInGround > 30 && world.getBlockState(pos).getValue(AGE).intValue() == 7) { EntityLivingBase thrower = whip.getThrower(); EntityItem bomb = new EntityItem(world, whip.posX, whip.posY + 1, whip.posZ, new ItemStack(ZSSItems.bomb, 1, BombType.BOMB_FLOWER.ordinal())); double dx = thrower.posX - bomb.posX; double dy = (thrower.posY + thrower.getEyeHeight()) - (bomb.posY - 1); double dz = thrower.posZ - bomb.posZ; TargetUtils.setEntityHeading(bomb, dx, dy + 0.5D, dz, 1.0F, 0.0F, true); if (!world.isRemote) { world.spawnEntityInWorld(bomb); } world.setBlockToAir(pos); whip.setDead(); } return Result.DENY; } /** * Override updateTick because bomb flowers don't care about fertile soil or water * Don't call super, so make sure to call checkAndDropBlock from BlockBush * Note that updateTick is only ever called on the server */ @Override public void updateTick(World world, BlockPos pos, IBlockState state, Random rand) { checkAndDropBlock(world, pos, state); int meta = getMetaFromState(state); if (meta > 7) { createExplosion(world, pos, true); } else if (meta < 7 && rand.nextInt(6) == 0) { world.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(meta + 1)), 2); } else if (meta == 7 && rand.nextInt(16) == 0) { disperseSeeds(world, pos, false); } } /** * Creates an immediate explosion at the block's coordinates, optionally setting the block to air */ private void createExplosion(World world, BlockPos pos, boolean toAir) { if (!world.isRemote) { if (toAir) { world.setBlockToAir(pos); } CustomExplosion.createExplosion(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 3.0F, BombType.BOMB_FLOWER); } } /** * Spawns a bomb entity at the block's position and sets the growth stage back to zero */ private void disperseSeeds(World world, BlockPos pos, boolean isGriefing) { if (!world.isRemote) { world.setBlockState(pos, getDefaultState()); EntityBomb bomb = new EntityBomb(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D).setType(BombType.BOMB_FLOWER).setFuseTime(64); if (!isGriefing) { bomb.setNoGrief(); } world.spawnEntityInWorld(bomb); } } /** * Returns the growth stage from meta, i.e. 0 (seedling), 1, 2, or 3 (fully mature) */ private int getGrowthStage(int meta) { meta &= 0x7; // only care about the first 7 bits return meta == 6 ? 2 : meta >> 1; } @Override public AxisAlignedBB getCollisionBoundingBox(World world, BlockPos pos, IBlockState state) { int stage = getGrowthStage(state.getValue(AGE).intValue()); if (stage == 0) { return null; } return new AxisAlignedBB(pos.getX() + minX, pos.getY() + minY, pos.getZ() + minZ, pos.getX() + maxX, pos.getY() + maxY, pos.getZ() + maxZ); } @Override public void setBlockBoundsBasedOnState(IBlockAccess world, BlockPos pos) { int stage = getGrowthStage(world.getBlockState(pos).getValue(AGE).intValue()); setBlockBounds(0.1F, 0.0F, 0.1F, 0.9F, 0.2F + (stage * 0.15F), 0.9F); } @Override public IBlockState getStateFromMeta(int meta) { Boolean explode = Boolean.valueOf((meta & 0x8) > 0); return this.getDefaultState().withProperty(AGE, Integer.valueOf(meta & 0x7)).withProperty(EXPLODE, explode); } @Override public int getMetaFromState(IBlockState state) { int i = state.getValue(AGE).intValue(); if (state.getValue(EXPLODE).booleanValue()) { i |= 0x8; } return i; } @Override protected BlockState createBlockState() { return new BlockState(this, AGE, EXPLODE); } @Override @SideOnly(Side.CLIENT) public IStateMapper getCustomStateMap() { return (new StateMap.Builder()).ignore(EXPLODE).build(); } }