/** 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.item; import java.util.Collection; import com.google.common.collect.Lists; import net.minecraft.block.Block; import net.minecraft.block.BlockSnow; import net.minecraft.block.state.IBlockState; import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.common.util.Constants; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.api.block.ILiftable; import zeldaswordskills.api.item.IDynamicItemBlock; import zeldaswordskills.api.item.IHandleToss; import zeldaswordskills.api.item.IUnenchantable; import zeldaswordskills.client.ISwapModel; import zeldaswordskills.client.render.item.ModelDynamicItemBlock; /** * * The goal of this class is to represent a single block, picked up and held * in the player's hands as though it were carried for real, using NBT to store * the block id and metadata. * * If at any time the player switches to a different item, it should be placed * immediately into the world. * */ public class ItemHeldBlock extends BaseModItem implements IDynamicItemBlock, IHandleToss, ISwapModel, IUnenchantable { public ItemHeldBlock() { super(); setMaxDamage(0); setMaxStackSize(1); } /** * Returns a new ItemStack containing the passed in block, metadata, and gauntlet stack * NBT format is 'blockId' => integer id of block, 'metadata' => metadata of block, 'gauntlets' => stored gauntlet itemstack */ public static ItemStack getBlockStack(IBlockState state, ItemStack gauntlets) { ItemStack stack = new ItemStack(ZSSItems.heldBlock); Block block = state.getBlock(); stack.setTagCompound(new NBTTagCompound()); stack.getTagCompound().setInteger("blockId", Block.getIdFromBlock(block)); stack.getTagCompound().setInteger("metadata", state.getBlock().damageDropped(state)); if (gauntlets != null) { // Hack to allow server to contain the client-side block color information (see HeldBlockColorPacket) if (gauntlets.hasTagCompound() && gauntlets.getTagCompound().hasKey("blockColor")) { stack.getTagCompound().setInteger("blockColor", gauntlets.getTagCompound().getInteger("blockColor")); gauntlets.getTagCompound().removeTag("blockColor"); } stack.getTagCompound().setTag("gauntlets", gauntlets.writeToNBT(new NBTTagCompound())); } return stack; } @Override public IBlockState getBlockStateFromStack(ItemStack stack) { Block block = getBlockFromStack(stack); return block.getStateFromMeta(getMetaFromStack(stack)); } /** Returns the stored Block or stone if none available */ public Block getBlockFromStack(ItemStack stack) { Block block = Blocks.stone; if (stack.hasTagCompound() && stack.getTagCompound().hasKey("blockId", Constants.NBT.TAG_INT)) { block = Block.getBlockById(stack.getTagCompound().getInteger("blockId")); } return (block == null ? Blocks.stone : block); } /** Returns the metadata value associated with the stored block */ public int getMetaFromStack(ItemStack stack) { return (stack.hasTagCompound() ? stack.getTagCompound().getInteger("metadata") : 0); } @Override @SideOnly(Side.CLIENT) public int getColorFromItemStack(ItemStack stack, int renderPass) { if (stack.hasTagCompound() && stack.getTagCompound().hasKey("blockColor")) { return stack.getTagCompound().getInteger("blockColor"); } return super.getColorFromItemStack(stack, renderPass); } @Override public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) { return !ItemStack.areItemsEqual(oldStack, newStack); } /** * Drops the held block as close to the entity's position as possible, without * dropping it on the entity * @return true if the dropped block was able to be placed */ public boolean dropHeldBlock(ItemStack stack, World world, EntityPlayer player) { Block block = getBlockFromStack(stack); if (block != null) { int x = MathHelper.floor_double(player.posX); int y = MathHelper.floor_double(player.getEntityBoundingBox().minY); int z = MathHelper.floor_double(player.posZ); int meta = getMetaFromStack(stack); IBlockState state = block.getStateFromMeta(meta); Vec3 vec3 = player.getLookVec(); int dx = Math.abs(vec3.xCoord) < 0.25D ? 0 : (vec3.xCoord < 0 ? -1 : 1); int dz = Math.abs(vec3.zCoord) < 0.25D ? 0 : (vec3.zCoord < 0 ? -1 : 1); boolean flag = tryDropBlock(stack, world, player, x + dx, y + 1, z + dz, dx, dz, state, meta, 4); if (!flag) { flag = tryDropBlock(stack, world, player, x, y + 1, z, -dx, -dz, state, meta, 5); } if (!flag && !block.getMaterial().isSolid()) { flag = placeBlockAt(stack, player, world, new BlockPos(x, y, z), EnumFacing.UP, state); } if (flag) { world.playSoundEffect((x + 0.5D), (y + 0.5D), (z + 0.5D), block.stepSound.getBreakSound(), (block.stepSound.getVolume() + 1.0F) / 2.0F, block.stepSound.getFrequency() * 0.8F); } return flag; } return false; } /** * Tries to drop the block at the closest block to x/y/z along the vector dx/dz in a semi-circular pattern * @param n the number of block lengths to check * @return true if the block was placed */ private boolean tryDropBlock(ItemStack stack, World world, EntityPlayer player, int x, int y, int z, int dx, int dz, IBlockState state, int meta, int n) { boolean flag = false; for (int i = 0; i < n && !flag; ++i) { for (int j = 0; j < (n - i) && !flag; ++ j) { for (int k = 0; k < 4 && !flag; ++k) { flag = tryPlaceBlock(stack, world, player, new BlockPos(x + (dz * j), y - k, z + (dx * j)), state, meta); if (!flag) { flag = tryPlaceBlock(stack, world, player, new BlockPos(x - (dz * j), y - k, z - (dx * j)), state, meta); } } } x += dx; z += dz; } return flag; } /** * Returns true if the block was placed at the given position; checks for entity collision and other blocks */ private boolean tryPlaceBlock(ItemStack stack, World world, EntityPlayer player, BlockPos pos, IBlockState state, int meta) { Block block = state.getBlock(); Block b = world.getBlockState(pos).getBlock(); if (world.getBlockState(pos.down()).getBlock().isFullBlock() && b.isReplaceable(world, pos)) { if (world.canBlockBePlaced(block, pos, false, EnumFacing.UP, null, stack)) { state = block.onBlockPlaced(world, pos, EnumFacing.UP, (pos.getX() + 0.5F), (pos.getY() + 0.5F), (pos.getZ() + 0.5F), meta, player); if (placeBlockAt(stack, player, world, pos, EnumFacing.UP, state)) { world.playSoundEffect((pos.getX() + 0.5D), (pos.getY() + 0.5D), (pos.getZ() + 0.5D), block.stepSound.getBreakSound(), (block.stepSound.getVolume() + 1.0F) / 2.0F, block.stepSound.getFrequency() * 0.8F); return true; } } } return false; } @Override public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isHeld) { if (isHeld) { if (!(entity instanceof EntityPlayer) || !((EntityPlayer) entity).capabilities.isFlying) { entity.motionX *= 0.25D; entity.motionZ *= 0.25D; } } else { if (!world.isRemote && entity instanceof EntityPlayer && dropHeldBlock(stack, world, (EntityPlayer) entity)) { ItemStack gauntlets = (stack.hasTagCompound() && stack.getTagCompound().hasKey("gauntlets") ? ItemStack.loadItemStackFromNBT(stack.getTagCompound().getCompoundTag("gauntlets")) : null); ((EntityPlayer) entity).inventory.setInventorySlotContents(slot, gauntlets); } } } @Override public boolean onEntitySwing(EntityLivingBase entity, ItemStack stack) { return true; } @Override public boolean onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ) { IBlockState worldState = world.getBlockState(pos); Block worldBlock = worldState.getBlock(); Block blockToPlace = getBlockFromStack(stack); if (worldBlock == Blocks.snow_layer && ((Integer) worldState.getValue(BlockSnow.LAYERS)).intValue() < 1) { side = EnumFacing.UP; } else if (!worldBlock.isReplaceable(world, pos)) { pos = pos.offset(side); } if (stack.stackSize == 0) { return false; } else if (!player.canPlayerEdit(pos, side, stack)) { return false; } else if (pos.getY() == 255 && blockToPlace.getMaterial().isSolid()) { return false; } else if (world.canBlockBePlaced(blockToPlace, pos, false, side, null, stack)) { int meta = getMetaFromStack(stack); IBlockState state = blockToPlace.onBlockPlaced(world, pos, side, hitX, hitY, hitZ, meta, player); if (!world.isRemote && placeBlockAt(stack, player, world, pos, side, state)) { world.playSoundEffect((pos.getX() + 0.5D), (pos.getY() + 0.5D), (pos.getZ() + 0.5D), blockToPlace.stepSound.getBreakSound(), (blockToPlace.stepSound.getVolume() + 1.0F) / 2.0F, blockToPlace.stepSound.getFrequency() * 0.8F); ItemStack gauntlets = (stack.hasTagCompound() && stack.getTagCompound().hasKey("gauntlets") ? ItemStack.loadItemStackFromNBT(stack.getTagCompound().getCompoundTag("gauntlets")) : null); player.setCurrentItemOrArmor(0, gauntlets); } return true; } else { return false; } } /** * Copied from ItemBlock (removed hit parameters); places the block after all other checks have been made */ public boolean placeBlockAt(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, IBlockState state) { if (!world.setBlockState(pos, state, 3)) { return false; } Block block = world.getBlockState(pos).getBlock(); if (block == state.getBlock()) { ItemBlock.setTileEntityNBT(world, player, pos, stack); block.onBlockPlacedBy(world, pos, state, player, stack); if (block instanceof ILiftable) { ((ILiftable) block).onHeldBlockPlaced(world, stack, pos, state); } } return true; } @Override public String getUnlocalizedName(ItemStack stack) { ItemStack block = new ItemStack(getBlockFromStack(stack)); return (block != null ? block.getUnlocalizedName() : Blocks.stone.getUnlocalizedName()); } @Override public void onItemTossed(EntityItem item, EntityPlayer player) { ItemStack stack = item.getEntityItem(); if (dropHeldBlock(stack, player.getEntityWorld(), player)) { ItemStack gauntlets = (stack.hasTagCompound() && stack.getTagCompound().hasKey("gauntlets") ? ItemStack.loadItemStackFromNBT(stack.getTagCompound().getCompoundTag("gauntlets")) : null); player.setCurrentItemOrArmor(0, gauntlets); item.setDead(); } } /** * Required or smart model will not work */ @Override @SideOnly(Side.CLIENT) public void registerResources() { ModelLoader.registerItemVariants(this, ModelDynamicItemBlock.resource); } @Override @SideOnly(Side.CLIENT) public Collection<ModelResourceLocation> getDefaultResources() { return Lists.newArrayList(ModelDynamicItemBlock.resource); } @Override @SideOnly(Side.CLIENT) public Class<? extends IBakedModel> getNewModel() { return ModelDynamicItemBlock.class; } }