package slimeknights.tconstruct.library.utils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.PlayerControllerMP; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Enchantments; import net.minecraft.init.MobEffects; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.network.play.client.CPacketPlayerDigging; import net.minecraft.network.play.server.SPacketAnimation; import net.minecraft.network.play.server.SPacketBlockChange; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.stats.AchievementList; import net.minecraft.stats.StatList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3i; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.IShearable; import net.minecraftforge.event.ForgeEventFactory; import java.util.List; import slimeknights.tconstruct.TConstruct; import slimeknights.tconstruct.common.TinkerNetwork; import slimeknights.tconstruct.library.TinkerRegistry; import slimeknights.tconstruct.library.events.TinkerToolEvent; import slimeknights.tconstruct.library.tinkering.Category; import slimeknights.tconstruct.library.tinkering.TinkersItem; import slimeknights.tconstruct.library.tools.ToolCore; import slimeknights.tconstruct.library.tools.ranged.IProjectile; import slimeknights.tconstruct.library.traits.ITrait; import slimeknights.tconstruct.tools.TinkerModifiers; import slimeknights.tconstruct.tools.common.network.ToolBreakAnimationPacket; import slimeknights.tconstruct.tools.modifiers.ModReinforced; public final class ToolHelper { private ToolHelper() { } public static boolean hasCategory(ItemStack stack, Category category) { if(stack == null || stack.getItem() == null || !(stack.getItem() instanceof TinkersItem)) { return false; } return ((TinkersItem) stack.getItem()).hasCategory(category); } /* Basic Tool data */ public static int getDurabilityStat(ItemStack stack) { return getIntTag(stack, Tags.DURABILITY); } public static int getHarvestLevelStat(ItemStack stack) { return getIntTag(stack, Tags.HARVESTLEVEL); } /** Returns the speed saved on the tool. NOT the actual mining speed, see getActualMiningSpeed */ public static float getMiningSpeedStat(ItemStack stack) { return getfloatTag(stack, Tags.MININGSPEED); } public static float getAttackStat(ItemStack stack) { return getfloatTag(stack, Tags.ATTACK); } public static float getActualAttack(ItemStack stack) { float damage = getAttackStat(stack); if(stack != null && stack.getItem() instanceof ToolCore) { damage *= ((ToolCore) stack.getItem()).damagePotential(); } return damage; } /** * Returns the attack speed saved on the tool. * This is normally just a number from 1 to 2, the actual attack speed is in getActualAttackSpeed */ public static float getAttackSpeedStat(ItemStack stack) { return getfloatTag(stack, Tags.ATTACKSPEEDMULTIPLIER); } /** Returns the actual attack speed */ public static float getActualAttackSpeed(ItemStack stack) { float speed = getAttackSpeedStat(stack); if(stack != null && stack.getItem() instanceof ToolCore) { speed *= ((ToolCore) stack.getItem()).attackSpeed(); } return speed; } /** Returns the actual mining speed. */ public static float getActualMiningSpeed(ItemStack stack) { float speed = getMiningSpeedStat(stack); if(stack != null && stack.getItem() instanceof ToolCore) { speed *= ((ToolCore) stack.getItem()).miningSpeedModifier(); } return speed; } public static int getFreeModifiers(ItemStack stack) { return getIntTag(stack, Tags.FREE_MODIFIERS); } public static int getFortuneLevel(ItemStack stack) { int fortune = EnchantmentHelper.getEnchantmentLevel(Enchantments.FORTUNE, stack); int luck = TinkerModifiers.modLuck.getLuckLevel(stack); return Math.max(fortune, luck); } public static List<ITrait> getTraits(ItemStack stack) { List<ITrait> traits = Lists.newLinkedList(); NBTTagList traitsTagList = TagUtil.getTraitsTagList(stack); for(int i = 0; i < traitsTagList.tagCount(); i++) { ITrait trait = TinkerRegistry.getTrait(traitsTagList.getStringTagAt(i)); if(trait != null) { traits.add(trait); } } return traits; } public static float calcDigSpeed(ItemStack stack, IBlockState blockState) { if(blockState == null) { return 0f; } if(!stack.hasTagCompound()) { return 1f; } // check if the tool has the correct class and harvest level if(!canHarvest(stack, blockState)) { return 0f; } if(isBroken(stack)) { return 0.3f; } // calculate speed depending on stats NBTTagCompound tag = TagUtil.getToolTag(stack); float speed = tag.getFloat(Tags.MININGSPEED); if(stack.getItem() instanceof ToolCore) { speed *= ((ToolCore) stack.getItem()).miningSpeedModifier(); } return speed; } /** * Returns true if the tool is effective for harvesting the given block. */ public static boolean isToolEffective(ItemStack stack, IBlockState state) { // check material for(String type : stack.getItem().getToolClasses(stack)) { if(state.getBlock().isToolEffective(type, state)) { return true; } } return false; } // also checks for the tools effectiveness public static boolean isToolEffective2(ItemStack stack, IBlockState state) { if(isToolEffective(stack, state)) { return true; } // this will be the only place besides fortify where a modifier is hardcoded. I promise. :L if(TinkerUtil.hasModifier(TagUtil.getTagSafe(stack), TinkerModifiers.modBlasting.getIdentifier()) && state.getMaterial().isToolNotRequired()) { return true; } return stack.getItem() instanceof ToolCore && ((ToolCore) stack.getItem()).isEffective(state); } /** * Checks if an item has the right harvest level of the correct type for the block. */ public static boolean canHarvest(ItemStack stack, IBlockState state) { Block block = state.getBlock(); // doesn't require a tool if(state.getMaterial().isToolNotRequired()) { return true; } String type = block.getHarvestTool(state); int level = block.getHarvestLevel(state); return stack.getItem().getHarvestLevel(stack, type) >= level; } /* Harvesting */ public static ImmutableList<BlockPos> calcAOEBlocks(ItemStack stack, World world, EntityPlayer player, BlockPos origin, int width, int height, int depth) { return calcAOEBlocks(stack, world, player, origin, width, height, depth, -1); } public static ImmutableList<BlockPos> calcAOEBlocks(ItemStack stack, World world, EntityPlayer player, BlockPos origin, int width, int height, int depth, int distance) { // only works with toolcore because we need the raytrace call if(stack == null || !(stack.getItem() instanceof ToolCore)) { return ImmutableList.of(); } // find out where the player is hitting the block IBlockState state = world.getBlockState(origin); if(!isToolEffective2(stack, state)) { return ImmutableList.of(); } if(state.getMaterial() == Material.AIR) { // what are you DOING? return ImmutableList.of(); } // raytrace to get the side, but has to result in the same block RayTraceResult mop = ((ToolCore) stack.getItem()).rayTrace(world, player, true); if(mop == null || !origin.equals(mop.getBlockPos())) { mop = ((ToolCore) stack.getItem()).rayTrace(world, player, false); if(mop == null || !origin.equals(mop.getBlockPos())) { return ImmutableList.of(); } } // fire event TinkerToolEvent.ExtraBlockBreak event = TinkerToolEvent.ExtraBlockBreak.fireEvent(stack, player, state, width, height, depth, distance); if(event.isCanceled()) { return ImmutableList.of(); } width = event.width; height = event.height; depth = event.depth; distance = event.distance; // we know the block and we know which side of the block we're hitting. time to calculate the depth along the different axes int x, y, z; BlockPos start = origin; switch(mop.sideHit) { case DOWN: case UP: // x y depends on the angle we look? Vec3i vec = player.getHorizontalFacing().getDirectionVec(); x = vec.getX() * height + vec.getZ() * width; y = mop.sideHit.getAxisDirection().getOffset() * -depth; z = vec.getX() * width + vec.getZ() * height; start = start.add(-x / 2, 0, -z / 2); if(x % 2 == 0) { if(x > 0 && mop.hitVec.xCoord - mop.getBlockPos().getX() > 0.5d) { start = start.add(1, 0, 0); } else if(x < 0 && mop.hitVec.xCoord - mop.getBlockPos().getX() < 0.5d) { start = start.add(-1, 0, 0); } } if(z % 2 == 0) { if(z > 0 && mop.hitVec.zCoord - mop.getBlockPos().getZ() > 0.5d) { start = start.add(0, 0, 1); } else if(z < 0 && mop.hitVec.zCoord - mop.getBlockPos().getZ() < 0.5d) { start = start.add(0, 0, -1); } } break; case NORTH: case SOUTH: x = width; y = height; z = mop.sideHit.getAxisDirection().getOffset() * -depth; start = start.add(-x / 2, -y / 2, 0); if(x % 2 == 0 && mop.hitVec.xCoord - mop.getBlockPos().getX() > 0.5d) { start = start.add(1, 0, 0); } if(y % 2 == 0 && mop.hitVec.yCoord - mop.getBlockPos().getY() > 0.5d) { start = start.add(0, 1, 0); } break; case WEST: case EAST: x = mop.sideHit.getAxisDirection().getOffset() * -depth; y = height; z = width; start = start.add(-0, -y / 2, -z / 2); if(y % 2 == 0 && mop.hitVec.yCoord - mop.getBlockPos().getY() > 0.5d) { start = start.add(0, 1, 0); } if(z % 2 == 0 && mop.hitVec.zCoord - mop.getBlockPos().getZ() > 0.5d) { start = start.add(0, 0, 1); } break; default: x = y = z = 0; } ImmutableList.Builder<BlockPos> builder = ImmutableList.builder(); for(int xp = start.getX(); xp != start.getX() + x; xp += x / MathHelper.abs(x)) { for(int yp = start.getY(); yp != start.getY() + y; yp += y / MathHelper.abs(y)) { for(int zp = start.getZ(); zp != start.getZ() + z; zp += z / MathHelper.abs(z)) { // don't add the origin block if(xp == origin.getX() && yp == origin.getY() && zp == origin.getZ()) { continue; } if(distance > 0 && MathHelper.abs(xp - origin.getX()) + MathHelper.abs(yp - origin.getY()) + MathHelper.abs( zp - origin.getZ()) > distance) { continue; } BlockPos pos = new BlockPos(xp, yp, zp); if(isToolEffective2(stack, world.getBlockState(pos))) { builder.add(pos); } } } } return builder.build(); } public static void breakExtraBlock(ItemStack stack, World world, EntityPlayer player, BlockPos pos, BlockPos refPos) { // prevent calling that stuff for air blocks, could lead to unexpected behaviour since it fires events if(world.isAirBlock(pos)) { return; } //if(!(player instanceof EntityPlayerMP)) { //return; //} // check if the block can be broken, since extra block breaks shouldn't instantly break stuff like obsidian // or precious ores you can't harvest while mining stone IBlockState state = world.getBlockState(pos); Block block = state.getBlock(); // only effective materials if(!isToolEffective2(stack, state)) { return; } IBlockState refState = world.getBlockState(refPos); float refStrength = ForgeHooks.blockStrength(refState, player, world, refPos); float strength = ForgeHooks.blockStrength(state, player, world, pos); // only harvestable blocks that aren't impossibly slow to harvest if(!ForgeHooks.canHarvestBlock(block, player, world, pos) || refStrength / strength > 10f) { return; } // From this point on it's clear that the player CAN break the block if(player.capabilities.isCreativeMode) { block.onBlockHarvested(world, pos, state, player); if(block.removedByPlayer(state, world, pos, player, false)) { block.onBlockDestroyedByPlayer(world, pos, state); } // send update to client if(!world.isRemote) { TinkerNetwork.sendPacket(player, new SPacketBlockChange(world, pos)); } return; } // callback to the tool the player uses. Called on both sides. This damages the tool n stuff. stack.onBlockDestroyed(world, state, pos, player); // server sided handling if(!world.isRemote) { // send the blockbreak event int xp = ForgeHooks.onBlockBreakEvent(world, ((EntityPlayerMP) player).interactionManager.getGameType(), (EntityPlayerMP) player, pos); if(xp == -1) { return; } // serverside we reproduce ItemInWorldManager.tryHarvestBlock TileEntity tileEntity = world.getTileEntity(pos); // ItemInWorldManager.removeBlock if(block.removedByPlayer(state, world, pos, player, true)) // boolean is if block can be harvested, checked above { block.onBlockDestroyedByPlayer(world, pos, state); block.harvestBlock(world, player, pos, state, tileEntity, stack); block.dropXpOnBlockBreak(world, pos, xp); } // always send block update to client TinkerNetwork.sendPacket(player, new SPacketBlockChange(world, pos)); } // client sided handling else { PlayerControllerMP pcmp = Minecraft.getMinecraft().playerController; // clientside we do a "this clock has been clicked on long enough to be broken" call. This should not send any new packets // the code above, executed on the server, sends a block-updates that give us the correct state of the block we destroy. // following code can be found in PlayerControllerMP.onPlayerDestroyBlock world.playBroadcastSound(2001, pos, Block.getStateId(state)); if(block.removedByPlayer(state, world, pos, player, true)) { block.onBlockDestroyedByPlayer(world, pos, state); } // callback to the tool stack.onBlockDestroyed(world, state, pos, player); if(stack.stackSize == 0 && stack == player.getHeldItemMainhand()) { ForgeEventFactory.onPlayerDestroyItem(player, stack, EnumHand.MAIN_HAND); player.setHeldItem(EnumHand.MAIN_HAND, null); } // send an update to the server, so we get an update back //if(PHConstruct.extraBlockUpdates) Minecraft.getMinecraft().getConnection().sendPacket(new CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, Minecraft .getMinecraft().objectMouseOver.sideHit)); } } public static boolean shearBlock(ItemStack itemstack, World world, EntityPlayer player, BlockPos pos) { // only serverside since it creates entities if(world.isRemote) { return false; } Block block = world.getBlockState(pos).getBlock(); if(block instanceof IShearable) { IShearable target = (IShearable) block; if(target.isShearable(itemstack, world, pos)) { int fortune = EnchantmentHelper.getEnchantmentLevel(Enchantments.FORTUNE, itemstack); List<ItemStack> drops = target.onSheared(itemstack, world, pos, fortune); for(ItemStack stack : drops) { float f = 0.7F; double d = (double) (TConstruct.random.nextFloat() * f) + (double) (1.0F - f) * 0.5D; double d1 = (double) (TConstruct.random.nextFloat() * f) + (double) (1.0F - f) * 0.5D; double d2 = (double) (TConstruct.random.nextFloat() * f) + (double) (1.0F - f) * 0.5D; EntityItem entityitem = new EntityItem(player.getEntityWorld(), (double) pos.getX() + d, (double) pos.getY() + d1, (double) pos.getZ() + d2, stack); entityitem.setDefaultPickupDelay(); world.spawnEntity(entityitem); } itemstack.damageItem(1, player); //player.addStat(net.minecraft.stats.StatList.mineBlockStatArray[Block.getIdFromBlock(block)], 1); world.setBlockToAir(pos); return true; } } return false; } /* Tool Durability */ public static int getCurrentDurability(ItemStack stack) { return stack.getMaxDamage() - stack.getItemDamage(); } public static int getMaxDurability(ItemStack stack) { return stack.getMaxDamage(); } /** Damages the tool. Entity is only needed in case the tool breaks for rendering the break effect. */ public static void damageTool(ItemStack stack, int amount, EntityLivingBase entity) { if(amount == 0 || isBroken(stack)) { return; } int actualAmount = amount; NBTTagList list = TagUtil.getTraitsTagList(stack); for(int i = 0; i < list.tagCount(); i++) { ITrait trait = TinkerRegistry.getTrait(list.getStringTagAt(i)); if(trait != null) { if(amount > 0) { actualAmount = trait.onToolDamage(stack, amount, actualAmount, entity); } else { actualAmount = trait.onToolHeal(stack, amount, actualAmount, entity); } } } // extra compatibility for unbreaking.. because things just love to mess it up.. like 3rd party stuff if(actualAmount > 0 && TagUtil.getTagSafe(stack).getBoolean(ModReinforced.TAG_UNBREAKABLE)) { actualAmount = 0; } // ensure we never deal more damage than durability actualAmount = Math.min(actualAmount, getCurrentDurability(stack)); stack.setItemDamage(stack.getItemDamage() + actualAmount); if(getCurrentDurability(stack) == 0) { breakTool(stack, entity); } } public static void healTool(ItemStack stack, int amount, EntityLivingBase entity) { damageTool(stack, -amount, entity); } public static boolean isBroken(ItemStack stack) { return TagUtil.getToolTag(stack).getBoolean(Tags.BROKEN); } public static void breakTool(ItemStack stack, EntityLivingBase entity) { NBTTagCompound tag = TagUtil.getToolTag(stack); tag.setBoolean(Tags.BROKEN, true); TagUtil.setToolTag(stack, tag); if(entity instanceof EntityPlayerMP) { TinkerNetwork.sendTo(new ToolBreakAnimationPacket(stack), (EntityPlayerMP) entity); } } public static void unbreakTool(ItemStack stack) { if(isBroken(stack)) { // ensure correct damage value stack.setItemDamage(stack.getMaxDamage()); // setItemDamage might break the tool again, so we do this afterwards NBTTagCompound tag = TagUtil.getToolTag(stack); tag.setBoolean(Tags.BROKEN, false); TagUtil.setToolTag(stack, tag); } } public static void repairTool(ItemStack stack, int amount) { // entity is optional, only needed for rendering break effect, never needed when repairing repairTool(stack, amount, null); } public static void repairTool(ItemStack stack, int amount, EntityLivingBase entity) { unbreakTool(stack); TinkerToolEvent.OnRepair.fireEvent(stack, amount); healTool(stack, amount, entity); } /* Dealing tons of damage */ public static boolean attackEntity(ItemStack stack, ToolCore tool, EntityLivingBase attacker, Entity targetEntity) { return attackEntity(stack, tool, attacker, targetEntity, null); } public static boolean attackEntity(ItemStack stack, ToolCore tool, EntityLivingBase attacker, Entity targetEntity, Entity projectileEntity) { return attackEntity(stack, tool, attacker, targetEntity, projectileEntity, true); } /** * Makes all the calls to attack an entity. Takes enchantments and potions and traits into account. Basically call this when a tool deals damage. * Most of this function is the same as {@link EntityPlayer#attackTargetEntityWithCurrentItem(Entity targetEntity)} */ public static boolean attackEntity(ItemStack stack, ToolCore tool, EntityLivingBase attacker, Entity targetEntity, Entity projectileEntity, boolean applyCooldown) { // nothing to do, no target? if(targetEntity == null || !targetEntity.canBeAttackedWithItem() || targetEntity.hitByEntity(attacker) || !stack.hasTagCompound()) { return false; } if(isBroken(stack)) { return false; } if(attacker == null) { return false; } boolean isProjectile = projectileEntity != null; EntityLivingBase target = null; EntityPlayer player = null; if(targetEntity instanceof EntityLivingBase) { target = (EntityLivingBase) targetEntity; } if(attacker instanceof EntityPlayer) { player = (EntityPlayer) attacker; } // traits on the tool List<ITrait> traits = Lists.newLinkedList(); NBTTagList traitsTagList = TagUtil.getTraitsTagList(stack); for(int i = 0; i < traitsTagList.tagCount(); i++) { ITrait trait = TinkerRegistry.getTrait(traitsTagList.getStringTagAt(i)); if(trait != null) { traits.add(trait); } } // players base damage (includes tools damage stat) float baseDamage = (float) attacker.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).getAttributeValue(); // missing because not supported by tcon tools: vanilla damage enchantments, we have our own modifiers // missing because not supported by tcon tools: vanilla knockback enchantments, we have our own modifiers float baseKnockback = attacker.isSprinting() ? 1 : 0; // calculate if it's a critical hit boolean isCritical = attacker.fallDistance > 0.0F && !attacker.onGround && !attacker.isOnLadder() && !attacker.isInWater() && !attacker.isPotionActive(MobEffects.BLINDNESS) && !attacker.isRiding(); for(ITrait trait : traits) { if(trait.isCriticalHit(stack, attacker, target)) { isCritical = true; } } // calculate actual damage float damage = baseDamage; if(target != null) { for(ITrait trait : traits) { damage = trait.damage(stack, attacker, target, baseDamage, damage, isCritical); } } // apply critical damage if(isCritical) { damage *= 1.5f; } // calculate cutoff damage = calcCutoffDamage(damage, tool.damageCutoff()); // calculate actual knockback float knockback = baseKnockback; if(target != null) { for(ITrait trait : traits) { knockback = trait.knockBack(stack, attacker, target, damage, baseKnockback, knockback, isCritical); } } // missing because not supported by tcon tools: vanilla fire aspect enchantments, we have our own modifiers float oldHP = 0; double oldVelX = targetEntity.motionX; double oldVelY = targetEntity.motionY; double oldVelZ = targetEntity.motionZ; if(target != null) { oldHP = target.getHealth(); } // apply cooldown damage decrease if(player != null) { float cooldown = ((EntityPlayer) attacker).getCooledAttackStrength(0.5F); damage *= (0.2F + cooldown * cooldown * 0.8F); } // deal the damage if(target != null) { int hurtResistantTime = target.hurtResistantTime; for(ITrait trait : traits) { trait.onHit(stack, attacker, target, damage, isCritical); // reset hurt reristant time target.hurtResistantTime = hurtResistantTime; } } boolean hit = false; if(isProjectile && tool instanceof IProjectile) { hit = ((IProjectile) tool).dealDamageRanged(stack, projectileEntity, attacker, targetEntity, damage); } else { hit = tool.dealDamage(stack, attacker, targetEntity, damage); } // did we hit? if(hit && target != null) { // actual damage dealt float damageDealt = oldHP - target.getHealth(); // apply knockback modifier oldVelX = target.motionX = oldVelX + (target.motionX - oldVelX) * tool.knockback(); oldVelY = target.motionY = oldVelY + (target.motionY - oldVelY) * tool.knockback() / 3f; oldVelZ = target.motionZ = oldVelZ + (target.motionZ - oldVelZ) * tool.knockback(); // apply knockback if(knockback > 0f) { double velX = -MathHelper.sin(attacker.rotationYaw * (float) Math.PI / 180.0F) * knockback * 0.5F; double velZ = MathHelper.cos(attacker.rotationYaw * (float) Math.PI / 180.0F) * knockback * 0.5F; targetEntity.addVelocity(velX, 0.1d, velZ); // slow down player attacker.motionX *= 0.6f; attacker.motionZ *= 0.6f; attacker.setSprinting(false); } // Send movement changes caused by attacking directly to hit players. // I guess this is to allow better handling at the hit players side? No idea why it resets the motion though. if(targetEntity instanceof EntityPlayerMP && targetEntity.velocityChanged) { TinkerNetwork.sendPacket(player, new SPacketEntityVelocity(targetEntity)); targetEntity.velocityChanged = false; targetEntity.motionX = oldVelX; targetEntity.motionY = oldVelY; targetEntity.motionZ = oldVelZ; } if(player != null) { // vanilla critical callback if(isCritical) { player.onCriticalHit(target); } // "magical" critical damage? (aka caused by modifiers) if(damage > baseDamage) { // this usually only displays some particles :) player.onEnchantmentCritical(targetEntity); } // vanilla achievement support :D if(damage >= 18f) { player.addStat(AchievementList.OVERKILL); } } attacker.setLastAttacker(target); // Damage indicator particles // we don't support vanilla thorns or antispider enchantments //EnchantmentHelper.applyThornEnchantments(target, player); //EnchantmentHelper.applyArthropodEnchantments(player, target); // call post-hit callbacks before reducing the durability for(ITrait trait : traits) { trait.afterHit(stack, attacker, target, damageDealt, isCritical, hit); // hit is always true } // damage the tool if(player != null) { stack.hitEntity(target, player); if(!player.capabilities.isCreativeMode && !isProjectile) { tool.reduceDurabilityOnHit(stack, player, damage); } player.addStat(StatList.DAMAGE_DEALT, Math.round(damageDealt * 10f)); player.addExhaustion(0.3f); if(player.getEntityWorld() instanceof WorldServer && damageDealt > 2f) { int k = (int) (damageDealt * 0.5); ((WorldServer) player.getEntityWorld()).spawnParticle(EnumParticleTypes.DAMAGE_INDICATOR, targetEntity.posX, targetEntity.posY + (double) (targetEntity.height * 0.5F), targetEntity.posZ, k, 0.1D, 0.0D, 0.1D, 0.2D); } // cooldown for non-projectiles if(!isProjectile && applyCooldown) { player.resetCooldown(); } } else if(!isProjectile) { tool.reduceDurabilityOnHit(stack, null, damage); } } return true; } public static float calcCutoffDamage(float damage, float cutoff) { float p = 1f; float d = damage; damage = 0f; while(d > cutoff) { damage += p * cutoff; // safety for ridiculous values if(p > 0.001f) { p *= 0.9f; } else { damage += p * cutoff * ((d / cutoff) - 1f); return damage; } d -= cutoff; } damage += p * d; return damage; } public static float getActualDamage(ItemStack stack, EntityLivingBase player) { float damage = (float) player.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).getAttributeValue(); damage += ToolHelper.getActualAttack(stack); if(stack.getItem() instanceof ToolCore) { damage = ToolHelper.calcCutoffDamage(damage, ((ToolCore) stack.getItem()).damageCutoff()); } return damage; } public static void swingItem(int speed, EntityLivingBase entity) { if(!entity.isSwingInProgress || entity.swingProgressInt >= 3 || entity.swingProgressInt < 0) { entity.swingProgressInt = Math.min(4, -1 + speed); entity.isSwingInProgress = true; if(entity.getEntityWorld() instanceof WorldServer) { ((WorldServer) entity.getEntityWorld()).getEntityTracker().sendToTracking(entity, new SPacketAnimation(entity, 0)); } } } /* Helper Functions */ static int getIntTag(ItemStack stack, String key) { NBTTagCompound tag = TagUtil.getToolTag(stack); return tag.getInteger(key); } static float getfloatTag(ItemStack stack, String key) { NBTTagCompound tag = TagUtil.getToolTag(stack); return tag.getFloat(key); } }