package magic.model; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import magic.ai.ArtificialScoringSystem; import magic.data.CardDefinitions; import magic.model.action.AttachAction; import magic.model.action.ChangeControlAction; import magic.model.action.ChangeCountersAction; import magic.model.action.ChangeStateAction; import magic.model.action.DestroyAction; import magic.model.action.RemoveFromPlayAction; import magic.model.action.SacrificeAction; import magic.model.action.SoulbondAction; import magic.model.choice.MagicTargetChoice; import magic.model.event.MagicActivation; import magic.model.event.MagicBestowActivation; import magic.model.event.MagicManaActivation; import magic.model.event.MagicPlayAuraEvent; import magic.model.event.MagicSourceActivation; import magic.model.mstatic.MagicLayer; import magic.model.mstatic.MagicPermanentStatic; import magic.model.mstatic.MagicStatic; import magic.model.target.MagicTarget; import magic.model.target.MagicTargetFilter; import magic.model.target.MagicTargetFilterFactory; import magic.model.trigger.EntersBattlefieldTrigger; import magic.model.trigger.MagicTrigger; import magic.model.trigger.MagicTriggerType; public class MagicPermanent extends MagicObjectImpl implements MagicSource, MagicTarget, Comparable, MagicMappable, IRenderableCard { public static final int NO_COLOR_FLAGS = -1; private final long id; private final MagicCardDefinition cardDefinition; private final MagicCard card; private final MagicPlayer firstController; private MagicPermanent equippedCreature = MagicPermanent.NONE; private final MagicPermanentSet equipmentPermanents; private MagicPermanent enchantedPermanent = MagicPermanent.NONE; private final MagicPermanentSet auraPermanents; private MagicPermanent blockedCreature = MagicPermanent.NONE; private final MagicPermanentList blockingCreatures; private MagicPermanent pairedCreature = MagicPermanent.NONE; private final MagicCardList exiledCards; private MagicPlayer chosenPlayer = MagicPlayer.NONE; private Map counters; private int stateFlags = MagicPermanentState.Summoned.getMask() | MagicPermanentState.MustPayEchoCost.getMask(); private int abilityPlayedThisTurn; private int damage; private int preventDamage; private int fixedScore; private int score; // Allows cached retrieval of controller, type, subtype, color, abilites, and p/t // also acts as last known information private MagicPlayer cachedController; private int cachedTypeFlags; private Set cachedSubTypeFlags; private int cachedColorFlags; private Set cachedAbilityFlags; private MagicPowerToughness cachedPowerToughness; private List> cachedActivations; private List cachedManaActivations; private List> cachedTriggers; private List etbTriggers; private Set> appliedStatics; // remember order among blockers (blockedName + id + block order) private String blockedName; private long stateId; public MagicPermanent(final long aId, final MagicCard aCard, final MagicPlayer aController) { this(aId, aCard, aCard.getCardDefinition(), aController); } public MagicPermanent(final long aId, final MagicCard aCard, final MagicCardDefinition aCardDef, final MagicPlayer aController) { id = aId; card = aCard; cardDefinition = aCardDef; firstController = aController; counters = new EnumMap<>(MagicCounterType.class); equipmentPermanents = new MagicPermanentSet(); auraPermanents = new MagicPermanentSet(); blockingCreatures = new MagicPermanentList(); exiledCards = new MagicCardList(); cachedController = firstController; cachedTypeFlags = getCardDefinition().getTypeFlags(); cachedSubTypeFlags = getCardDefinition().genSubTypes(); cachedColorFlags = getCardDefinition().getColorFlags(); cachedAbilityFlags = getCardDefinition().genAbilityFlags(); cachedPowerToughness = getCardDefinition().genPowerToughness(); cachedActivations = new LinkedList<>(); cachedManaActivations = new LinkedList<>(); cachedTriggers = new LinkedList<>(); etbTriggers = new LinkedList<>(); } private MagicPermanent(final MagicCopyMap copyMap, final MagicPermanent sourcePermanent) { id = sourcePermanent.id; cardDefinition = sourcePermanent.cardDefinition; copyMap.put(sourcePermanent, this); card = copyMap.copy(sourcePermanent.card); firstController = copyMap.copy(sourcePermanent.firstController); stateFlags = sourcePermanent.stateFlags; counters = new EnumMap<>(sourcePermanent.counters); abilityPlayedThisTurn = sourcePermanent.abilityPlayedThisTurn; equippedCreature = copyMap.copy(sourcePermanent.equippedCreature); equipmentPermanents = new MagicPermanentSet(copyMap, sourcePermanent.equipmentPermanents); enchantedPermanent = copyMap.copy(sourcePermanent.enchantedPermanent); auraPermanents = new MagicPermanentSet(copyMap, sourcePermanent.auraPermanents); blockedCreature = copyMap.copy(sourcePermanent.blockedCreature); blockingCreatures = new MagicPermanentList(copyMap, sourcePermanent.blockingCreatures); pairedCreature = copyMap.copy(sourcePermanent.pairedCreature); exiledCards = new MagicCardList(copyMap, sourcePermanent.exiledCards); chosenPlayer = copyMap.copy(sourcePermanent.chosenPlayer); damage = sourcePermanent.damage; preventDamage = sourcePermanent.preventDamage; fixedScore = sourcePermanent.fixedScore; score = sourcePermanent.score; stateId = sourcePermanent.stateId; cachedController = copyMap.copy(sourcePermanent.cachedController); cachedTypeFlags = sourcePermanent.cachedTypeFlags; cachedSubTypeFlags = sourcePermanent.cachedSubTypeFlags; cachedColorFlags = sourcePermanent.cachedColorFlags; cachedAbilityFlags = sourcePermanent.cachedAbilityFlags; cachedPowerToughness = sourcePermanent.cachedPowerToughness; cachedActivations = new LinkedList<>(sourcePermanent.cachedActivations); cachedManaActivations = new LinkedList<>(sourcePermanent.cachedManaActivations); cachedTriggers = new LinkedList<>(sourcePermanent.cachedTriggers); etbTriggers = new LinkedList<>(sourcePermanent.etbTriggers); } @Override public MagicPermanent copy(final MagicCopyMap copyMap) { return new MagicPermanent(copyMap, this); } @Override public MagicPermanent map(final MagicGame game) { final MagicPlayer mappedController = getController().map(game); final MagicPermanent found = mappedController.getPermanents().getPermanent(id); if (found.isValid()) { return found; } else { return mappedController.getOpponent().getPermanents().getPermanent(id); } } @Override public long getId() { return id; } public boolean isValid() { return getController().controlsPermanent(this); } @Override public boolean isInvalid() { return !isValid(); } @Override public long getStateId() { stateId = stateId != 0 ? stateId : MurmurHash3.hash(new long[]{ cardDefinition.getIndex(), card.getStateId(), stateFlags, damage, preventDamage, equipmentPermanents.getStateId(), auraPermanents.getStateId(), blockingCreatures.getStateId(), //pairedCreature.getStateId(), exiledCards.getStateId(), chosenPlayer.getId(), getCountersHash(), abilityPlayedThisTurn, cachedController.getId(), cachedTypeFlags, cachedSubTypeFlags.hashCode(), cachedColorFlags, cachedAbilityFlags.hashCode(), cachedPowerToughness.power(), cachedPowerToughness.toughness(), cachedActivations.hashCode(), cachedManaActivations.hashCode(), cachedTriggers.hashCode(), etbTriggers.hashCode() }); return stateId; } @Override public long getRenderKey() { return getStateId(); } private long getCountersHash() { final long[] keys = new long[counters.size() * 2]; int idx = 0; for (final Entry entry : counters.entrySet()) { keys[idx + 0] = entry.getKey().ordinal(); keys[idx + 1] = entry.getValue(); idx += 2; } return MurmurHash3.hash(keys); } /** * Determines uniqueness of a mana permanent, e.g. for producing mana, all Mountains are equal. */ public int getManaId() { // Creatures or lands that can be animated are unique // Enchanted/equipped permanents are unique // 'Summoned' permanents are unique if (hasExcludeManaOrCombat() || isEnchanted() || isEquipped() || hasState(MagicPermanentState.Summoned)) { return (int) id; } // Uniqueness is determined by card definition and number of charge counters. return -((cardDefinition.getIndex() << 16) + getCounters(MagicCounterType.Charge)); } public boolean hasExcludeManaOrCombat() { return getCardDefinition().hasExcludeManaOrCombat() || (producesMana() && isCreature()); } public MagicCard getCard() { return card; } @Override public boolean isToken() { return card.isToken(); } public boolean isNonToken() { return !card.isToken(); } @Override public boolean isDoubleFaced() { return card.isDoubleFaced(); } @Override public boolean isFlipCard() { return card.isFlipCard(); } @Override public MagicCardDefinition getCardDefinition() { if (isFaceDown()) { if (hasState(MagicPermanentState.Manifest)) { return CardDefinitions.getCard("Manifest"); } else { return CardDefinitions.getCard("Morph"); } } else if (isFlipped()) { return cardDefinition.getFlippedDefinition(); } else if (isTransformed()) { return cardDefinition.getTransformedDefinition(); } else { return cardDefinition; } } // only for rendering the card image popup final public MagicCardDefinition getRealCardDefinition() { if (isFaceDown()) { return cardDefinition; } else { return getCardDefinition(); } } @Override public Collection> getSourceActivations() { List> sourceActs = new LinkedList<>(); for (final MagicActivation act : cachedActivations) { sourceActs.add(MagicSourceActivation.create(this, act)); } return sourceActs; } public void loseAllAbilities() { cachedActivations.clear(); cachedManaActivations.clear(); cachedTriggers.clear(); cachedAbilityFlags.clear(); etbTriggers.clear(); } public void addAbility(final MagicAbility ability, final Set flags) { final MagicAbilityList abilityList = new MagicAbilityList(); ability.addAbility(abilityList); abilityList.giveAbility(this, flags); } public void addAbility(final MagicAbilityList abilityList) { abilityList.giveAbility(this, cachedAbilityFlags); } public void addAbility(final MagicAbility ability) { final MagicAbilityList abilityList = new MagicAbilityList(); ability.addAbility(abilityList); abilityList.giveAbility(this, cachedAbilityFlags); } public void addAbility(final MagicActivation act) { cachedActivations.add(act); } public void addAbility(final MagicTrigger trig) { if (trig instanceof EntersBattlefieldTrigger) { etbTriggers.add((EntersBattlefieldTrigger) trig); } else { cachedTriggers.add(trig); } } public void addAbility(final MagicManaActivation act) { cachedManaActivations.add(act); } public Collection> getActivations() { return cachedActivations; } @Override public Collection getManaActivations() { return cachedManaActivations; } public Collection getStatics() { return getCardDefinition().getStatics(); } public Collection> getTriggers() { return cachedTriggers; } public Collection getComeIntoPlayTriggers() { return etbTriggers; } public int getConvertedCost() { if (isTransformed() && isDoubleFaced()) { return getCardDefinition().getTransformedDefinition().getConvertedCost(); } return getCardDefinition().getConvertedCost(); } public int getDevotion(final MagicColor... colors) { return getCardDefinition().getCost().getDevotion(colors); } public boolean producesMana() { return !cachedManaActivations.isEmpty(); } public int countManaActivations() { return cachedManaActivations.size(); } public boolean isName(final String other) { final String name = getName(); return !name.isEmpty() && name.equalsIgnoreCase(other); } @Override public String getName() { final String name = getCardDefinition().getName(); if (name.isEmpty() && getGame().isReal()) { return "Permanent #" + (id % 1000); } else { return name; } } @Override public String toString() { return getName(); } @Override public MagicGame getGame() { return getOwner().getGame(); } public MagicPlayer getOwner() { return card.getOwner(); } public MagicPlayer getFirstController() { return firstController; } @Override public MagicPlayer getController() { assert cachedController != null : "cachedController is null in " + this; return cachedController; } public boolean isOwner(final MagicTarget player) { return getOwner() == player; } public boolean isController(final MagicTarget player) { return getController() == player; } public boolean isOpponent(final MagicTarget player) { return getOpponent() == player; } public static void update(final MagicGame game) { updateProperties(game); updateScoreFixController(game); } private static void updateScoreFixController(final MagicGame game) { for (final MagicPlayer player : game.getPlayers()) { for (final MagicPermanent perm : player.getPermanents()) { final MagicPlayer curr = perm.getController(); if (curr != player) { game.addDelayedAction(new ChangeControlAction(curr, perm, perm.getScore())); } perm.updateScore(); perm.clearStatics(); } } } private static void updateProperties(final MagicGame game) { for (final MagicLayer layer : MagicLayer.PERMANENT) { for (final MagicPlayer player : game.getPlayers()) { for (final MagicPermanent perm : player.getPermanents()) { perm.apply(layer); } } for (final MagicPermanentStatic mpstatic : game.getStatics(layer)) { final MagicStatic mstatic = mpstatic.getStatic(); final MagicPermanent source = mpstatic.getPermanent(); for (final MagicPlayer player : game.getPlayers()) { for (final MagicPermanent perm : player.getPermanents()) { if (mstatic.accept(game, source, perm)) { perm.appliedStatic(source, mstatic); perm.apply(source, mstatic); } } } } } } private void apply(final MagicLayer layer) { switch (layer) { case Card: cachedController = firstController; cachedTypeFlags = getCardDefinition().getTypeFlags(); cachedSubTypeFlags = getCardDefinition().genSubTypes(); cachedColorFlags = getCardDefinition().getColorFlags(); cachedAbilityFlags = getCardDefinition().genAbilityFlags(); cachedPowerToughness = getCardDefinition().genPowerToughness(); cachedActivations = new LinkedList<>(getCardDefinition().getActivations()); cachedActivations.addAll(cardDefinition.getMorphActivations()); cachedManaActivations = new LinkedList<>(getCardDefinition().getManaActivations()); cachedTriggers = new LinkedList<>(getCardDefinition().getTriggers()); etbTriggers = new LinkedList<>(getCardDefinition().getETBTriggers()); appliedStatics = new HashSet<>(); break; case CDASubtype: getCardDefinition().applyCDASubType(getGame(), getController(), cachedSubTypeFlags); break; case CDAColor: cachedColorFlags = getCardDefinition().applyCDAColor(getGame(), getController(), cachedColorFlags); break; case CDAPT: getCardDefinition().applyCDAPowerToughness(getGame(), getController(), this, cachedPowerToughness); break; default: break; } } private void apply(final MagicPermanent source, final MagicStatic mstatic) { final MagicLayer layer = mstatic.getLayer(); switch (layer) { case Control: cachedController = mstatic.getController(source, this, cachedController); break; case Type: cachedTypeFlags = mstatic.getTypeFlags(this, cachedTypeFlags); mstatic.modSubTypeFlags(this, cachedSubTypeFlags); break; case Color: cachedColorFlags = mstatic.getColorFlags(this, cachedColorFlags); break; case Ability: case AbilityCond: mstatic.modAbilityFlags(source, this, cachedAbilityFlags); break; case SetPT: case ModPT: case CountersPT: case SwitchPT: mstatic.modPowerToughness(source, this, cachedPowerToughness); break; default: break; } } public void setState(final MagicPermanentState state) { stateFlags |= state.getMask(); } public void clearState(final MagicPermanentState state) { stateFlags &= Integer.MAX_VALUE - state.getMask(); } public boolean hasState(final MagicPermanentState state) { return state.hasState(stateFlags); } public int getStateFlags() { return stateFlags; } public void setStateFlags(final int flags) { stateFlags = flags; } public boolean isTapped() { return hasState(MagicPermanentState.Tapped); } public boolean isUntapped() { return !hasState(MagicPermanentState.Tapped); } @Override public int getColorFlags() { return cachedColorFlags; } @Override public boolean hasColor(final MagicColor color) { return color.hasColor(getColorFlags()); } @Override public int getCounters(final MagicCounterType counterType) { final Integer cnt = counters.get(counterType); return cnt != null ? cnt : 0; } @Override public void changeCounters(final MagicCounterType counterType, final int amount) { final int oldAmt = getCounters(counterType); final int newAmt = oldAmt + amount; if (newAmt == 0) { counters.remove(counterType); } else { counters.put(counterType, newAmt); } } public Collection getCounterTypes() { return counters.keySet(); } public boolean hasCounters() { return counters.size() > 0; } @Override public boolean hasSubType(final MagicSubType subType) { return cachedSubTypeFlags.contains(subType); } public boolean hasAllCreatureTypes() { return cachedSubTypeFlags.equals(MagicSubType.ALL_CREATURES); } public MagicPowerToughness getPowerToughness() { return cachedPowerToughness; } public int getPower() { return getPowerToughness().getPositivePower(); } public int getPowerValue() { return getPowerToughness().power(); } public int getToughness() { return getPowerToughness().getPositiveToughness(); } public int getToughnessValue() { return getPowerToughness().toughness(); } public Set getAbilityFlags() { return cachedAbilityFlags; } @Override public boolean hasAbility(final MagicAbility ability) { return cachedAbilityFlags.contains(ability); } private void updateScore() { stateId = 0; fixedScore = ArtificialScoringSystem.getFixedPermanentScore(this); score = fixedScore + ArtificialScoringSystem.getVariablePermanentScore(this); } public int getScore() { return score; } public int getStaticScore() { return cardDefinition.getStaticType().getScore(this); } public int getCountersScore() { int amount = 0; for (final Entry entry : counters.entrySet()) { amount += entry.getKey().getScore() * entry.getValue(); } return amount; } public int getCardScore() { return cardDefinition.getScore(); } public int getDamage() { return damage; } public void setDamage(final int aDamage) { damage = aDamage; } @Override public int getPreventDamage() { return preventDamage; } @Override public void setPreventDamage(final int amount) { preventDamage = amount; } public int getLethalDamage(final int toughness) { return toughness <= damage ? 0 : toughness - damage; } // Tap symbol. public boolean canTap() { return !hasState(MagicPermanentState.Tapped) && (!hasState(MagicPermanentState.Summoned) || !isCreature() || hasAbility(MagicAbility.Haste) ); } // Untap symbol. public boolean canUntap() { return hasState(MagicPermanentState.Tapped) && (!hasState(MagicPermanentState.Summoned) || !isCreature() || hasAbility(MagicAbility.Haste) ); } public boolean canRegenerate() { return !hasState(MagicPermanentState.Regenerated) && !hasState(MagicPermanentState.CannotBeRegenerated); } public boolean isRegenerated() { return hasState(MagicPermanentState.Regenerated) && !hasState(MagicPermanentState.CannotBeRegenerated); } public boolean isAttacking() { return hasState(MagicPermanentState.Attacking); } public boolean isBlocked() { return hasState(MagicPermanentState.Blocked); } public boolean isBlocking() { return hasState(MagicPermanentState.Blocking); } public MagicPermanent getBlockedCreature() { return blockedCreature; } public void setBlockedCreature(final MagicPermanent creature) { if (creature.isValid()) { blockedName = creature.getName() + creature.getId() + (100 + creature.numBlockingCreatures()); } blockedCreature = creature; } public String getBlockedName() { return blockedName; } public MagicPermanentList getBlockingCreatures() { return blockingCreatures; } public int numBlockingCreatures() { return blockingCreatures.size(); } public void setBlockingCreatures(final MagicPermanentList creatures) { blockingCreatures.clear(); blockingCreatures.addAll(creatures); } public void addBlockingCreature(final MagicPermanent creature) { blockingCreatures.add(creature); } public void removeBlockingCreature(final MagicPermanent creature) { blockingCreatures.remove(creature); } public void removeBlockingCreatures() { blockingCreatures.clear(); } public MagicPermanent getPairedCreature() { return pairedCreature; } public void setPairedCreature(final MagicPermanent creature) { pairedCreature = creature; } public boolean isPaired() { return pairedCreature != MagicPermanent.NONE; } public MagicCardList getExiledCards() { return exiledCards; } public MagicCard getExiledCard() { return exiledCards.getCardAtTop(); } public void addExiledCard(final MagicCard card) { // only non tokens can be added if (!card.isToken()) { exiledCards.add(card); } } public void removeExiledCard(final MagicCard card) { exiledCards.remove(card); } public MagicPlayer getChosenPlayer() { return chosenPlayer; } public void setChosenPlayer(final MagicPlayer player) { chosenPlayer = player; } void generateStateBasedActions() { final MagicGame game = getGame(); if (isCreature()) { final int toughness = getToughness(); if (toughness <= 0) { game.logAppendMessage( getController(), MagicMessage.format("%s is put into its owner's graveyard.", this) ); game.addDelayedAction(new RemoveFromPlayAction(this, MagicLocationType.Graveyard)); } else if (hasState(MagicPermanentState.Destroyed)) { game.addDelayedAction(ChangeStateAction.Clear(this, MagicPermanentState.Destroyed)); game.addDelayedAction(new DestroyAction(this)); } else if (toughness - damage <= 0) { game.addDelayedAction(new DestroyAction(this)); } // Soulbond if (pairedCreature.isValid() && !pairedCreature.isCreature()) { game.logAppendMessage( getController(), MagicMessage.format("%s becomes unpaired as %s is no longer a creature.", this, pairedCreature) ); game.addDelayedAction(new SoulbondAction(this, pairedCreature, false)); } } if (isAura()) { //not targeting since Aura is already attached final MagicTargetChoice tchoice = new MagicTargetChoice(getAuraTargetChoice(), false); String reason = ""; if (isCreature()) { reason = "it is a creature."; } else if (!enchantedPermanent.isValid() || !game.isLegalTarget(getController(), this, tchoice, enchantedPermanent)) { reason = "it no longer enchants a valid permanent."; } else if (enchantedPermanent.hasProtectionFrom(this)) { reason = MagicMessage.format("%s has protection.", enchantedPermanent); } if (!reason.isEmpty()) { // 702.102e If an Aura with bestow is attached to an illegal object or player, it becomes unattached. // This is an exception to rule 704.5n. if (hasAbility(MagicAbility.Bestow)) { game.logAppendMessage( getController(), MagicMessage.format("%s becomes unattached as %s", this, reason) ); game.addDelayedAction(new AttachAction(this, MagicPermanent.NONE)); } else { // 704.5n game.logAppendMessage( getController(), MagicMessage.format("%s is put into its owner's graveyard as %s", this, reason) ); game.addDelayedAction(new RemoveFromPlayAction(this, MagicLocationType.Graveyard)); } } } if (isEquipment() && equippedCreature.isValid()) { String reason = ""; if (isCreature()) { reason = "it is a creature."; } else if (!equippedCreature.isCreature()) { reason = MagicMessage.format("%s is no longer a creature.", equippedCreature); } else if (equippedCreature.hasProtectionFrom(this)) { reason = MagicMessage.format("%s has protection.", equippedCreature); } if (!reason.isEmpty()) { game.logAppendMessage( getController(), MagicMessage.format("%s becomes unattached as %s", this, reason) ); game.addDelayedAction(new AttachAction(this, MagicPermanent.NONE)); } } // rule 704.5i If a planeswalker has loyalty 0, it's put into its owner's graveyard. if (isPlaneswalker() && getCounters(MagicCounterType.Loyalty) == 0) { game.logAppendMessage( getController(), MagicMessage.format("%s is put into its owner's graveyard.", this) ); game.addDelayedAction(new RemoveFromPlayAction(this, MagicLocationType.Graveyard)); } // Only support Sagas with 3 chapters for now. // The comprehensive rules doesn't say that all Sagas must have exactly 3 chapters though. if (isSaga() && getCounters(MagicCounterType.Lore) >= 3 && game.getStack().stream().noneMatch(item -> item.getSource() == this) && game.getPendingTriggers().stream().noneMatch(item -> item.getSource() == this)) { game.logAppendMessage( getController(), MagicMessage.format("Sacrifice %s.", this) ); game.addDelayedAction(new SacrificeAction(this)); } // +1/+1 and -1/-1 counters cancel each other out. final int plusCounters = getCounters(MagicCounterType.PlusOne); if (plusCounters > 0) { final int minusCounters = getCounters(MagicCounterType.MinusOne); if (minusCounters > 0) { final int amount = -Math.min(plusCounters, minusCounters); game.addDelayedAction(ChangeCountersAction.Enters(this, MagicCounterType.PlusOne, amount)); game.addDelayedAction(ChangeCountersAction.Enters(this, MagicCounterType.MinusOne, amount)); } } } public boolean hasProtectionFrom(final MagicSource source) { // from everything if (hasAbility(MagicAbility.ProtectionFromEverything)) { return true; } // from a color int numColors = 0; for (final MagicColor color : MagicColor.values()) { if (source.hasColor(color)) { numColors++; if (hasAbility(color.getProtectionAbility()) || hasAbility(MagicAbility.ProtectionFromAllColors)) { return true; } else if (source.isSpell() && hasAbility(MagicAbility.ProtectionFromColoredSpells)) { return true; } } } // from monocolored if (numColors == 1 && hasAbility(MagicAbility.ProtectionFromMonoColored)) { return true; } // from multicolored if (numColors > 1 && hasAbility(MagicAbility.ProtectionFromMultiColored)) { return true; } if (!source.isPermanent()) { return false; } final MagicPermanent permanent = (MagicPermanent) source; for (MagicTrigger trigger : cachedTriggers) { if (trigger.getType() == MagicTriggerType.Protection) { @SuppressWarnings("unchecked") final MagicTrigger protection = (MagicTrigger) trigger; if (protection.accept(this, permanent)) { return true; } } } return false; } public boolean canAttack() { if (hasAbility(MagicAbility.CannotAttack) || hasState(MagicPermanentState.CannotAttack) || hasAbility(MagicAbility.CannotAttackOrBlock) || (hasState(MagicPermanentState.ExcludeFromCombat) && !hasAbility(MagicAbility.AttacksEachTurnIfAble)) || (hasAbility(MagicAbility.Defender) && !hasAbility(MagicAbility.CanAttackWithDefender))) { return false; } return isCreature() && canTap(); } public boolean canBlock() { if (!isCreature() || isTapped() || hasState(MagicPermanentState.ExcludeFromCombat)) { return false; } return !hasAbility(MagicAbility.CannotAttackOrBlock) && !hasAbility(MagicAbility.CannotBlock); } public boolean canBeBlocked(final MagicPlayer defendingPlayer) { // Unblockable if (hasAbility(MagicAbility.Unblockable)) { return false; } // Landwalk for (final MagicSubType basicLand : MagicSubType.ALL_BASIC_LANDS) { if (hasAbility(basicLand.getLandwalkAbility()) && defendingPlayer.controlsPermanent(basicLand)) { return false; } } if (hasAbility(MagicAbility.Snowlandwalk) && defendingPlayer.controlsPermanent(MagicTargetFilterFactory.SNOW_LAND)) { return false; } if (hasAbility(MagicAbility.Snowswampwalk) && defendingPlayer.controlsPermanent(MagicTargetFilterFactory.SNOW_SWAMP)) { return false; } if (hasAbility(MagicAbility.Snowforestwalk) && defendingPlayer.controlsPermanent(MagicTargetFilterFactory.SNOW_FOREST)) { return false; } if (hasAbility(MagicAbility.LegendaryLandwalk) && defendingPlayer.controlsPermanent(MagicTargetFilterFactory.LEGENDARY_LAND)) { return false; } if (hasAbility(MagicAbility.NonbasicLandwalk) && defendingPlayer.controlsPermanent(MagicTargetFilterFactory.NONBASIC_LAND)) { return false; } return true; } public boolean canBlock(final MagicPermanent attacker) { // Fear-types and Intimidate if (!isArtifact()) { if (attacker.hasAbility(MagicAbility.Fear) && !hasColor(MagicColor.Black)) { return false; } if (attacker.hasAbility(MagicAbility.WhiteFear) && !hasColor(MagicColor.White)) { return false; } if (attacker.hasAbility(MagicAbility.RedFear) && !hasColor(MagicColor.Red)) { return false; } if (attacker.hasAbility(MagicAbility.Intimidate) && !shareColor(attacker)) { return false; } } // Shadow if (attacker.hasAbility(MagicAbility.Shadow)) { if (!hasAbility(MagicAbility.Shadow) && !hasAbility(MagicAbility.CanBlockShadow)) { return false; } } else if (hasAbility(MagicAbility.Shadow)) { return false; } if (!attacker.hasAbility(MagicAbility.Flying) && hasAbility(MagicAbility.CannotBlockWithoutFlying)) { return false; } // Reach if (attacker.hasAbility(MagicAbility.Flying) && !hasAbility(MagicAbility.Flying) && !hasAbility(MagicAbility.Reach)) { return false; } // Horsemanship if (attacker.hasAbility(MagicAbility.Horsemanship) && !hasAbility(MagicAbility.Horsemanship)) { return false; } //cannot be blocked by ... for (MagicTrigger trigger : attacker.getTriggers()) { if (trigger.getType() == MagicTriggerType.CannotBeBlocked) { @SuppressWarnings("unchecked") final MagicTrigger cannotBeBlocked = (MagicTrigger) trigger; if (cannotBeBlocked.accept(attacker, this)) { return false; } } } //can't block ... for (MagicTrigger trigger : getTriggers()) { if (trigger.getType() == MagicTriggerType.CantBlock) { @SuppressWarnings("unchecked") final MagicTrigger cantBlock = (MagicTrigger) trigger; if (cantBlock.accept(this, attacker)) { return false; } } } // Protection return !attacker.hasProtectionFrom(this); } public MagicPermanent getEquippedCreature() { return equippedCreature; } public void setEquippedCreature(final MagicPermanent creature) { equippedCreature = creature; } public MagicPermanentSet getEquipmentPermanents() { return equipmentPermanents; } public void addEquipment(final MagicPermanent equipment) { equipmentPermanents.add(equipment); } public void removeEquipment(final MagicPermanent equipment) { equipmentPermanents.remove(equipment); } public boolean isEquipped() { return equipmentPermanents.size() > 0; } public MagicPermanent getEnchantedPermanent() { return enchantedPermanent; } public void setEnchantedPermanent(final MagicPermanent perm) { enchantedPermanent = perm; } public MagicPermanentSet getAuraPermanents() { return auraPermanents; } public void addAura(final MagicPermanent aura) { auraPermanents.add(aura); } public void removeAura(final MagicPermanent aura) { auraPermanents.remove(aura); } public boolean isEnchanted() { return auraPermanents.size() > 0; } public int getAbilityPlayedThisTurn() { return abilityPlayedThisTurn; } public void setAbilityPlayedThisTurn(final int amount) { abilityPlayedThisTurn = amount; } public void incrementAbilityPlayedThisTurn() { abilityPlayedThisTurn++; } public void decrementAbilityPlayedThisTurn() { abilityPlayedThisTurn--; } private int getTypeFlags() { return cachedTypeFlags; } @Override public boolean hasType(final MagicType type) { return type.hasType(getTypeFlags()); } public boolean isBasic() { return hasType(MagicType.Basic); } public boolean isEquipment() { return isArtifact() && hasSubType(MagicSubType.Equipment); } @Override public boolean isArtifact() { return hasType(MagicType.Artifact); } @Override public boolean isEnchantment() { return hasType(MagicType.Enchantment); } public boolean isAura() { return isEnchantment() && hasSubType(MagicSubType.Aura); } public boolean isFaceDown() { return hasState(MagicPermanentState.FaceDown); } public boolean isFlipped() { return hasState(MagicPermanentState.Flipped); } @Override public boolean isSplitCard() { return false; } public boolean isTransformed() { return hasState(MagicPermanentState.Transformed); } public MagicTargetChoice getAuraTargetChoice() { if (isAura()) { final MagicPlayAuraEvent auraEvent = cardDefinition.isAura() ? (MagicPlayAuraEvent) cardDefinition.getCardEvent() : MagicBestowActivation.BestowEvent; return auraEvent.getTargetChoice(); } else { return MagicTargetChoice.NONE; } } @Override public boolean isSpell() { return false; } @Override public boolean isPlayer() { return false; } @Override public boolean isPermanent() { return true; } @Override public boolean isValidTarget(final MagicSource source) { // Can't be the target of spells or abilities. if (hasAbility(MagicAbility.Shroud)) { return false; } // Can't be the target of spells or abilities your opponents controls. if (hasAbility(MagicAbility.Hexproof) && isEnemy(source)) { return false; } // Can't be the target of spells or abilities player 0 controls. if (hasAbility(MagicAbility.CannotBeTheTarget0) && source.getController().getIndex() == 0) { return false; } // Can't be the target of spells or abilities player 1 controls. if (hasAbility(MagicAbility.CannotBeTheTarget1) && source.getController().getIndex() == 1) { return false; } // Can't be the target of nongreen spells or abilities from nongreen sources if (hasAbility(MagicAbility.CannotBeTheTargetOfNonGreen) && !source.hasColor(MagicColor.Green)) { return false; } // Can't be the target of black or red spell your opponent control if (hasAbility(MagicAbility.CannotBeTheTargetOfBlackOrRedOpponentSpell) && (source.hasColor(MagicColor.Black) || source.hasColor(MagicColor.Red)) && source.isSpell() && isEnemy(source)) { return false; } // Protection. return !hasProtectionFrom(source); } @Override public int compareTo(final MagicPermanent permanent) { // Important for sorting of permanent mana activations. final int diff = cardDefinition.getIndex() - permanent.cardDefinition.getIndex(); if (diff != 0) { return diff; } else { return Long.signum(id - permanent.id); } } @Override public boolean isLegalTarget(final MagicPlayer player, final MagicTargetFilter targetFilter) { return getController().controlsPermanent(this); } public static final MagicPermanent NONE = new MagicPermanent(-1L, MagicCard.NONE, MagicPlayer.NONE) { @Override public boolean isValid() { return false; } @Override public String toString() { return "MagicPermanent.NONE"; } @Override public MagicPermanent copy(final MagicCopyMap copyMap) { return this; } @Override public MagicPermanent map(final MagicGame game) { return this; } @Override public MagicPowerToughness getPowerToughness() { return new MagicPowerToughness(0, 0); } @Override public MagicPlayer getController() { return MagicPlayer.NONE; } @Override public MagicGame getGame() { throw new RuntimeException("getGame called for MagicPermanent.NONE"); } @Override public boolean hasColor(final MagicColor color) { return false; } @Override public boolean hasType(final MagicType type) { return false; } @Override public boolean hasSubType(final MagicSubType type) { return false; } @Override public Set getAbilityFlags() { return Collections.emptySet(); } @Override public boolean hasAbility(final MagicAbility ability) { return false; } @Override public long getStateId() { return hashCode(); } @Override public int getCounters(final MagicCounterType counterType) { return 0; } @Override public boolean hasCounters() { return false; } @Override public boolean canTap() { return false; } @Override public void changeCounters(final MagicCounterType counterType, final int amount) { //do nothing } @Override public void addEquipment(final MagicPermanent equipment) { //do nothing } @Override public void addAura(final MagicPermanent equipment) { //do nothing } @Override public void addAbility(final MagicActivation act) { } @Override public void addAbility(final MagicTrigger trig) { //do nothing } @Override public void addAbility(final MagicManaActivation act) { //do nothing } @Override public void addExiledCard(final MagicCard card) { //do nothing } @Override public void removeExiledCard(final MagicCard card) { //do nothing } }; @Override public Set getSubTypes() { return EnumSet.copyOf(cachedSubTypeFlags); } @Override public String getSubTypeText() { if (cachedSubTypeFlags.containsAll(MagicSubType.ALL_CREATURES)) { return getCardDefinition().getSubTypeText(); } else { StringBuilder subtypes = new StringBuilder(); for (final MagicSubType subType: cachedSubTypeFlags) { subtypes.append(subType).append(" "); } return subtypes.toString(); } } @Override public String getPowerToughnessText() { return isCreature() || hasSubType(MagicSubType.Vehicle) ? getPower() + "/" + getToughness() : ""; } public void appliedStatic(final MagicPermanent source, final MagicStatic mstatic) { appliedStatics.add(new SimpleImmutableEntry<>(source, mstatic)); } public boolean applied(final MagicPermanent source, final MagicStatic mstatic) { return appliedStatics.contains(new SimpleImmutableEntry<>(source, mstatic)); } public void clearStatics() { appliedStatics = null; } }