@Override
 public boolean generate(World world, Random rand, int i, int j, int k) {
   generate2(
       world,
       rand,
       i,
       j,
       k,
       (Config.getJarsPerCluster() - rand.nextInt(Config.getJarsPerCluster())),
       false);
   return true;
 }
 private boolean canPlaceBlockAt(World world, int i, int j, int k, boolean isUnderground) {
   return world.isAirBlock(i, j, k)
       || (!isUnderground
           && Config.genJarsInWater()
           && world.getBlock(i, j, k).getMaterial() == Material.water
           && !world.canBlockFreeze(i, j, k, false));
 }
 /** Whether the block at x/y/z can be melted by any of the various fire effects */
 public static boolean canMeltBlock(World world, Block block, int x, int y, int z) {
   int meta = world.getBlockMetadata(x, y, z);
   boolean flag = Config.enableFireArrowMelt() ? (meta & ~8) == 5 : meta == 5;
   return (block.getMaterial() == Material.ice
       || block.getMaterial() == Material.packedIce
       || block.getMaterial() == Material.snow
       || block.getMaterial() == Material.craftedSnow
       || (block == ZSSBlocks.secretStone && flag));
 }
 /**
  * Attempts to generate a single jar cluster
  *
  * @param jarsPerCluster max number of jars to generate in this cluster
  */
 public void doJarGen(
     World world, Random rand, int chunkX, int chunkZ, int jarsPerCluster, boolean isUnderground) {
   int i = chunkX + rand.nextInt(16) + 8;
   int k = chunkZ + rand.nextInt(16) + 8;
   int j = (world.provider.isHellWorld ? rand.nextInt(128) : world.getHeightValue(i, k) + 1);
   int n = jarsPerCluster - rand.nextInt(jarsPerCluster);
   if (Config.genJarsInWater() && !isUnderground) {
     while (j > 0 && world.getBlock(i, j, k).getMaterial() == Material.water) {
       --j;
     }
   }
   generate2(world, rand, i, j, k, n, isUnderground);
 }
 @SubscribeEvent
 public void onEntityJoinWorld(EntityJoinWorldEvent event) {
   if (!event.entity.worldObj.isRemote) {
     if (event.entity instanceof EntityPlayer) {
       ZSSEntityInfo.get((EntityPlayer) event.entity).onJoinWorld();
       ZSSPlayerInfo.get((EntityPlayer) event.entity).onJoinWorld();
     } else if (event.entity.getClass() == EntityVillager.class) {
       EntityGoron.doVillagerSpawn((EntityVillager) event.entity, event.entity.worldObj);
     }
     if (!Config.areVanillaBuffsDisabled() && event.entity instanceof EntityLivingBase) {
       initBuffs((EntityLivingBase) event.entity);
     }
   }
 }
 /** Returns true if the whip can destroy the material type */
 protected boolean canBreakBlock(Block block, Material m, int x, int y, int z, int side) {
   EntityLivingBase thrower = getThrower();
   if (block instanceof IWhipBlock) {
     return ((IWhipBlock) block).canBreakBlock(getType(), thrower, worldObj, x, y, z, side);
   }
   boolean isBreakable = block.getBlockHardness(worldObj, x, y, z) >= 0.0F;
   boolean canPlayerEdit = false;
   if (thrower instanceof EntityPlayer) {
     canPlayerEdit =
         ((EntityPlayer) thrower).capabilities.allowEdit && Config.canHookshotBreakBlocks();
   }
   // can dislodge blocks such as torches, leaves, flowers, etc.
   return (isBreakable
       && canPlayerEdit
       && (block instanceof BlockTorch || m == Material.leaves || m == Material.plants));
 }
  private boolean lootTarget(EntityPlayer player, EntityLivingBase target) {
    if (target.getEntityData().getBoolean("LootableEntityFlag")) {
      return false;
    }
    IEntityLootable lootable =
        (target instanceof IEntityLootable ? (IEntityLootable) target : null);
    float lootChance =
        (lootable != null
            ? lootable.getLootableChance(player, getType())
            : LootableEntityRegistry.getEntityLootChance(target.getClass()));
    lootChance *= Config.getWhipLootMultiplier();
    boolean wasItemStolen = false;
    if (rand.nextFloat() < lootChance) {
      ItemStack loot =
          (lootable != null
              ? lootable.getEntityLoot(player, getType())
              : LootableEntityRegistry.getEntityLoot(target.getClass()));
      // TODO remove the following if Skulltulas are added:
      if (target instanceof EntitySpider && rand.nextInt(25) == 0) {
        loot = new ItemStack(ZSSItems.skulltulaToken);
      }
      if (loot != null) {
        EntityItem item = new EntityItem(worldObj, posX, posY + 1, posZ, loot);
        double dx = player.posX - posX;
        double dy = player.posY - posY;
        double dz = player.posZ - posZ;
        TargetUtils.setEntityHeading(item, dx, dy, dz, 1.0F, 1.0F, true);
        if (!worldObj.isRemote) {
          worldObj.spawnEntityInWorld(item);
        }
        player.triggerAchievement(ZSSAchievements.orcaThief);
        wasItemStolen = true;
      }
    }

    if (lootable == null || lootable.onLootStolen(player, wasItemStolen)) {
      if (!worldObj.isRemote) {
        target.getEntityData().setBoolean("LootableEntityFlag", true);
      }
    }
    return wasItemStolen;
  }
 @Override
 protected void onImpact(MovingObjectPosition mop) {
   if (mop.typeOfHit == MovingObjectType.BLOCK) {
     Block block = worldObj.getBlock(mop.blockX, mop.blockY, mop.blockZ);
     if (!isInGround() && ticksExisted < getMaxDistance()) {
       WorldUtils.playSoundAtEntity(this, Sounds.WHIP_CRACK, 1.0F, 0.2F);
       motionX = motionY = motionZ = 0.0D;
       if (canGrabBlock(block, mop.blockX, mop.blockY, mop.blockZ, mop.sideHit)) {
         setInGround(true);
         AxisAlignedBB box =
             block.getCollisionBoundingBoxFromPool(worldObj, mop.blockX, mop.blockY, mop.blockZ);
         // bounding box may be null, depending on the block
         if (box != null) {
           posX = box.minX + ((box.maxX - box.minX) / 2.0D);
           posY = box.minY + ((box.maxY - box.minY) / 2.0D);
           posZ = box.minZ + ((box.maxZ - box.minZ) / 2.0D);
           switch (mop.sideHit) {
             case 5:
               posX = box.maxX;
               break; // EAST
             case 4:
               posX = box.minX - 0.015D;
               break; // WEST (a little extra to compensate for block border, otherwise renders
                      // black)
             case 3:
               posZ = box.maxZ;
               break; // SOUTH
             case 2:
               posZ = box.minZ - 0.015D;
               break; // NORTH (a little extra to compensate for block border, otherwise renders
                      // black)
             case SideHit.TOP:
               posY = box.maxY;
               break;
             case SideHit.BOTTOM:
               posY = box.minY - 0.015D;
               break;
           }
         } else {
           // adjusting posX/Y/Z here seems to make no difference to the rendering, even when
           // client side makes same changes
           posX = (double) mop.blockX + 0.5D;
           posY = (double) mop.blockY + 0.5D;
           posZ = (double) mop.blockZ + 0.5D;
           // side hit documentation is wrong!!! based on printing out mop.sideHit:
           // 2 = NORTH (face of block), 3 = SOUTH, 4 = WEST, 5 = EAST, 0 = BOTTOM, 1 = TOP
           switch (mop.sideHit) {
               // case 5: posX += 0.5D; break; // EAST
               // case 4: posX -= 0.515D; break; // WEST (a little extra to compensate for block
               // border, otherwise renders black)
               // case 3: posZ += 0.5D; break; // SOUTH
               // case 2: posZ -= 0.515D; break; // NORTH (a little extra to compensate for block
               // border, otherwise renders black)
             case SideHit.TOP:
               posY = mop.blockY + 1.0D;
               break;
             case SideHit.BOTTOM:
               posY = mop.blockY - 0.015D;
               break;
           }
         }
         // however, setting position as watched values and using these on the client works...
         // weird
         dataWatcher.updateObject(HIT_POS_X, (float) posX);
         dataWatcher.updateObject(HIT_POS_Y, (float) posY);
         dataWatcher.updateObject(HIT_POS_Z, (float) posZ);
         // unfortunately, this means the datawatcher values are no longer usable for getting the
         // block,
         // so need to store hitX/Y/Z separately for updating
         hitX = mop.blockX;
         hitY = mop.blockY;
         hitZ = mop.blockZ;
       } else if (canBreakBlock(
           block, block.getMaterial(), mop.blockX, mop.blockY, mop.blockZ, mop.sideHit)) {
         if (!worldObj.isRemote) {
           // don't drop items for players in creative mode
           boolean drop =
               (getThrower() instanceof EntityPlayer
                   ? !(((EntityPlayer) getThrower()).capabilities.isCreativeMode)
                   : true);
           worldObj.func_147480_a(mop.blockX, mop.blockY, mop.blockZ, drop);
           setDead();
         }
       } else if (block.getMaterial().blocksMovement()) {
         // Only call onEntityCollidedWithBlock if the whip didn't already grab or break the block
         block.onEntityCollidedWithBlock(worldObj, mop.blockX, mop.blockY, mop.blockZ, this);
       } else {
         block.onEntityCollidedWithBlock(worldObj, mop.blockX, mop.blockY, mop.blockZ, this);
         return;
       }
     }
   } else if (mop.entityHit != null) {
     worldObj.playSoundAtEntity(mop.entityHit, Sounds.WHIP_CRACK, 1.0F, 1.0F);
     boolean inflictDamage = true; // set to false if held item disarmed
     if (mop.entityHit instanceof EntityLivingBase) {
       EntityLivingBase target = (EntityLivingBase) mop.entityHit;
       if (getThrower() instanceof EntityPlayer) {
         EntityPlayer player = (EntityPlayer) getThrower();
         if (lootTarget(player, target)) {
           inflictDamage =
               (target instanceof IEntityLootable
                   ? ((IEntityLootable) target).isHurtOnTheft(player, getType())
                   : Config.getHurtOnSteal());
         } else if (target.getHeldItem() != null
             && ZSSPlayerSkills.get(player).hasSkill(SkillBase.parry)) {
           float chance = Parry.getDisarmModifier(player, target);
           float yaw = (target.rotationYaw - player.rotationYaw);
           while (yaw >= 360.0F) {
             yaw -= 360.0F;
           }
           while (yaw < 0.0F) {
             yaw += 360.0F;
           }
           yaw = Math.abs(Math.abs(yaw) - 180.0F);
           // should be impossible to disarm from more than 90 degrees to either side
           // however, rotationYaw does not seem to be the most reliable, but it's close enough
           float mod = 0.5F - (0.25F * (yaw / 45.0F));
           chance = 0.05F + (chance * mod); // max chance is 0.3F, min is 0.05F
           if (worldObj.rand.nextFloat() < chance) {
             WorldUtils.dropHeldItem(target);
             inflictDamage = false;
           }
         }
       }
       if (inflictDamage && target.getEquipmentInSlot(ArmorIndex.EQUIPPED_CHEST) != null) {
         inflictDamage = false; // cannot damage armor-wearing opponents
       }
     }
     if (inflictDamage) {
       mop.entityHit.attackEntityFrom(getDamageSource(), getDamage());
     }
     setDead();
   }
 }
 public float getMaxDistance() {
   return (float) Config.getWhipRange() * (getType().isExtended() ? 1.5F : 1.0F);
 }