/** Copyright (C) <2015> <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.skills.sword; import java.util.List; import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.util.DamageSource; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.client.ZSSKeyHandler; import zeldaswordskills.entity.player.ZSSPlayerSkills; import zeldaswordskills.network.PacketDispatcher; import zeldaswordskills.network.bidirectional.ActivateSkillPacket; import zeldaswordskills.network.client.MortalDrawPacket; import zeldaswordskills.ref.Config; import zeldaswordskills.ref.Sounds; import zeldaswordskills.skills.ILockOnTarget; import zeldaswordskills.skills.SkillActive; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.WorldUtils; /** * * MORTAL DRAW * Activation: While empty-handed and locked on, hold the block key and attack * Effect: The art of drawing the sword, or Battoujutsu, is a risky but deadly move, capable * of inflicting deadly wounds on unsuspecting opponents with a lightning-fast blade strike * Exhaustion: 3.0F - (0.2F * level) * Damage: If successful, inflicts damage + (damage * multiplier) * Duration: Window of attack opportunity is (level + 2) ticks * Notes: * - The first sword found in the action bar will be used for the strike; plan accordingly * - There is a 1.5s cooldown between uses, representing re-sheathing of the sword * */ public class MortalDraw extends SkillActive { /** Delay before skill can be used again */ private static final int DELAY = 30; /** The time remaining during which the skill will succeed; also used as animation flag */ private int attackTimer; /** Nearest sword slot index */ private int swordSlot; /** * The entity that attacked and was successfully drawn against the first time * Used to continue canceling the attack event until mortal draw has finished executing */ private Entity target; public MortalDraw(String name) { super(name); } private MortalDraw(MortalDraw skill) { super(skill); } @Override public MortalDraw newInstance() { return new MortalDraw(this); } @Override @SideOnly(Side.CLIENT) public void addInformation(List<String> desc, EntityPlayer player) { desc.add(getDamageDisplay(getDamageMultiplier(), true) + "%"); desc.add(getTimeLimitDisplay(getAttackTime() - DELAY)); desc.add(getExhaustionDisplay(getExhaustion())); } @Override public boolean canDrop() { return false; } @Override public boolean isLoot() { return false; } @Override public boolean isActive() { // subtract 2 to allow short window in which still considered active so that // the attacker defended against is not able to immediately damage the defender return attackTimer > DELAY - 2; } /** * Animation flag should continue for a few ticks after the skill finishes to * prevent immediate switching of weapons or attacking (it just 'feels' better) */ @Override @SideOnly(Side.CLIENT) public boolean isAnimating() { return attackTimer > (DELAY - 5); } @Override protected float getExhaustion() { return 3.0F - (0.2F * level); } /** * The number of ticks for which this skill is considered 'active', plus the DELAY * Set the attackTimer to this amount upon activation. */ private int getAttackTime() { return level + DELAY + 2; } /** Returns the amount by which damage will be increased, as a percent: [damage + (damage * x)] */ private int getDamageMultiplier() { return 100 + (10 * level); } @Override public boolean canUse(EntityPlayer player) { swordSlot = -1; if (super.canUse(player) && player.getHeldItem() == null && attackTimer == 0) { for (int i = 0; i < 9; ++i) { ItemStack stack = player.inventory.getStackInSlot(i); if (stack != null && PlayerUtils.isSword(stack)) { swordSlot = i; break; } } } return swordSlot > -1; } @Override @SideOnly(Side.CLIENT) public boolean canExecute(EntityPlayer player) { // can't use player.isUsingItem, since hands are empty! return player.getHeldItem() == null && (Minecraft.getMinecraft().gameSettings.keyBindUseItem.isKeyDown() || ZSSKeyHandler.keys[ZSSKeyHandler.KEY_BLOCK].isKeyDown()); } @Override @SideOnly(Side.CLIENT) public boolean isKeyListener(Minecraft mc, KeyBinding key) { return (key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_ATTACK] || (Config.allowVanillaControls && key == mc.gameSettings.keyBindAttack)); } @Override @SideOnly(Side.CLIENT) public boolean keyPressed(Minecraft mc, KeyBinding key, EntityPlayer player) { if (canExecute(player)) { PacketDispatcher.sendToServer(new ActivateSkillPacket(this)); return true; } return false; } @Override protected boolean onActivated(World world, EntityPlayer player) { attackTimer = getAttackTime(); target = null; return isActive(); } @Override protected void onDeactivated(World world, EntityPlayer player) { attackTimer = 0; swordSlot = -1; target = null; } @Override public void onUpdate(EntityPlayer player) { if (attackTimer > 0) { --attackTimer; if (attackTimer == DELAY && !player.worldObj.isRemote) { drawSword(player, null); if (player.getHeldItem() != null) { PacketDispatcher.sendTo(new MortalDrawPacket(), (EntityPlayerMP) player); } } } } @Override public boolean onBeingAttacked(EntityPlayer player, DamageSource source) { if (!player.worldObj.isRemote && source.getEntity() != null) { // Changed isActive to return true for an extra 2 ticks to allow canceling damage if (target == source.getEntity()) { return true; } else if (attackTimer > DELAY) { if (drawSword(player, source.getEntity())) { PacketDispatcher.sendTo(new MortalDrawPacket(), (EntityPlayerMP) player); target = source.getEntity(); return true; } else { // failed - do not continue trying attackTimer = DELAY; target = null; } } } return false; } /** * Call upon landing a mortal draw blow */ public void onImpact(EntityPlayer player, LivingHurtEvent event) { // need to check time again, due to 2-tick delay for damage prevention if (attackTimer > DELAY) { attackTimer = DELAY; event.ammount *= (1.0F + ((float) getDamageMultiplier() / 100F)); WorldUtils.playSoundAtEntity(player, Sounds.MORTAL_DRAW, 0.4F, 0.5F); } else { // too late - didn't defend against this target! target = null; } } /** * Returns true if the player was able to draw a sword * @return true if the skill should be triggered (ignored on client) */ public boolean drawSword(EntityPlayer player, Entity attacker) { boolean flag = false; // letting this run on both sides is fine - client will sync from server later anyway if (swordSlot > -1 && swordSlot != player.inventory.currentItem && player.getHeldItem() == null) { ItemStack sword = player.inventory.getStackInSlot(swordSlot); if (!player.worldObj.isRemote) { player.inventory.setInventorySlotContents(swordSlot, null); } player.setCurrentItemOrArmor(0, sword); // attack will happen before entity#onUpdate refreshes equipment, so apply it now: player.getAttributeMap().applyAttributeModifiers(sword.getAttributeModifiers()); ILockOnTarget skill = ZSSPlayerSkills.get(player).getTargetingSkill(); flag = (skill != null && skill.getCurrentTarget() == attacker); } swordSlot = -1; return flag; } }