package com.hearthsim.card.minion; import com.hearthsim.card.CharacterIndex; import com.hearthsim.card.weapon.WeaponCard; import com.hearthsim.event.deathrattle.DeathrattleAction; import com.hearthsim.exception.HSException; import com.hearthsim.model.BoardModel; import com.hearthsim.model.PlayerModel; import com.hearthsim.model.PlayerSide; import com.hearthsim.util.HearthAction; import com.hearthsim.util.HearthAction.Verb; import com.hearthsim.util.factory.BoardStateFactoryBase; import com.hearthsim.util.tree.HearthTreeNode; import org.json.JSONObject; public abstract class Hero extends Minion implements MinionSummonedInterface { protected static final byte HERO_ABILITY_COST = 2; // Assumed to be 2 for all heroes private WeaponCard weapon; private byte armor_; public Hero() { super(); } @Deprecated public byte getWeaponCharge() { if (weapon == null) { return 0; } else { return weapon.getWeaponCharge(); } } public void addArmor(byte armor) { armor_ += armor; } public byte getArmor() { return armor_; } public void setArmor(byte armor) { armor_ = armor; } /** * * @return the class name of the hero, e.g. Hunter */ public String getHeroClass() { return this.getClass().getSimpleName(); } @Override public boolean isHero() { return true; } @Override public Hero deepCopy() { Hero copy = (Hero) super.deepCopy(); if (weapon != null) { copy.weapon = weapon.deepCopy(); } copy.armor_ = armor_; return copy; } /** * Attack with the hero * <p> * A hero can only attack if it has a temporary buff, such as weapons * * @param targetMinionPlayerSide * @param targetMinion The target minion * @param boardState The BoardState before this card has performed its action. It will be manipulated and returned. * @return The boardState is manipulated and returned */ @Override public HearthTreeNode attack(PlayerSide targetMinionPlayerSide, Minion targetMinion, HearthTreeNode boardState) throws HSException { if (!this.canAttack(boardState.data_, PlayerSide.CURRENT_PLAYER)) { return null; } WeaponCard attackingWeapon = this.getWeapon(); if (attackingWeapon != null) { attackingWeapon.beforeAttack(targetMinionPlayerSide, targetMinion, boardState); } HearthTreeNode toRet = super.attack(targetMinionPlayerSide, targetMinion, boardState); // TODO: weapon deathrattles should happen at the same time as the minion deathrattles if (toRet != null && attackingWeapon != null) { attackingWeapon.afterAttack(targetMinionPlayerSide, targetMinion, boardState); DeathrattleAction weaponDeathrattle = this.checkForWeaponDeath(); if (weaponDeathrattle != null) { toRet = weaponDeathrattle.performAction(CharacterIndex.HERO, PlayerSide.CURRENT_PLAYER, toRet); toRet = BoardStateFactoryBase.handleDeadMinions(toRet); } } return toRet; } @Override public boolean canBeUsedOn(PlayerSide playerSide, Minion minion, BoardModel boardModel) { if (this.hasBeenUsed()) { return false; } if (boardModel.getCurrentPlayer().getMana() < HERO_ABILITY_COST) { return false; } if (!minion.isHeroTargetable()) { return false; } return true; } public final HearthTreeNode useHeroAbility(PlayerSide targetPlayerSide, CharacterIndex targetIndex, HearthTreeNode boardState) throws HSException { Minion targetMinion = boardState.data_.modelForSide(targetPlayerSide).getCharacter(targetIndex); HearthTreeNode toRet = this.useHeroAbility(targetPlayerSide, targetMinion, boardState); if (toRet != null) toRet = toRet.notifyHeroAbilityUsed(targetPlayerSide, targetMinion); return toRet; } /** * Use the hero ability on a given target * * @param targetPlayerSide * @param targetMinion The target minion * @param boardState * @return */ public final HearthTreeNode useHeroAbility(PlayerSide targetPlayerSide, Minion targetMinion, HearthTreeNode boardState) { if (!this.canBeUsedOn(targetPlayerSide, targetMinion, boardState.data_)) { return null; } PlayerModel targetPlayer = boardState.data_.modelForSide(targetPlayerSide); HearthTreeNode toRet = this.useHeroAbility_core(targetPlayerSide, targetMinion, boardState); if (toRet != null) { CharacterIndex targetIndex = targetPlayer.getIndexForCharacter(targetMinion); toRet.setAction(new HearthAction(Verb.HERO_ABILITY, PlayerSide.CURRENT_PLAYER, 0, targetPlayerSide, targetIndex)); toRet = BoardStateFactoryBase.handleDeadMinions(toRet); } return toRet; } protected abstract HearthTreeNode useHeroAbility_core(PlayerSide targetPlayerSide, Minion targetMinion, HearthTreeNode boardState); /** * Called when this minion takes damage * <p> * Overridden from Minion. Need to handle armor. * * @param damage The amount of damage to take * @param thisPlayerSide * @param isSpellDamage */ @Override public byte takeDamage(byte damage, PlayerSide originSide, PlayerSide thisPlayerSide, BoardModel board, boolean isSpellDamage) { byte totalDamage = damage; if (isSpellDamage) { totalDamage += board.modelForSide(originSide).getSpellDamage(); } byte damageRemaining = (byte) (totalDamage - armor_); if (damageRemaining > 0) { armor_ = 0; super.takeDamage(damageRemaining, originSide, thisPlayerSide, board, false); } else { armor_ = (byte) (armor_ - totalDamage); } return totalDamage; } /** * Simpler version of take damage * <p> * For now, the Hero taking damage has no consequence to the board state. So, this version can be used as a way to simplify the code. * * @param damage The amount of damage taken by the hero */ public void takeDamage(byte damage) { byte damageRemaining = (byte) (damage - armor_); if (damageRemaining > 0) { armor_ = 0; health_ -= (byte) (damage - armor_); } else { armor_ = (byte) (armor_ - damage); } } @Override public JSONObject toJSON() { JSONObject json = super.toJSON(); if (this.armor_ > 0) json.put("armor", this.armor_); json.put("weapon", this.weapon); return json; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; Hero hero = (Hero) o; if (armor_ != hero.armor_) return false; if (weapon != null ? !weapon.equals(hero.weapon) : hero.weapon != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (weapon != null ? weapon.hashCode() : 0); result = 31 * result + armor_; return result; } @Override public byte getTotalAttack(BoardModel boardModel, PlayerSide thisMinionPlayerSide) { byte attack = super.getTotalAttack(boardModel, thisMinionPlayerSide); if (this.getWeapon() != null) { attack += this.getWeapon().getWeaponDamage(); } return attack; } public DeathrattleAction setWeapon(WeaponCard weapon) { if (weapon == null) { throw new RuntimeException("use 'destroy weapon' method if trying to remove weapon."); } DeathrattleAction action = this.weapon != null && this.weapon.hasDeathrattle() ? this.weapon.getDeathrattle() : null; this.weapon = weapon; return action; } public WeaponCard getWeapon() { return weapon; } public DeathrattleAction destroyWeapon() { if (weapon != null) { DeathrattleAction action = weapon.hasDeathrattle() ? weapon.getDeathrattle() : null; weapon = null; return action; } return null; } public DeathrattleAction checkForWeaponDeath() { if (weapon != null && weapon.getWeaponCharge() == 0) { return this.destroyWeapon(); } return null; } @Override public HearthTreeNode minionSummonEvent(PlayerSide thisMinionPlayerSide, PlayerSide summonedMinionPlayerSide, Minion summonedMinion, HearthTreeNode boardState) { if (weapon != null) { weapon.minionSummonedEvent(thisMinionPlayerSide, summonedMinionPlayerSide, summonedMinion, boardState); } return boardState; } }