/**
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.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
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.api.damage.DamageUtils;
import zeldaswordskills.client.ZSSKeyHandler;
import zeldaswordskills.entity.DirtyEntityAccessor;
import zeldaswordskills.entity.player.ZSSPlayerInfo;
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.ILockOnTarget;
import zeldaswordskills.skills.SkillActive;
import zeldaswordskills.util.PlayerUtils;
import zeldaswordskills.util.WorldUtils;
/**
*
* ARMOR BREAK
* Description: Unleash a powerful blow that ignores armor
* Activation: Hold attack for (20 - level) ticks
* Effect: Unleashes an attack that inflicts normal weapon damage but ignores armor
* Exhaustion: 2.0F - (0.1F * level)
* Special: May only be used while locked on to a target
* Charge time is reduced by 5 ticks when wielding a Master Sword
*
* Using this skill performs an attack that ignores armor but otherwise deals exactly the
* same damage as a normal attack with the given item would, including all bonuses from other
* skills and enchantments.
*
* Armor Break cannot be activated by normal means. It must be charged by holding the 'attack'
* key, and once the charge reaches full, the player will perform the Armor Break attack.
*
*/
public class ArmorBreak extends SkillActive
{
/** Set to 1 when triggered; set to 0 when target struck in onImpact() */
private int activeTimer = 0;
/** Current charge time */
private int charge = 0;
/**
* Flags whether the vanilla keyBindAttack was used to trigger this skill, in which
* case the keybinding state must be manually set to false once the skill activates;
* this is because the key is still pressed, and vanilla behavior is to attack like
* crazy as long as the key is held, which is not very cool. For custom key bindings
* this is not an issue, as it only results in an attack when the key is first pressed.
*
* Another issue: while mouse state is true, if the cursor moves over a block, the player
* will furiously swing his arm at it, as though trying to break it. Perhaps it is better
* to set the key state to false as before and track 'buttonstate' from within the skill,
* though in that case it needs to listen for key releases as well as presses.
*/
private boolean requiresReset;
public ArmorBreak(String name) {
super(name);
}
private ArmorBreak(ArmorBreak skill) {
super(skill);
}
@Override
public ArmorBreak newInstance() {
return new ArmorBreak(this);
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(List<String> desc, EntityPlayer player) {
desc.add(getChargeDisplay(getChargeTime(player)));
desc.add(getExhaustionDisplay(getExhaustion()));
}
@Override
protected boolean allowUserActivation() {
return false;
}
@Override
public boolean isActive() {
return activeTimer > 0;
}
@Override
protected float getExhaustion() {
return 2.0F - (0.1F * level);
}
/** Returns number of ticks required before attack will execute: 20 - level */
private int getChargeTime(EntityPlayer player) {
return (PlayerUtils.isHoldingMasterSword(player) ? 15 : 20) - level;
}
/** Returns true if the skill is still charging up; always false on the server, as charge is handled client-side */
public boolean isCharging(EntityPlayer player) {
ILockOnTarget target = ZSSPlayerSkills.get(player).getTargetingSkill();
return charge > 0 && target != null && target.isLockedOn();
}
@Override
public boolean canUse(EntityPlayer player) {
return super.canUse(player) && !isActive() && PlayerUtils.isWeapon(player.getHeldItem());
}
/**
* ArmorBreak does not listen for any keys so that there is no chance it is bypassed by
* another skill processing first; instead, keyPressed must be called manually, both
* when the attack key is pressed (and, to handle mouse clicks, when released)
*/
@Override
@SideOnly(Side.CLIENT)
public boolean isKeyListener(Minecraft mc, KeyBinding key) {
return false;
}
/**
* Must be called manually when the attack key is pressed (and, for the mouse, when released);
* this is necessary to allow charging to start from a single key press, when other skills
* might otherwise preclude ArmorBreak's keyPressed from being called.
*/
@Override
@SideOnly(Side.CLIENT)
public boolean keyPressed(Minecraft mc, KeyBinding key, EntityPlayer player) {
requiresReset = (key == mc.gameSettings.keyBindAttack);
if (requiresReset || key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_ATTACK]) {
charge = getChargeTime(player);
if (requiresReset) {
// manually set the keybind state, since it will not be set by the canceled mouse event
// releasing the mouse unsets it normally, but it must be manually unset if the skill is triggered
KeyBinding.setKeyBindState(mc.gameSettings.keyBindAttack.getKeyCode(), true);
}
return true; // doesn't matter, as ArmorBreak is handled outside the normal framework
}
return false;
}
/**
* Returns true if the attack key is still pressed (i.e. ArmorBreak should continue to charge)
*/
@SideOnly(Side.CLIENT)
public boolean isKeyPressed() {
return (ZSSKeyHandler.keys[ZSSKeyHandler.KEY_ATTACK].isKeyDown() || (Config.allowVanillaControls && Minecraft.getMinecraft().gameSettings.keyBindAttack.isKeyDown()));
}
/**
* ArmorBreak's activation was triggered from the client side and it will be over
* on the server by the time the client receives the packet, so don't bother
*/
@Override
protected boolean sendClientUpdate() {
return false;
}
@Override
protected boolean onActivated(World world, EntityPlayer player) {
activeTimer = 1; // needs to be active for hurt event to process correctly
// Attack first so skill still active upon impact, then set timer to zero
ILockOnTarget skill = ZSSPlayerSkills.get(player).getTargetingSkill();
if (skill != null && skill.isLockedOn()) {
player.attackTargetEntityWithCurrentItem(skill.getCurrentTarget());
}
// Armor Break is never added to the list of active skills, since the skill is
// typically on longer active after attacking; luckily, it doesn't need to be
return false;
}
@Override
protected void onDeactivated(World world, EntityPlayer player) {
activeTimer = 0;
charge = 0;
}
@Override
public void onUpdate(EntityPlayer player) {
if (isCharging(player)) {
if (isKeyPressed() && PlayerUtils.isWeapon(player.getHeldItem())) {
if (!player.isSwingInProgress) {
if (charge < (getChargeTime(player) - 1)) {
Minecraft.getMinecraft().playerController.sendUseItem(player, player.worldObj, player.getHeldItem());
}
--charge;
}
// ArmorBreak triggers here, on the client side first, so onActivated need not process on the client
if (charge == 0) {
// can't use the standard animation methods to prevent key/mouse input,
// since Armor Break will not return true for isActive
ZSSPlayerInfo.get(player).setAttackTime(4); // flag for isAnimating? no player parameter; // flag for isAnimating? no player parameter
player.swingItem();
if (requiresReset) { // activated by vanilla attack key: manually unset the key state (fix for mouse event issues)
KeyBinding.setKeyBindState(Minecraft.getMinecraft().gameSettings.keyBindAttack.getKeyCode(), false);
}
SwordBasic skill = (SwordBasic) ZSSPlayerSkills.get(player).getPlayerSkill(swordBasic);
if (skill != null && skill.onAttack(player)) {
PacketDispatcher.sendToServer(new ActivateSkillPacket(this, true));
}
}
} else {
charge = 0;
}
}
if (isActive()) {
activeTimer = 0;
}
}
/**
* WARNING: Something REALLY dirty is about to go down here.
* Uses a custom accessor class planted in net.minecraft.entity package to access
* protected method {@link EntityLivingBase#damageEntity damageEntity}; sets the
* event amount to zero and deactivates the skill to prevent further processing
* during this event cycle; LivingHurtEvent is posted again from damageEntity, at
* which point ArmorBreak will no longer be active and the event may continue as
* normal, but with armor-ignoring damage.
*/
public void onImpact(EntityPlayer player, LivingHurtEvent event) {
activeTimer = 0;
WorldUtils.playSoundAtEntity(player, Sounds.ARMOR_BREAK, 0.4F, 0.5F);
DirtyEntityAccessor.damageEntity(event.entityLiving, DamageUtils.causeArmorBreakDamage(player), event.ammount);
event.ammount = 0.0F;
}
}