/** 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.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zeldaswordskills.client.ZSSClientEvents; import zeldaswordskills.client.ZSSKeyHandler; import zeldaswordskills.entity.DirtyEntityAccessor; import zeldaswordskills.entity.ZSSEntityInfo; import zeldaswordskills.entity.buff.Buff; import zeldaswordskills.entity.player.ZSSPlayerSkills; import zeldaswordskills.network.PacketDispatcher; import zeldaswordskills.network.bidirectional.ActivateSkillPacket; import zeldaswordskills.ref.Config; import zeldaswordskills.ref.Sounds; import zeldaswordskills.skills.ICombo; import zeldaswordskills.skills.ILockOnTarget; import zeldaswordskills.skills.SkillActive; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.WorldUtils; /** * * ENDING BLOW * Description: Finish off an enemy made vulnerable by your flurry of blows * Activation: Forward, forward, and attack during combo * Effect: Build up combo momentum and then finish off your enemy with a decisive strike, * gaining bonus xp if successful or becoming flat-footed if not * Damage: +(level * 20) percent * Duration of vulnerability: 110 - (level * 10) ticks * Exhaustion: 2.0F - (level * 0.1F) * XP Bonus: level + (value between 1 and the opponent's last remaining health) * Special: * - May only be used after two or more consecutive strikes on the same target * - Slaying an opponent with this move grants additional experience * - Failure to slay the target results in a -50% defense penalty for the duration * */ public class EndingBlow extends SkillActive { /** Flag for isActive() so that skill can trigger upon impact from LivingHurtEvent */ private int activeTimer = 0; /** Only for vanilla activation: Current number of ticks remaining before skill will not activate */ @SideOnly(Side.CLIENT) private int ticksTilFail; /** Number of times the forward key has been pressed this activation cycle */ @SideOnly(Side.CLIENT) private int keyPressed; /** Number of consecutive hits the combo had when the skill was last used */ private int lastNumHits; /** Workaround for armor / potions changing damage: checks next tick if entity is dead or not */ private EntityLivingBase entityHit; /** Xp amount to grant if entityHit is dead on update tick */ private int xp; public EndingBlow(String name) { super(name); } private EndingBlow(EndingBlow skill) { super(skill); } @Override public EndingBlow newInstance() { return new EndingBlow(this); } @Override @SideOnly(Side.CLIENT) public void addInformation(List<String> desc, EntityPlayer player) { desc.add(getDamageDisplay(level * 20, true) + "%"); desc.add(getDurationDisplay(getDuration(), true)); desc.add(getExhaustionDisplay(getExhaustion())); } @Override public boolean isActive() { return activeTimer > 0; } @Override protected float getExhaustion() { return 2.0F - (level * 0.1F); } /** Returns the duration of the defense down effect */ public int getDuration() { return 110 - (level * 10); } @Override public boolean canUse(EntityPlayer player) { if (!isActive() && super.canUse(player) && PlayerUtils.isWeapon(player.getHeldItem())) { ICombo skill = ZSSPlayerSkills.get(player).getComboSkill(); if (skill != null && skill.isComboInProgress()) { if (lastNumHits > 0) { return skill.getCombo().getConsecutiveHits() > 1 && skill.getCombo().getNumHits() > lastNumHits + 2; } else { return skill.getCombo().getConsecutiveHits() > 1; } } } return false; } @Override @SideOnly(Side.CLIENT) public boolean canExecute(EntityPlayer player) { return ticksTilFail > 0 && keyPressed > 1 && canUse(player); } @Override @SideOnly(Side.CLIENT) public boolean isKeyListener(Minecraft mc, KeyBinding key) { return (key == mc.gameSettings.keyBindForward || key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_ATTACK] || (Config.allowVanillaControls && key == mc.gameSettings.keyBindAttack)); } /** * Increments the number of times the key has been pressed and starts the fail timer if not yet set, * or triggers the skill if the right conditions are met */ @Override @SideOnly(Side.CLIENT) public boolean keyPressed(Minecraft mc, KeyBinding key, EntityPlayer player) { if (key == mc.gameSettings.keyBindForward) { if (ticksTilFail == 0) { ticksTilFail = 6; } ++keyPressed; } else if (canExecute(player)) { ticksTilFail = 0; keyPressed = 0; PacketDispatcher.sendToServer(new ActivateSkillPacket(this)); return true; } return false; } @Override protected boolean onActivated(World world, EntityPlayer player) { activeTimer = 3; // gives server some time for client attack to occur ICombo skill = ZSSPlayerSkills.get(player).getComboSkill(); if (skill.getCombo() != null) { lastNumHits = skill.getCombo().getNumHits(); } if (world.isRemote) { // only attack after server has been activated, i.e. client receives activation packet back ZSSClientEvents.performComboAttack(Minecraft.getMinecraft(), ZSSPlayerSkills.get(player).getTargetingSkill()); ticksTilFail = 0; keyPressed = 0; } return isActive(); } @Override protected void onDeactivated(World world, EntityPlayer player) { activeTimer = 0; entityHit = null; xp = 0; if (world.isRemote) { keyPressed = 0; ticksTilFail = 0; } } @Override public void onUpdate(EntityPlayer player) { if (player.worldObj.isRemote && ticksTilFail > 0) { --ticksTilFail; if (ticksTilFail == 0) { keyPressed = 0; } } if (lastNumHits > 0) { if (entityHit != null && xp > 0) { updateEntityState(player); } ICombo skill = ZSSPlayerSkills.get(player).getComboSkill(); if (skill == null || !skill.isComboInProgress()) { lastNumHits = 0; } } if (isActive()) { --activeTimer; if (activeTimer == 0 && !player.worldObj.isRemote) { WorldUtils.playSoundAtEntity(player, Sounds.GRUNT, 0.3F, 0.8F); ZSSEntityInfo.get(player).applyBuff(Buff.DEFENSE_DOWN, getDuration() * 2, 50); } } } /** * Checks if entity hit is dead, granting Xp or causing defensive penalty */ private void updateEntityState(EntityPlayer player) { if (!player.worldObj.isRemote) { if (entityHit.getHealth() <= 0.0F) { if (entityHit instanceof EntityLiving) { DirtyEntityAccessor.setLivingXp((EntityLiving) entityHit, xp, true); } else { WorldUtils.spawnXPOrbsWithRandom(player.worldObj, player.worldObj.rand, MathHelper.floor_double(entityHit.posX), MathHelper.floor_double(entityHit.posY), MathHelper.floor_double(entityHit.posZ), xp); } } else { WorldUtils.playSoundAtEntity(player, Sounds.GRUNT, 0.3F, 0.8F); ZSSEntityInfo.get(player).applyBuff(Buff.DEFENSE_DOWN, getDuration(), 50); } } entityHit = null; xp = 0; } @Override public float postImpact(EntityPlayer player, EntityLivingBase entity, float amount) { activeTimer = 0; ICombo combo = ZSSPlayerSkills.get(player).getComboSkill(); ILockOnTarget lock = ZSSPlayerSkills.get(player).getTargetingSkill(); if (combo != null && combo.isComboInProgress() && lock != null && lock.getCurrentTarget() == combo.getCombo().getLastEntityHit()) { amount *= 1.0F + (level * 0.2F); WorldUtils.playSoundAtEntity(player, Sounds.MORTAL_DRAW, 0.4F, 0.5F); entityHit = entity; xp = level + 1 + player.worldObj.rand.nextInt(Math.max(2, MathHelper.ceiling_float_int(entity.getHealth()))); } return amount; } }