/** 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.ArrayList; import java.util.Collection; import java.util.List; import com.google.common.collect.Lists; import mods.battlegear2.api.ISheathed; import mods.battlegear2.api.core.BattlegearUtils; import mods.battlegear2.api.core.InventoryPlayerBattle; import mods.battlegear2.api.shield.IArrowCatcher; import mods.battlegear2.api.shield.IArrowDisplay; import mods.battlegear2.api.shield.IShield; import net.minecraft.client.renderer.ItemMeshDefinition; 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.IProjectile; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntityArrow; import net.minecraft.entity.projectile.EntityFireball; import net.minecraft.item.EnumAction; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.BlockPos; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MathHelper; import net.minecraft.util.StatCollector; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.fml.common.Optional; import net.minecraftforge.fml.common.Optional.Method; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.ZSSAchievements; import zeldaswordskills.ZSSMain; import zeldaswordskills.api.damage.DamageUtils.DamageSourceArmorBreak; import zeldaswordskills.api.damage.IDamageAoE; import zeldaswordskills.api.entity.IReflectable; import zeldaswordskills.api.item.IDashItem; import zeldaswordskills.api.item.IFairyUpgrade; import zeldaswordskills.api.item.ISwingSpeed; import zeldaswordskills.api.item.IUnenchantable; import zeldaswordskills.block.tileentity.TileEntityDungeonCore; import zeldaswordskills.client.ISwapModel; import zeldaswordskills.client.render.item.ModelItemShield; import zeldaswordskills.creativetab.ZSSCreativeTabs; import zeldaswordskills.entity.player.ZSSPlayerInfo; import zeldaswordskills.ref.ModInfo; import zeldaswordskills.ref.Sounds; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.TargetUtils; import zeldaswordskills.util.WorldUtils; @Optional.InterfaceList(value={ @Optional.Interface(iface="mods.battlegear2.api.ISheathed", modid="battlegear2", striprefs=true), @Optional.Interface(iface="mods.battlegear2.api.shield.IArrowCatcher", modid="battlegear2", striprefs=true), @Optional.Interface(iface="mods.battlegear2.api.shield.IArrowDisplay", modid="battlegear2", striprefs=true), @Optional.Interface(iface="mods.battlegear2.api.shield.IShield", modid="battlegear2", striprefs=true) }) public class ItemZeldaShield extends BaseModItem implements IDashItem, IFairyUpgrade, ISwapModel, ISwingSpeed, IUnenchantable, IShield, ISheathed, IArrowCatcher, IArrowDisplay { @SideOnly(Side.CLIENT) private List<ModelResourceLocation> models; /** * Material used in construction determines various properties: * WOOD: Can catch arrows, vulnerable to fire; EMERALD: Reflects projectiles */ protected final ToolMaterial toolMaterial; /** Percent of damage from magical AoE attacks that is blocked, if any */ private final float magicReduction; /** Time for which blocking will be disabled after a successful block */ private final int recoveryTime; /** Rate at which BG2 stamina bar will decay per tick */ private final float bg2DecayRate; /** Rate at which BG2 stamina bar will recover per tick; 0.012F takes 5 seconds */ private final float bg2RecoveryRate; /** * @param material Affects shield's qualities, such as weakness to fire or ability to reflect projectiles * @param magicReduction Percent of damage from magical AoE attacks that is blocked, if any * @param recoveryTime time in ticks it takes to recover from a block when held normally * @param decayRate number of seconds it will take the BG2 stamina bar to deplete * @param recoveryRate number of seconds until BG2 stamina bar will completely replenish */ public ItemZeldaShield(ToolMaterial material, float magicReduction, int recoveryTime, float decayRate, float recoveryRate) { super(); this.toolMaterial = material; this.magicReduction = magicReduction; this.recoveryTime = recoveryTime; this.bg2DecayRate = 1F / decayRate / 20F; this.bg2RecoveryRate = 1F / recoveryRate / 20F; setFull3D(); setMaxDamage(64); setMaxStackSize(1); setCreativeTab(ZSSCreativeTabs.tabCombat); } /** Time for which blocking will be disabled after a successful block */ public int getRecoveryTime() { return recoveryTime; } /** * Returns true if the shield can block this kind of damage */ public boolean canBlockDamage(ItemStack shield, DamageSource source) { boolean flag = source.isUnblockable() && !(source instanceof DamageSourceArmorBreak); if (toolMaterial == ToolMaterial.WOOD) { return !flag; } return !flag || source.isMagicDamage() || source.isFireDamage() || (source.isProjectile() && toolMaterial == ToolMaterial.EMERALD); } /** * Called when the shield blocks an attack when held in the normal fashion (i.e. non-BG2) * used by Deku Shield to damage / destroy the stack and by Mirror Shield to reflect projectiles * @return Return the amount of damage remaining, if any; 0 cancels the hurt event */ public float onBlock(EntityPlayer player, ItemStack shield, DamageSource source, float damage) { ZSSPlayerInfo.get(player).onAttackBlocked(shield, damage); WorldUtils.playSoundAtEntity(player, Sounds.HAMMER, 0.4F, 0.5F); float damageBlocked = damage; if (toolMaterial == ToolMaterial.WOOD) { if (source.isProjectile() && !source.isExplosion() && source.getSourceOfDamage() instanceof IProjectile) { if (ZSSMain.isBG2Enabled && player.getHeldItem() == shield && shield.getItem() instanceof IArrowCatcher){ if (((IArrowCatcher) shield.getItem()).catchArrow(shield, player, (IProjectile) source.getSourceOfDamage())) { ((InventoryPlayerBattle) player.inventory).hasChanged = true; } } } else if (source instanceof IDamageAoE && ((IDamageAoE) source).isAoEDamage()) { damageBlocked *= magicReduction; } int dmg = Math.round(source.isFireDamage() ? damage + 10.0F : damage - 2.0F); if (dmg > 0) { shield.damageItem(dmg, player); if (shield.stackSize <= 0) { ForgeEventFactory.onPlayerDestroyItem(player, shield); if (ZSSMain.isBG2Enabled && BattlegearUtils.isPlayerInBattlemode(player)) { BattlegearUtils.setPlayerOffhandItem(player, null); } else { player.destroyCurrentEquippedItem(); } } } } else if (toolMaterial == ToolMaterial.EMERALD) { if (source.isProjectile() && !source.isExplosion() && source.getSourceOfDamage() != null) { float chance = (source.isMagicDamage() ? (1F / 3F) : 1.0F); if (source.getSourceOfDamage() instanceof IReflectable) { ((IReflectable) source.getSourceOfDamage()).getReflectChance(shield, player, source.getEntity()); } if (player.worldObj.rand.nextFloat() < chance) { Entity projectile = null; try { projectile = source.getSourceOfDamage().getClass().getConstructor(World.class).newInstance(player.worldObj); } catch (Exception e) { ; } if (projectile != null) { NBTTagCompound data = new NBTTagCompound(); source.getSourceOfDamage().writeToNBT(data); projectile.readFromNBT(data); projectile.getEntityData().setBoolean("isReflected", true); projectile.posX -= projectile.motionX; projectile.posY -= projectile.motionY; projectile.posZ -= projectile.motionZ; double motionX = (double)(-MathHelper.sin(player.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(player.rotationPitch / 180.0F * (float) Math.PI)); double motionZ = (double)(MathHelper.cos(player.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(player.rotationPitch / 180.0F * (float) Math.PI)); double motionY = (double)(-MathHelper.sin(player.rotationPitch / 180.0F * (float) Math.PI)); TargetUtils.setEntityHeading(projectile, motionX, motionY, motionZ, 1.0F, 2.0F + (20.0F * player.worldObj.rand.nextFloat()), false); if (projectile instanceof IReflectable) { ((IReflectable) projectile).onReflected(shield, player, source.getEntity(), source.getSourceOfDamage()); } player.worldObj.spawnEntityInWorld(projectile); } } else if (source.isUnblockable() || (source instanceof IDamageAoE && ((IDamageAoE) source).isAoEDamage())) { // failed to reflect projectile damageBlocked *= magicReduction; } } } else if (source.isUnblockable() || (source instanceof IDamageAoE && ((IDamageAoE) source).isAoEDamage())) { damageBlocked *= magicReduction; // default shield behavior blocks half damage from AoE magic attacks } return (damage - damageBlocked); } @Override public float getExhaustion() { return 0.3F; } @Override public int getSwingSpeed() { return 10; // same as BG2 bash speed } @Override public EnumAction getItemUseAction(ItemStack stack) { return EnumAction.BLOCK; } @Override public int getMaxItemUseDuration(ItemStack stack) { return 72000; } @Override public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity target) { if (target instanceof EntityLivingBase) { WorldUtils.playSoundAtEntity(player, Sounds.HAMMER, 0.4F, 0.5F); TargetUtils.knockTargetBack((EntityLivingBase) target, player); } return true; } @Override public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) { if (ZSSPlayerInfo.get(player).canBlock()) { player.setItemInUse(stack, getMaxItemUseDuration(stack)); } return stack; } @Override public void onUsingTick(ItemStack stack, EntityPlayer player, int count) { if (toolMaterial == ToolMaterial.EMERALD) { if (player.getHeldItem() != null && ZSSPlayerInfo.get(player).canBlock()) { Vec3 vec3 = player.getLookVec(); double dx = player.posX + vec3.xCoord * 2.0D; double dy = player.posY + player.getEyeHeight() + vec3.yCoord * 2.0D; double dz = player.posZ + vec3.zCoord * 2.0D; List<EntityFireball> list = player.worldObj.getEntitiesWithinAABB(EntityFireball.class, new AxisAlignedBB(dx - 1, dy - 1, dz - 1, dx + 1, dy + 1, dz + 1)); for (EntityFireball fireball : list) { DamageSource source = DamageSource.causeFireballDamage(fireball, fireball.shootingEntity); if (canBlockDamage(stack, source) && fireball.attackEntityFrom(DamageSource.causePlayerDamage(player), 1.0F)) { fireball.getEntityData().setBoolean("isReflected", true); ZSSPlayerInfo.get(player).onAttackBlocked(stack, 1.0F); WorldUtils.playSoundAtEntity(player, Sounds.HAMMER, 0.4F, 0.5F); break; } } } } } @Override public int getItemEnchantability() { return 0; } @Override public boolean getIsRepairable(ItemStack toRepair, ItemStack stack) { return toRepair.isItemStackDamageable() && stack.isItemEqual(toolMaterial.getRepairItemStack()); } @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")); } @Override public void handleFairyUpgrade(EntityItem item, EntityPlayer player, TileEntityDungeonCore core) { BlockPos pos = core.getPos(); if (PlayerUtils.hasItem(player, ZSSItems.swordMasterTrue)) { item.setDead(); player.triggerAchievement(ZSSAchievements.shieldMirror); WorldUtils.spawnItemWithRandom(core.getWorld(), new ItemStack(ZSSItems.shieldMirror), pos.getX(), pos.getY() + 2, pos.getZ()); core.getWorld().playSoundEffect(pos.getX() + 0.5D, pos.getY() + 1, pos.getZ() + 0.5D, Sounds.SECRET_MEDLEY, 1.0F, 1.0F); } else { core.getWorld().playSoundEffect(pos.getX() + 0.5D, pos.getY() + 1, pos.getZ() + 0.5D, Sounds.FAIRY_LAUGH, 1.0F, 1.0F); PlayerUtils.sendTranslatedChat(player, "chat.zss.fairy.laugh.sword"); } } @Override public boolean hasFairyUpgrade(ItemStack stack) { return this == ZSSItems.shieldHylian; } @Override @SideOnly(Side.CLIENT) public ModelResourceLocation getModel(ItemStack stack, EntityPlayer player, int ticksRemaining) { return (player.isUsingItem() ? models.get(1) : models.get(0)); } @Override public String[] getVariants() { String name = getUnlocalizedName(); name = ModInfo.ID + ":" + name.substring(name.lastIndexOf(".") + 1); return new String[]{name, name + "_using", name + "_back"}; } @Override @SideOnly(Side.CLIENT) public void registerResources() { String[] variants = getVariants(); models = new ArrayList<ModelResourceLocation>(variants.length); for (int i = 0; i < variants.length; ++i) { models.add(new ModelResourceLocation(variants[i], "inventory")); } ModelLoader.registerItemVariants(this, models.toArray(new ModelResourceLocation[0])); ModelLoader.setCustomMeshDefinition(this, new ItemMeshDefinition() { @Override public ModelResourceLocation getModelLocation(ItemStack stack) { return models.get(0); } }); } @Override @SideOnly(Side.CLIENT) public Collection<ModelResourceLocation> getDefaultResources() { String[] variants = getVariants(); // Swap both regular and 'using' models return Lists.newArrayList(new ModelResourceLocation(variants[0], "inventory"), new ModelResourceLocation(variants[1], "inventory")); } @Override @SideOnly(Side.CLIENT) public Class<? extends IBakedModel> getNewModel() { return ModelItemShield.class; } @Method(modid="battlegear2") @Override public void setArrowCount(ItemStack stack, int count) { if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } stack.getTagCompound().setShort("arrows", (short) Math.min(count, Short.MAX_VALUE)); } @Method(modid="battlegear2") @Override public int getArrowCount(ItemStack stack) { return (stack.hasTagCompound() ? stack.getTagCompound().getShort("arrows") : 0); } @Method(modid="battlegear2") @Override public boolean catchArrow(ItemStack shield, EntityPlayer player, IProjectile projectile) { if (toolMaterial == ToolMaterial.WOOD && projectile instanceof EntityArrow){ setArrowCount(shield, getArrowCount(shield) + 1); player.setArrowCountInEntity(player.getArrowCountInEntity() - 1); ((EntityArrow) projectile).setDead(); return true; } return false; } @Method(modid="battlegear2") @Override public boolean sheatheOnBack(ItemStack item) { return true; } @Method(modid="battlegear2") @Override public float getDecayRate(ItemStack shield) { return bg2DecayRate; } @Method(modid="battlegear2") @Override public float getRecoveryRate(ItemStack shield) { return bg2RecoveryRate; } @Method(modid="battlegear2") @Override public boolean canBlock(ItemStack shield, DamageSource source) { return canBlockDamage(shield, source); } @Method(modid="battlegear2") @Override public float getDamageDecayRate(ItemStack shield, float amount) { return 0.0F; // 1F/20F is the default BG2 value } @Method(modid="battlegear2") @Override public float getBlockAngle(ItemStack shield) { return 60; // this is the default BG2 value } @Method(modid="battlegear2") @Override public int getBashTimer(ItemStack shield) { return 10; // this is the default BG2 value } @Method(modid="battlegear2") @Override public void blockAnimation(EntityPlayer player, float amount) {} @Method(modid="battlegear2") @Override public float getDamageReduction(ItemStack shield, DamageSource source) { return 0.0F; } }