/** 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.HashSet; import java.util.List; import java.util.Set; import net.minecraft.block.Block; import net.minecraft.block.material.Material; 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.EnumRarity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.BlockPos; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MathHelper; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.MovingObjectPosition.MovingObjectType; import net.minecraft.util.StatCollector; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.api.damage.DamageUtils.DamageSourceFire; import zeldaswordskills.api.damage.DamageUtils.DamageSourceIce; import zeldaswordskills.api.entity.MagicType; import zeldaswordskills.api.item.IFairyUpgrade; import zeldaswordskills.api.item.ISacredFlame; import zeldaswordskills.api.item.IUnenchantable; import zeldaswordskills.block.BlockSacredFlame; import zeldaswordskills.block.ZSSBlocks; import zeldaswordskills.block.tileentity.TileEntityDungeonCore; import zeldaswordskills.creativetab.ZSSCreativeTabs; import zeldaswordskills.entity.player.ZSSPlayerInfo; import zeldaswordskills.entity.projectile.EntityCyclone; import zeldaswordskills.entity.projectile.EntityMagicSpell; import zeldaswordskills.entity.projectile.EntityMobThrowable; import zeldaswordskills.network.PacketDispatcher; import zeldaswordskills.network.client.PacketISpawnParticles; import zeldaswordskills.ref.Config; import zeldaswordskills.ref.Sounds; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.TargetUtils; import zeldaswordskills.util.WorldUtils; /** * * Magic rods, such as the Fire and Ice Rod * * A variety of magical rods are available throughout Link's adventures. * Each rod has two abilities: the first is a continuous effect activated while the rod is in * use - note that magic is consumed each tick; the second is activated by using the * item while sneaking, shooting a single projectile per use. * * All magic rods can be upgraded by first bathing in the appropriate Sacred Flame, and then * tossing it and enough emeralds into an active fairy pool. * */ public class ItemMagicRod extends BaseModItem implements IFairyUpgrade, ISacredFlame, ISpawnParticles, IUnenchantable { /** The type of magic this rod uses (e.g. FIRE, ICE, etc.) */ private final MagicType magicType; /** The amount of damage inflicted by the rod's projectile spell */ private final float damage; /** Amount of magic consumed when used; magic / 50 is added per 4 ticks of use */ private final float magic_cost; /** * @param magicType The type of magic this rod uses (e.g. FIRE, ICE, etc.) * @param damage The amount of damage inflicted by the rod's projectile spell * @param magic_cost Amount of magic consumed when used; magic / 50 is added per 4 ticks of use */ public ItemMagicRod(MagicType magicType, float damage, float magic_cost) { super(); this.magicType = magicType; this.damage = damage; this.magic_cost = magic_cost; setFull3D(); setMaxDamage(0); setMaxStackSize(1); setCreativeTab(ZSSCreativeTabs.tabTools); } /** * Returns the next time this stack may be used */ private long getNextUseTime(ItemStack stack) { return (stack.hasTagCompound() ? stack.getTagCompound().getLong("next_use") : 0); } /** * Sets the next time this stack may be used to the current world time plus a number of ticks */ private void setNextUseTime(ItemStack stack, World world, int ticks) { if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } stack.getTagCompound().setLong("next_use", (world.getTotalWorldTime() + ticks)); } /** * Returns whether the rod has absorbed its associated sacred flame */ private boolean hasAbsorbedFlame(ItemStack stack) { return (stack.hasTagCompound() && stack.getTagCompound().getBoolean("absorbedFlame")); } /** * Returns true if the rod is upgraded to the 'Nice' version */ private boolean isUpgraded(ItemStack stack) { return (stack.hasTagCompound() && stack.getTagCompound().getBoolean("isUpgraded")); } @Override public String getItemStackDisplayName(ItemStack stack) { String s = (isUpgraded(stack) ? StatCollector.translateToLocal("item.rodmagic.nice") + " " : ""); return s + StatCollector.translateToLocal(getUnlocalizedName() + ".name"); } @Override public int getItemEnchantability() { return 0; } @Override public int getMaxItemUseDuration(ItemStack stack) { return 72000; } @Override public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) { float mp = (player.isSneaking() ? magic_cost : magic_cost / 20.0F); boolean isUpgraded = isUpgraded(stack); if (isUpgraded) { mp *= 1.5F; } if (player.capabilities.isCreativeMode || (world.getTotalWorldTime() > getNextUseTime(stack) && ZSSPlayerInfo.get(player).useMagic(mp))) { if (player.isSneaking()) { EntityMobThrowable magic; if (magicType == MagicType.WIND) { magic = new EntityCyclone(world, player).setArea(isUpgraded(stack) ? 3.0F : 2.0F); } else { magic = new EntityMagicSpell(world, player).setType(magicType).setArea(isUpgraded ? 3.0F : 2.0F); if (magicType == MagicType.FIRE && !Config.getRodFireGriefing()) { ((EntityMagicSpell) magic).disableGriefing(); } } magic.setDamage(isUpgraded ? damage * 1.5F : damage); if (!world.isRemote) { WorldUtils.playSoundAtEntity(player, Sounds.WHOOSH, 0.4F, 0.5F); world.spawnEntityInWorld(magic); } setNextUseTime(stack, world, 10); // prevents use during swing animation player.swingItem(); } else { player.setItemInUse(stack, getMaxItemUseDuration(stack)); if (this == ZSSItems.rodTornado) { ZSSPlayerInfo.get(player).reduceFallAmount += (isUpgraded(stack) ? 8.0F : 4.0F); } } } else { // TODO need better magic fail sound... player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F); } return stack; } @Override public void onUsingTick(ItemStack stack, EntityPlayer player, int count) { if (this == ZSSItems.rodTornado) { player.fallDistance = 0.0F; } if (!player.worldObj.isRemote) { int ticksInUse = getMaxItemUseDuration(stack) - count; float mp = (magic_cost / 50.0F) * (isUpgraded(stack) ? 1.5F : 1.0F); if (ticksInUse % 4 == 0 && !ZSSPlayerInfo.get(player).consumeMagic(mp)) { player.clearItemInUse(); } else if (this == ZSSItems.rodTornado) { if (ticksInUse % 10 == 0) { player.worldObj.spawnEntityInWorld(new EntityCyclone(player.worldObj, player.posX, player.posY, player.posZ).disableGriefing()); } } else { handleUpdateTick(stack, player.worldObj, player, ticksInUse); } } } /** * Handles fire and ice rod update tick */ private void handleUpdateTick(ItemStack stack, World world, EntityPlayer player, int ticksInUse) { float r = 0.5F + Math.min(5.5F, (ticksInUse / 10F)); if (isUpgraded(stack)) { r *= 1.5F; } PacketDispatcher.sendToAllAround(new PacketISpawnParticles(player, r), player, 64.0D); if (ticksInUse % 4 == 3) { if (magicType != MagicType.FIRE || Config.getRodFireGriefing()) { affectBlocks(world, player, r); } List<EntityLivingBase> targets = TargetUtils.acquireAllLookTargets(player, Math.round(r), 1.0F); for (EntityLivingBase target : targets) { target.attackEntityFrom(getDamageSource(player), r); if (magicType == MagicType.FIRE && !target.isImmuneToFire()) { target.setFire(5); } } } if (ticksInUse % magicType.getSoundFrequency() == 0) { world.playSoundAtEntity(player, magicType.getMovingSound(), magicType.getSoundVolume(world.rand), magicType.getSoundPitch(world.rand)); } } /** * Affects blocks within the area of effect provided there is line of sight to the player */ private void affectBlocks(World world, EntityPlayer player, float radius) { Set<BlockPos> affectedBlocks = new HashSet<BlockPos>(); Vec3 vec3 = player.getLookVec(); double x = player.posX + vec3.xCoord; double y = player.posY + player.getEyeHeight() + vec3.yCoord; double z = player.posZ + vec3.zCoord; int r = MathHelper.ceiling_float_int(radius); for (int n = 0; n < r; ++n) { int i = MathHelper.floor_double(x); int j = MathHelper.floor_double(y); int k = MathHelper.floor_double(z); if (canAddBlockPosition(world, player, i, j, k)) { affectedBlocks.add(new BlockPos(i, j, k)); } x += vec3.xCoord; y += vec3.yCoord; z += vec3.zCoord; } affectAllBlocks(world, affectedBlocks, magicType); } private boolean canAddBlockPosition(World world, EntityPlayer player, int i, int j, int k) { Vec3 vec31 = new Vec3(player.posX, player.posY + player.getEyeHeight(), player.posZ); Vec3 vec32 = new Vec3(i, j, k); MovingObjectPosition mop = world.rayTraceBlocks(vec31, vec32); return (mop == null || (mop.typeOfHit == MovingObjectType.BLOCK && (mop.getBlockPos().getX() == i && mop.getBlockPos().getY() == j && mop.getBlockPos().getZ() == k) || !world.getBlockState(mop.getBlockPos()).getBlock().getMaterial().blocksLight())); } /** * Affects all blocks in the set of chunk positions with the magic type's effect (freeze, thaw, etc.) */ public static void affectAllBlocks(World world, Set<BlockPos> blocks, MagicType type) { for (BlockPos pos : blocks) { Block block = world.getBlockState(pos).getBlock(); switch(type) { case FIRE: if (WorldUtils.canMeltBlock(world, block, pos) && world.rand.nextInt(4) == 0) { world.setBlockToAir(pos); world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } else if (block.getMaterial() == Material.air && world.getBlockState(pos.down()).getBlock().isFullBlock() && world.rand.nextInt(8) == 0) { world.setBlockState(pos, Blocks.fire.getDefaultState()); world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_IGNITE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } else if (block == ZSSBlocks.bombFlower) { block.onBlockExploded(world, pos, null); } break; case ICE: if (block.getMaterial() == Material.water && world.rand.nextInt(2) == 0) { world.setBlockState(pos, Blocks.ice.getDefaultState()); world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.GLASS_BREAK, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } else if (block.getMaterial() == Material.lava && world.rand.nextInt(4) == 0) { Block solid = (block == Blocks.lava ? Blocks.obsidian : Blocks.cobblestone); world.setBlockState(pos, solid.getDefaultState()); world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } else if (block.getMaterial() == Material.fire) { world.setBlockToAir(pos); world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } break; default: } } } /** Only used for Fire and Ice Rods */ private DamageSource getDamageSource(EntityPlayer player) { switch(magicType) { // causing AoE damage case ICE: return new DamageSourceIce("blast.ice", player, 60, 1, true); default: return new DamageSourceFire("blast.fire", player, true); } } @Override @SideOnly(Side.CLIENT) public void spawnParticles(World world, EntityPlayer player, ItemStack stack, double x, double y, double z, float r) { y += player.getEyeHeight(); Vec3 look = player.getLookVec(); for (float f = 0; f < r; f += 0.5F) { x += look.xCoord; y += look.yCoord; z += look.zCoord; for (int i = 0; i < 4; ++i) { world.spawnParticle(magicType.getTrailingParticle(), x + 0.5F - world.rand.nextFloat(), y + 0.5F - world.rand.nextFloat(), z + 0.5F - world.rand.nextFloat(), look.xCoord * (0.5F * world.rand.nextFloat()), look.yCoord * 0.15D, look.zCoord * (0.5F * world.rand.nextFloat())); } } } @Override @SideOnly(Side.CLIENT) public EnumRarity getRarity(ItemStack stack) { return isUpgraded(stack) ? EnumRarity.RARE : EnumRarity.COMMON; } @Override @SideOnly(Side.CLIENT) public boolean hasEffect(ItemStack stack) { return true; } @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack stack, EntityPlayer player, List<String> list, boolean advanced) { list.add(EnumChatFormatting.ITALIC + StatCollector.translateToLocal("tooltip." + getUnlocalizedName().substring(5) + ".desc.0")); list.add(EnumChatFormatting.ITALIC + StatCollector.translateToLocal("tooltip." + getUnlocalizedName().substring(5) + ".desc.1")); list.add(""); list.add(EnumChatFormatting.RED + StatCollector.translateToLocalFormatted("tooltip.zss.damage", "", String.format("%.1f", isUpgraded(stack) ? damage * 1.5F : damage))); if (this != ZSSItems.rodTornado) { list.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocalFormatted("tooltip.zss.area", String.format("%.1f", isUpgraded(stack) ? 3.0F : 2.0F))); } } @Override public boolean onActivatedSacredFlame(ItemStack stack, World world, EntityPlayer player, BlockSacredFlame.EnumType flame, boolean isActive) { return false; } @Override public boolean onClickedSacredFlame(ItemStack stack, World world, EntityPlayer player, BlockSacredFlame.EnumType flame, boolean isActive) { if (!world.isRemote) { if (hasAbsorbedFlame(stack)) { PlayerUtils.sendTranslatedChat(player, "chat.zss.sacred_flame.old.any", new ChatComponentTranslation(stack.getUnlocalizedName() + ".name")); } else if (isActive) { boolean canAbsorb = false; switch(magicType) { case FIRE: canAbsorb = (flame == BlockSacredFlame.EnumType.DIN); break; case ICE: canAbsorb = (flame == BlockSacredFlame.EnumType.NAYRU); break; case WIND: canAbsorb = (flame == BlockSacredFlame.EnumType.FARORE); break; default: break; } if (canAbsorb) { if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } stack.getTagCompound().setBoolean("absorbedFlame", true); world.playSoundAtEntity(player, Sounds.FLAME_ABSORB, 1.0F, 1.0F); PlayerUtils.sendTranslatedChat(player, "chat.zss.sacred_flame.new", new ChatComponentTranslation(stack.getUnlocalizedName() + ".name"), new ChatComponentTranslation("tile.zss.sacred_flame." + flame.getName() + ".name")); return true; } else { PlayerUtils.sendTranslatedChat(player, "chat.zss.sacred_flame.random"); } } else { PlayerUtils.sendTranslatedChat(player, "chat.zss.sacred_flame.inactive"); } WorldUtils.playSoundAtEntity(player, Sounds.SWORD_MISS, 0.4F, 0.5F); } return false; } @Override public void handleFairyUpgrade(EntityItem item, EntityPlayer player, TileEntityDungeonCore core) { int cost = Math.round(Config.getRodUpgradeCost() * (magicType == MagicType.WIND ? 0.75F : 1.0F)); if (core.consumeRupees(cost)) { if (!item.getEntityItem().hasTagCompound()) { item.getEntityItem().setTagCompound(new NBTTagCompound()); } item.getEntityItem().getTagCompound().setBoolean("isUpgraded", true); core.getWorld().playSoundEffect(core.getPos().getX() + 0.5D, core.getPos().getY() + 1, core.getPos().getZ() + 0.5D, Sounds.SECRET_MEDLEY, 1.0F, 1.0F); } else { core.getWorld().playSoundEffect(core.getPos().getX() + 0.5D, core.getPos().getY() + 1, core.getPos().getZ() + 0.5D, Sounds.FAIRY_LAUGH, 1.0F, 1.0F); PlayerUtils.sendTranslatedChat(player, "chat.zss.fairy.laugh.unworthy"); } } @Override public boolean hasFairyUpgrade(ItemStack stack) { return !isUpgraded(stack) && hasAbsorbedFlame(stack); } }