package com.hearthsim.card; import com.hearthsim.card.minion.Hero; import com.hearthsim.card.minion.Minion; import com.hearthsim.card.spellcard.SpellCard; import com.hearthsim.card.spellcard.SpellRandomInterface; import com.hearthsim.event.deathrattle.DeathrattleAction; import com.hearthsim.event.effect.*; import com.hearthsim.event.filter.FilterCharacter; import com.hearthsim.event.filter.FilterHand; import com.hearthsim.exception.HSException; import com.hearthsim.model.BoardModel; import com.hearthsim.model.PlayerModel; import com.hearthsim.model.PlayerSide; import com.hearthsim.util.DeepCopyable; import com.hearthsim.util.HearthAction; import com.hearthsim.util.HearthAction.Verb; import com.hearthsim.util.factory.BoardStateFactoryBase; import com.hearthsim.util.tree.HearthTreeNode; import com.hearthsim.util.tree.RandomEffectNode; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Card implements DeepCopyable<Card> { private static final Logger log = LoggerFactory.getLogger(Card.class); protected boolean hasBeenUsed; protected boolean inHand; private byte manaDelta = 0; protected DeathrattleAction deathrattleAction_; protected boolean deathrattleTriggered; protected final ImplementedCardList.ImplementedCard implementedCard; public enum CardRarity { UNKNOWN, FREE, COMMON, RARE, EPIC, LEGENDARY } public static CardRarity StringToCardRarity(String rarity) { rarity = rarity == null ? "" : rarity.toLowerCase(); switch (rarity) { case "free": return CardRarity.FREE; case "common": return CardRarity.COMMON; case "rare": return CardRarity.RARE; case "epic": return CardRarity.EPIC; case "legendary": return CardRarity.LEGENDARY; default: return CardRarity.UNKNOWN; } } /** * Constructor */ public Card() { ImplementedCardList cardList = ImplementedCardList.getInstance(); ImplementedCardList.ImplementedCard implementedCard = cardList.getCardForClass(this.getClass()); this.implementedCard = implementedCard; this.initFromImplementedCard(implementedCard); } protected void initFromImplementedCard(ImplementedCardList.ImplementedCard implementedCard) { this.hasBeenUsed = false; this.inHand = true; this.deathrattleTriggered = false; } /** * Get the name of the card * * @return Name of the card */ public String getName() { return this.implementedCard != null ? this.implementedCard.name_ : null; } /** * Get the mana cost of the card * * @param side The PlayerSide of the card for which you want the mana cost * @param board The BoardModel representing the current board state * * @return Mana cost of the card */ public byte getManaCost(PlayerSide side, BoardModel board) { return (byte) Math.max(this.getBaseManaCost() + this.getManaDelta(), 0); } public byte getManaDelta() { return manaDelta; } public void setManaDelta(byte manaDelta) { this.manaDelta = manaDelta; } /** * Get the base mana cost of the card * * @return Mana cost of the card */ public byte getBaseManaCost() { if (this.implementedCard == null) { return 0; } return (byte) this.implementedCard.mana_; } /** * Returns whether the card has been used or not * * @return */ public boolean hasBeenUsed() { return hasBeenUsed; } /** * Sets whether the card has been used or not * * @param value The new hasBeenUsed value */ public void hasBeenUsed(boolean value) { hasBeenUsed = value; } public void setInHand(boolean value) { inHand = value; } public boolean setInHand() { return inHand; } // Use for bounce (e.g., Brewmaster) or recreate (e.g., Reincarnate) public Card createResetCopy() { try { Constructor<? extends Card> ctor = this.getClass().getConstructor(); return ctor.newInstance(); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } // This deepCopy pattern is required because we use the class of each card to recreate it under certain circumstances @Override public Card deepCopy() { Card copy = null; try { copy = getClass().newInstance(); } catch(InstantiationException e) { Card.log.error("instantiation error", e); } catch(IllegalAccessException e) { Card.log.error("illegal access error", e); } if (copy == null) { throw new RuntimeException("unable to instantiate card."); } copy.hasBeenUsed = this.hasBeenUsed; copy.inHand = this.inHand; copy.manaDelta = this.manaDelta; copy.deathrattleTriggered = deathrattleTriggered; return copy; } @Override public boolean equals(Object other) { if (other == null) { return false; } if (this.getClass() != other.getClass()) { return false; } // More logic here to be discuss below... if (this.getManaDelta() != ((Card)other).getManaDelta()) return false; if (this.getBaseManaCost() != ((Card)other).getBaseManaCost()) return false; if (hasBeenUsed != ((Card)other).hasBeenUsed) return false; if (inHand != ((Card)other).inHand) return false; if (deathrattleTriggered != ((Card)other).deathrattleTriggered) return false; if (this.getName() == null) { if (((Card)other).getName() != null) { return false; } } else if (!this.getName().equals(((Card)other).getName())) { return false; } return true; } @Override public int hashCode() { int result = this.getName() != null ? this.getName().hashCode() : 0; result = 31 * result + this.getBaseManaCost(); result = 31 * result + (hasBeenUsed ? 1 : 0); result = 31 * result + (inHand ? 1 : 0); result = 31 * result + (deathrattleTriggered ? 1 : 0); return result; } /** * Returns whether this card can be used on the given target or not * * This function is an optional optimization feature. Some cards in Hearthstone have limited targets; Shadow Bolt cannot be used on heroes, Mind Blast can only target enemy heroes, etc. Even in this situation though, BoardStateFactory * will still try to play non- sensical moves because it doesn't know that such moves are invalid until it tries to play them. The problem is, the BoardStateFactory has to go and make a deep copy of the current BoardState before it can * go and try to play the invalid move, and it turns out that 98% of execution time in HearthSim is BoardStateFactory calling BoardState.deepCopy(). By overriding this function and returning false on appropriate occasions, we can save * some calls to deepCopy and get better performance out of the code. * * By default, this function only checks mana cost. * * @param playerSide * @param boardModel * @return */ public boolean canBeUsedOn(PlayerSide playerSide, Minion minion, BoardModel boardModel) { if (!(this.getManaCost(PlayerSide.CURRENT_PLAYER, boardModel) <= boardModel.getCurrentPlayer().getMana())) { return false; } if (this instanceof EffectOnResolveTargetable) { if (!((EffectOnResolveTargetable)this).getTargetableFilter().targetMatches(PlayerSide.CURRENT_PLAYER, this, playerSide, minion, boardModel)) { return false; } } else if (this instanceof SpellCard && playerSide != PlayerSide.CURRENT_PLAYER || !minion.isHero()) { // TODO ignore minion cards for now return false; } return true; } public boolean canBeUsedOn(PlayerSide playerSide, CharacterIndex targetIndex, BoardModel boardModel) { Minion targetMinion = boardModel.modelForSide(playerSide).getCharacter(targetIndex); return this.canBeUsedOn(playerSide, targetMinion, boardModel); } public HearthTreeNode useOn(PlayerSide side, CharacterIndex targetIndex, HearthTreeNode boardState) throws HSException { Minion target = boardState.data_.modelForSide(side).getCharacter(targetIndex); return this.useOn(side, target, boardState); } /** * Use the card on the given target * * @param side * @param targetMinion The target minion (can be a Hero) * @param boardState The BoardState before this card has performed its action. It will be manipulated and returned. * * @return The boardState is manipulated and returned */ private HearthTreeNode useOn(PlayerSide side, Minion targetMinion, HearthTreeNode boardState) throws HSException { if (!this.canBeUsedOn(side, targetMinion, boardState.data_)) return null; PlayerModel currentPlayer = boardState.data_.getCurrentPlayer(); PlayerModel targetPlayer = boardState.data_.modelForSide(side); // Need to record card and target index *before* the board state changes int cardIndex = currentPlayer.getHand().indexOf(this); CharacterIndex targetIndex = targetPlayer.getIndexForCharacter(targetMinion); currentPlayer.addNumCardsUsed((byte)1); HearthTreeNode toRet = this.notifyCardPlayBegin(boardState); if (toRet != null) { toRet = this.use_core(side, targetMinion, toRet); } if (toRet != null) { // we need to resolve each RNG child separately if (toRet instanceof RandomEffectNode && toRet.numChildren() > 0) { for (HearthTreeNode child : toRet.getChildren()) { this.resolveCardPlayedAndNotify(child); // TODO deal with null return } } else { toRet = this.resolveCardPlayedAndNotify(toRet); } } if (toRet != null) { toRet.setAction(new HearthAction(Verb.USE_CARD, PlayerSide.CURRENT_PLAYER, cardIndex, side, targetIndex)); } return toRet; } private HearthTreeNode resolveCardPlayedAndNotify(HearthTreeNode boardState) { if (boardState != null && this.triggersOverload()) { boardState.data_.modelForSide(PlayerSide.CURRENT_PLAYER).addOverload(this.getOverload()); } if (boardState != null) { boardState = this.notifyCardPlayResolve(boardState); } return boardState; } /** * Use the card on the given target * <p> * This is the core implementation of card's ability * * @param side * @param boardState The BoardState before this card has performed its action. It will be manipulated and returned. * @return The boardState is manipulated and returned */ protected HearthTreeNode use_core( PlayerSide side, Minion targetMinion, HearthTreeNode boardState) throws HSException { HearthTreeNode toRet = boardState; int originIndex = boardState.data_.modelForSide(PlayerSide.CURRENT_PLAYER).getHand().indexOf(this); CharacterIndex targetIndex = boardState.data_.modelForSide(side).getIndexForCharacter(targetMinion); EffectCharacter<Card> targetableEffect = null; if (this instanceof EffectOnResolveTargetable) { targetableEffect = ((EffectOnResolveTargetable) this).getTargetableEffect(); } byte manaCost = this.getManaCost(PlayerSide.CURRENT_PLAYER, boardState.data_); // If this card is a minion card, the summon phase happens first if (this instanceof Minion) { PlayerModel currentPlayer = toRet.data_.getCurrentPlayer(); toRet.data_.modelForSide(PlayerSide.CURRENT_PLAYER).subtractMana(manaCost); this.hasBeenUsed(true); currentPlayer.getHand().remove(this); if (targetableEffect != null) { toRet = targetableEffect.applyEffect(side, targetMinion, toRet); } } // TODO this is to workaround using super.use_core since we no longer have an accurate reference to the origin card (specifically, Soulfire messes things up) Collection<HearthTreeNode> rngChildren = null; // different interfaces have different usage patterns if (this instanceof SpellRandomInterface) { rngChildren = ((SpellRandomInterface) this).createChildren(PlayerSide.CURRENT_PLAYER, originIndex, toRet); } else if (this instanceof EffectOnResolveRandomCharacter) { EffectOnResolveRandomCharacter character = (EffectOnResolveRandomCharacter)this; rngChildren = this.effectRandomCharacterUsingFilter(character.getRandomTargetEffect(), character.getRandomTargetSecondaryEffect(), character.getRandomTargetFilter(), boardState); } else if (this instanceof EffectOnResolveRandomHand) { EffectOnResolveRandomHand hand = (EffectOnResolveRandomHand)this; rngChildren = this.effectRandomHandUsingFilter(hand.getRandomTargetEffect(), hand.getRandomTargetSecondaryEffect(), hand.getRandomTargetFilter(), PlayerSide.CURRENT_PLAYER, boardState); } else if (this instanceof EffectOnResolveAoe) { toRet = this.effectAllUsingFilter(((EffectOnResolveAoe) this).getAoeEffect(), ((EffectOnResolveAoe) this).getAoeFilter(), toRet); } // if we expected rngChildren but none were created, don't let this card be played if it was the only effect if (rngChildren != null && rngChildren.size() == 0 && targetableEffect == null) { toRet = null; } if (toRet == null) { return null; } toRet = this.createRngNodeWithChildren(toRet, rngChildren); if (toRet != null && toRet instanceof RandomEffectNode) { // create an RNG "base" that is untouched. This allows us to recreate the RNG children during history traversal. // this.hasBeenUsed(false); // revert back to unused for the purposes of replays // for each child, apply the effect and mana cost. we want to do as much as we can with the non-random effect portion (e.g., the damage part of Soulfire) if (!(this instanceof Minion)) { for (HearthTreeNode child : toRet.getChildren()) { if (targetableEffect != null) { // Sometimes, the target of the card use is already dead. In this case, if this is not a minion card, the effect should just fizzle. try { child = targetableEffect.applyEffect(side, targetIndex, child); } catch (IndexOutOfBoundsException e) { log.debug("Target already dead"); } } child.data_.modelForSide(PlayerSide.CURRENT_PLAYER).subtractMana(manaCost); } } } else { if (toRet != null && !(this instanceof Minion)) { // apply standard card played effects PlayerModel currentPlayer = toRet.data_.getCurrentPlayer(); toRet.data_.modelForSide(PlayerSide.CURRENT_PLAYER).subtractMana(manaCost); this.hasBeenUsed(true); currentPlayer.getHand().remove(this); if (targetableEffect != null) { toRet = targetableEffect.applyEffect(side, targetMinion, toRet); } } } return toRet; } // ====================================================================================== // Various notifications // ====================================================================================== private HearthTreeNode notifyCardPlayBegin(HearthTreeNode boardState) { PlayerModel currentPlayer = boardState.data_.getCurrentPlayer(); PlayerModel waitingPlayer = boardState.data_.getWaitingPlayer(); HearthTreeNode toRet = boardState; ArrayList<CardPlayBeginInterface> matches = new ArrayList<>(); for (Card card : currentPlayer.getHand()) { if (card instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)card); } } Card hero = currentPlayer.getHero(); if (hero instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)hero); } for (Minion minion : currentPlayer.getMinions()) { if (!minion.isSilenced() && minion instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)minion); } } for (CardPlayBeginInterface match : matches) { toRet = match.onCardPlayBegin(PlayerSide.CURRENT_PLAYER, PlayerSide.CURRENT_PLAYER, this, toRet); } matches.clear(); for (Card card : waitingPlayer.getHand()) { if (card instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)card); } } hero = waitingPlayer.getHero(); if (hero instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)hero); } for (Minion minion : waitingPlayer.getMinions()) { if (!minion.isSilenced() && minion instanceof CardPlayBeginInterface) { matches.add((CardPlayBeginInterface)minion); } } for (CardPlayBeginInterface match : matches) { toRet = match.onCardPlayBegin(PlayerSide.WAITING_PLAYER, PlayerSide.CURRENT_PLAYER, this, toRet); } // check for and remove dead minions toRet = BoardStateFactoryBase.handleDeadMinions(toRet); return toRet; } private HearthTreeNode notifyCardPlayResolve(HearthTreeNode boardState) { PlayerModel currentPlayer = boardState.data_.getCurrentPlayer(); PlayerModel waitingPlayer = boardState.data_.getWaitingPlayer(); HearthTreeNode toRet = boardState; ArrayList<CardPlayAfterInterface> matches = new ArrayList<>(); for (Card card : currentPlayer.getHand()) { if (card instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)card); } } Card hero = currentPlayer.getHero(); if (hero instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)hero); } for (Minion minion : currentPlayer.getMinions()) { if (!minion.isSilenced() && minion instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)minion); } } for (CardPlayAfterInterface match : matches) { toRet = match.onCardPlayResolve(PlayerSide.CURRENT_PLAYER, PlayerSide.CURRENT_PLAYER, this, toRet); } matches.clear(); for (Card card : waitingPlayer.getHand()) { if (card instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)card); } } hero = waitingPlayer.getHero(); if (hero instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)hero); } for (Minion minion : waitingPlayer.getMinions()) { if (!minion.isSilenced() && minion instanceof CardPlayAfterInterface) { matches.add((CardPlayAfterInterface)minion); } } for (CardPlayAfterInterface match : matches) { toRet = match.onCardPlayResolve(PlayerSide.WAITING_PLAYER, PlayerSide.CURRENT_PLAYER, this, toRet); } // check for and remove dead minions toRet = BoardStateFactoryBase.handleDeadMinions(toRet); return toRet; } protected final HearthTreeNode effectAllUsingFilter(EffectCharacter<Card> effect, FilterCharacter filter, HearthTreeNode boardState) { if (boardState != null && filter != null) { for (CharacterIndex.CharacterLocation location : boardState.data_) { Minion character = boardState.data_.getCharacter(location); if (filter.targetMatches(PlayerSide.CURRENT_PLAYER, this, location.getPlayerSide(), character, boardState.data_)) { boardState = effect.applyEffect(location.getPlayerSide(), character, boardState); } } } return boardState; } protected final Collection<HearthTreeNode> effectRandomCharacterUsingFilter(EffectCharacter<Card> effect, EffectCharacter<Card> effectOthers, FilterCharacter filter, HearthTreeNode boardState) { return this.effectRandomCharacterUsingFilter(effect, effectOthers, filter, PlayerSide.CURRENT_PLAYER, boardState); } protected final Collection<HearthTreeNode> effectRandomCharacterUsingFilter(EffectCharacter<Card> effect, EffectCharacter<Card> effectOthers, FilterCharacter filter, PlayerSide originSide, HearthTreeNode boardState) { return this.iterateAndEffectRandom(effect, effectOthers, filter, originSide, boardState, boardState.data_.iterator()); } protected final Collection<HearthTreeNode> effectRandomHandUsingFilter(EffectHand effect, EffectHand effectOthers, FilterHand filter, PlayerSide originSide, HearthTreeNode boardState) { Iterator<CardInHandIndex.CardInHandLocation> handIterator = boardState.data_.handIterator(); return this.iterateAndEffectRandom(effect, effectOthers, filter, originSide, boardState, handIterator); } protected final Collection<HearthTreeNode> iterateAndEffectRandom(EffectHand effect, EffectHand effectOthers, FilterHand filter, PlayerSide originSide, HearthTreeNode boardState, Iterator<CardInHandIndex.CardInHandLocation> targetIterator) { int originIndex = boardState.data_.modelForSide(originSide).getHand().indexOf(this); boolean originInHand = originIndex >= 0; CharacterIndex originCharacterIndex = CharacterIndex.UNKNOWN; if (!originInHand) { originCharacterIndex = boardState.data_.modelForSide(originSide).getIndexForCharacter((Minion)this); } ArrayList<HearthTreeNode> children = new ArrayList<>(); while (targetIterator.hasNext()) { CardInHandIndex.CardInHandLocation location = targetIterator.next(); if (filter.targetMatches(originSide, this, location.getPlayerSide(), location.getIndex(), boardState.data_)) { boolean somethingHappened = false; HearthTreeNode newState = new HearthTreeNode(boardState.data_.deepCopy()); Card origin; if (originInHand) { origin = newState.data_.modelForSide(originSide).getHand().get(originIndex); } else { origin = newState.data_.modelForSide(originSide).getCharacter(originCharacterIndex); } if (effect != null) { newState = effect.applyEffect(originSide, origin, location.getPlayerSide(), location.getIndex(), newState); somethingHappened = newState != null; } if (effectOthers != null && newState != null) { Iterator<CardInHandIndex.CardInHandLocation> secondaryIterator = newState.data_.handIterator(); while (secondaryIterator.hasNext()) { CardInHandIndex.CardInHandLocation childLocation = secondaryIterator.next(); if (location.equals(childLocation)) { continue; } if (filter.targetMatches(originSide, origin, childLocation.getPlayerSide(), childLocation.getIndex(), boardState.data_)) { newState = effectOthers.applyEffect(originSide, origin, childLocation.getPlayerSide(), childLocation.getIndex(), newState); somethingHappened = newState != null; } } } if (somethingHappened) { if (originInHand) { newState.data_.modelForSide(originSide).getHand().remove(origin); } newState = BoardStateFactoryBase.handleDeadMinions(newState); children.add(newState); } } } return children; } protected final Collection<HearthTreeNode> iterateAndEffectRandom(EffectCharacter<Card> effect, EffectCharacter<Card> effectOthers, FilterCharacter filter, PlayerSide originSide, HearthTreeNode boardState, Iterator<CharacterIndex.CharacterLocation> targetIterator) { int originIndex = boardState.data_.modelForSide(originSide).getHand().indexOf(this); boolean originInHand = originIndex >= 0; CharacterIndex originCharacterIndex = CharacterIndex.UNKNOWN; if (!originInHand) { originCharacterIndex = boardState.data_.modelForSide(originSide).getIndexForCharacter((Minion)this); } ArrayList<HearthTreeNode> children = new ArrayList<>(); while (targetIterator.hasNext()) { CharacterIndex.CharacterLocation location = targetIterator.next(); if (filter.targetMatches(originSide, this, location.getPlayerSide(), location.getIndex(), boardState.data_)) { boolean somethingHappened = false; HearthTreeNode newState = new HearthTreeNode(boardState.data_.deepCopy()); Card origin; if (originInHand) { origin = newState.data_.modelForSide(originSide).getHand().get(originIndex); } else { origin = newState.data_.modelForSide(originSide).getCharacter(originCharacterIndex); } if (effect != null) { newState = effect.applyEffect(location.getPlayerSide(), location.getIndex(), newState); somethingHappened = newState != null; } if (effectOthers != null && newState != null) { for (CharacterIndex.CharacterLocation childLocation : newState.data_) { if (location.equals(childLocation)) { continue; } if (filter.targetMatches(originSide, origin, childLocation.getPlayerSide(), childLocation.getIndex(), boardState.data_)) { newState = effectOthers.applyEffect(childLocation.getPlayerSide(), childLocation.getIndex(), newState); somethingHappened = newState != null; } } } if (somethingHappened) { if (originInHand) { newState.data_.modelForSide(originSide).getHand().remove(origin); } newState = BoardStateFactoryBase.handleDeadMinions(newState); children.add(newState); } } } return children; } protected HearthTreeNode createRngNodeWithChildren(HearthTreeNode boardState, Collection<HearthTreeNode> rngChildren) { if (rngChildren != null && rngChildren.size() > 0) { RandomEffectNode rngNode = new RandomEffectNode(boardState, boardState.getAction()); boardState = this.createNodeWithChildren(rngNode, rngChildren); } return boardState; } protected HearthTreeNode createNodeWithChildren(HearthTreeNode boardState, Collection<HearthTreeNode> children) { if (children != null) { boardState.addChildren(children); } return boardState; } public JSONObject toJSON() { JSONObject json = new JSONObject(); json.put("name", this.getName()); json.put("mana", this.getBaseManaCost()); if (hasBeenUsed) json.put("hasBeenUsed", hasBeenUsed); if (this.manaDelta != 0) json.put("manaDelta", this.manaDelta); return json; } @Override public String toString() { return this.toJSON().toString(); } protected boolean isWaitingPlayer(PlayerSide side) { return PlayerSide.WAITING_PLAYER == side; } protected boolean isCurrentPlayer(PlayerSide side) { return PlayerSide.CURRENT_PLAYER == side; } protected byte getOverload() { if (this.implementedCard == null) { return 0; } return (byte) this.implementedCard.overload; } public boolean triggersOverload() { return this.getOverload() > 0; } public boolean hasDeathrattle() { return deathrattleAction_ != null; } public DeathrattleAction getDeathrattle() { return deathrattleAction_; } public void setDeathrattle(DeathrattleAction action) { deathrattleAction_ = action; } public boolean isDeathrattleTriggered() { return deathrattleTriggered; } public void setDeathrattleTriggered(boolean deathrattleTriggered) { this.deathrattleTriggered = deathrattleTriggered; } public CardRarity getRarity() { if (this.implementedCard == null) { return CardRarity.UNKNOWN; } return StringToCardRarity(implementedCard.rarity_); } @Deprecated public Card(String name, byte baseManaCost, boolean hasBeenUsed, boolean inHand, byte overload) { this.hasBeenUsed = hasBeenUsed; this.inHand = inHand; this.implementedCard = null; } @Deprecated public Card(byte baseManaCost, boolean hasBeenUsed, boolean inHand) { ImplementedCardList cardList = ImplementedCardList.getInstance(); ImplementedCardList.ImplementedCard implementedCard = cardList.getCardForClass(this.getClass()); this.hasBeenUsed = hasBeenUsed; this.inHand = inHand; this.implementedCard = implementedCard; } @Deprecated protected boolean isHero(Minion targetMinion) { return targetMinion instanceof Hero; } }