Java tutorial
/* * Forge: Play Magic: the Gathering. * Copyright (C) 2011 Forge Team * * This program is free software: 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 forge.game.player; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.LobbyPlayer; import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.game.*; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.DetachedCardEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardPredicates.Presets; import forge.game.event.*; import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordCollection.KeywordCollectionView; import forge.game.keyword.KeywordsChange; import forge.game.mana.ManaPool; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementResult; import forge.game.spellability.Ability; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZoneBattlefield; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.IPaperCard; import forge.util.Aggregates; import forge.util.collect.FCollection; import forge.util.Expressions; import forge.util.Lang; import forge.util.MyRandom; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentSkipListMap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; /** * <p> * Abstract Player class. * </p> * * @author Forge * @version $Id: Player.java 30003 2015-09-14 16:04:41Z Agetian $ */ public class Player extends GameEntity implements Comparable<Player> { public static final List<ZoneType> ALL_ZONES = Collections.unmodifiableList( Arrays.asList(ZoneType.Battlefield, ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante, ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck)); private final Map<Card, Integer> commanderDamage = new HashMap<Card, Integer>(); private int poisonCounters = 0; private int life = 20; private int startingLife = 20; private final Map<Card, Integer> assignedDamage = new HashMap<Card, Integer>(); private int spellsCastThisTurn = 0; private int landsPlayedThisTurn = 0; private int lifeLostThisTurn = 0; private int lifeLostLastTurn = 0; private int lifeGainedThisTurn = 0; private int numPowerSurgeLands; private int numLibrarySearchedOwn = 0; //The number of times this player has searched his library private int maxHandSize = 7; private int startingHandSize = 7; private boolean unlimitedHandSize = false; private Card lastDrawnCard = null; private String namedCard = ""; private int numDrawnThisTurn = 0; private int numDrawnThisDrawStep = 0; private int numDiscardedThisTurn = 0; private int numCardsInHandStartedThisTurnWith = 0; /** A list of tokens not in play, but on their way. * This list is kept in order to not break ETB-replacement * on tokens. */ private CardCollection inboundTokens = new CardCollection(); private KeywordCollection keywords = new KeywordCollection(); private Map<Long, KeywordsChange> changedKeywords = new ConcurrentSkipListMap<Long, KeywordsChange>(); private ManaPool manaPool = new ManaPool(this); private GameEntity mustAttackEntity = null; private boolean attackedWithCreatureThisTurn = false; private boolean activateLoyaltyAbilityThisTurn = false; private int attackersDeclaredThisTurn = 0; private final Map<ZoneType, PlayerZone> zones = new EnumMap<ZoneType, PlayerZone>(ZoneType.class); private CardCollection currentPlanes = new CardCollection(); private List<String> prowl = new ArrayList<String>(); private PlayerStatistics stats = new PlayerStatistics(); private PlayerController controller; private PlayerController controllerCreator = null; private Player mindSlaveMaster = null; private int teamNumber = -1; private Card activeScheme = null; private Card commander = null; private final Game game; private boolean triedToDrawFromEmptyLibrary = false; private boolean isPlayingExtraTrun = false; private CardCollection lostOwnership = new CardCollection(); private CardCollection gainedOwnership = new CardCollection(); private int numManaConversion = 0; private final AchievementTracker achievementTracker = new AchievementTracker(); private final PlayerView view; public Player(String name0, Game game0, final int id0) { super(id0); game = game0; for (final ZoneType z : Player.ALL_ZONES) { final PlayerZone toPut = z == ZoneType.Battlefield ? new PlayerZoneBattlefield(z, this) : new PlayerZone(z, this); zones.put(z, toPut); } view = new PlayerView(id0, game.getTracker()); view.updateMaxHandSize(this); view.updateKeywords(this); setName(chooseName(name0)); if (id0 >= 0) { game.addPlayer(id, this); } } public final AchievementTracker getAchievementTracker() { return achievementTracker; } public final PlayerOutcome getOutcome() { return stats.getOutcome(); } private String chooseName(String originalName) { String nameCandidate = originalName; for (int i = 2; i <= 8; i++) { // several tries, not matter how many boolean haveDuplicates = false; for (Player p : game.getPlayers()) { if (p.getName().equals(nameCandidate)) { haveDuplicates = true; break; } } if (!haveDuplicates) { return nameCandidate; } nameCandidate = Lang.getOrdinal(i) + " " + originalName; } return nameCandidate; } @Override public Game getGame() { return game; } public final PlayerStatistics getStats() { return stats; } public final int getTeam() { return teamNumber; } public final void setTeam(int iTeam) { teamNumber = iTeam; } public boolean isArchenemy() { return getZone(ZoneType.SchemeDeck).size() > 0; //Only the archenemy has schemes. } public void setSchemeInMotion() { for (final Player p : game.getPlayers()) { if (p.hasKeyword("Schemes can't be set in motion this turn.")) { return; } } // Replacement effects final HashMap<String, Object> repRunParams = new HashMap<String, Object>(); repRunParams.put("Event", "SetInMotion"); repRunParams.put("Affected", this); if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { return; } game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); activeScheme = getZone(ZoneType.SchemeDeck).get(0); // gameAction moveTo ? game.getAction().moveTo(ZoneType.Command, activeScheme); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Scheme", activeScheme); game.getTriggerHandler().runTrigger(TriggerType.SetInMotion, runParams, false); } /** * getOpponent. Used by current-generation AI. */ public final Player getOpponent() { for (Player p : game.getPlayers()) { if (p.isOpponentOf(this)) { return p; } } throw new IllegalStateException("No opponents left ingame for " + this); } public final Player getOtherPlayer() { for (Player p : game.getRegisteredPlayers()) { if (p.isOpponentOf(this)) { return p; } } throw new IllegalStateException("No opponents for " + this + "registered"); } /** * returns all opponents. * Should keep player relations somewhere in the match structure */ public final FCollection<Player> getOpponents() { FCollection<Player> result = new FCollection<Player>(); for (Player p : game.getPlayers()) { if (p.isOpponentOf(this)) { result.add(p); } } return result; } public void updateOpponentsForView() { view.updateOpponents(this); } public void updateFlashbackForView() { view.updateFlashbackForPlayer(this); } //get single opponent for player if only one, otherwise returns null //meant to be used after game ends for the sake of achievements public Player getSingleOpponent() { if (game.getRegisteredPlayers().size() == 2) { for (Player p : game.getRegisteredPlayers()) { if (p.isOpponentOf(this)) { return p; } } } return null; } /** * Find the smallest life total amongst this player's opponents. */ public final int getOpponentsSmallestLifeTotal() { return Aggregates.min(getOpponents(), Accessors.FN_GET_LIFE_TOTAL); } /** * Find the greatest life total amongst this player's opponents. */ public final int getOpponentsGreatestLifeTotal() { return Aggregates.max(getOpponents(), Accessors.FN_GET_LIFE_TOTAL); } /** * Get the total number of poison counters amongst this player's opponents. */ public final int getOpponentsTotalPoisonCounters() { return Aggregates.sum(getOpponents(), Accessors.FN_GET_POISON_COUNTERS); } /** * returns allied players. * Should keep player relations somewhere in the match structure */ public final FCollection<Player> getAllies() { FCollection<Player> result = new FCollection<Player>(); for (Player p : game.getPlayers()) { if (!p.isOpponentOf(this)) { result.add(p); } } return result; } /** * returns all other players. * Should keep player relations somewhere in the match structure */ public final FCollection<Player> getAllOtherPlayers() { FCollection<Player> result = new FCollection<Player>(game.getPlayers()); result.remove(this); return result; } /** * returns the weakest opponent (based on life totals). * Should keep player relations somewhere in the match structure */ public final Player getWeakestOpponent() { List<Player> opponents = getOpponents(); Player weakest = opponents.get(0); for (int i = 1; i < opponents.size(); i++) { if (weakest.getLife() > opponents.get(i).getLife()) { weakest = opponents.get(i); } } return weakest; } public boolean isOpponentOf(Player other) { return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber); } public final boolean setLife(final int newLife, final Card source) { boolean change = false; // rule 118.5 if (life > newLife) { change = (loseLife(life - newLife) > 0); } else if (newLife > life) { change = gainLife(newLife - life, source); } else { // life == newLife change = false; } return change; } public final int getStartingLife() { return startingLife; } public final void setStartingLife(final int startLife) { //Should only be called from newGame(). startingLife = startLife; life = startLife; view.updateLife(this); } public final int getLife() { return life; } public final boolean gainLife(final int toGain, final Card source) { // Run any applicable replacement effects. final HashMap<String, Object> repParams = new HashMap<String, Object>(); repParams.put("Event", "GainLife"); repParams.put("Affected", this); repParams.put("LifeGained", toGain); repParams.put("Source", source); if (!canGainLife()) { return false; } if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { return false; } boolean newLifeSet = false; final int lifeGain = toGain; if (lifeGain > 0) { int oldLife = life; life += lifeGain; view.updateLife(this); newLifeSet = true; lifeGainedThisTurn += lifeGain; // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Player", this); runParams.put("LifeAmount", lifeGain); game.getTriggerHandler().runTrigger(TriggerType.LifeGained, runParams, false); game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); } else { System.out.println("Player - trying to gain negative or 0 life"); } return newLifeSet; } public final boolean canGainLife() { if (hasKeyword("You can't gain life.") || hasKeyword("Your life total can't change.")) { return false; } return true; } public final int loseLife(final int toLose) { int lifeLost = 0; if (!canLoseLife()) { return 0; } if (toLose > 0) { int oldLife = life; life -= toLose; view.updateLife(this); lifeLost = toLose; game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); } else if (toLose == 0) { // Rule 118.4 // this is for players being able to pay 0 life nothing to do } else { System.out.println("Player - trying to lose negative life"); return 0; } lifeLostThisTurn += toLose; // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Player", this); runParams.put("LifeAmount", toLose); game.getTriggerHandler().runTrigger(TriggerType.LifeLost, runParams, false); return lifeLost; } public final boolean canLoseLife() { if (hasKeyword("Your life total can't change.")) { return false; } return true; } public final boolean canPayLife(final int lifePayment) { if (life < lifePayment) { return false; } if ((lifePayment > 0) && hasKeyword("Your life total can't change.")) { return false; } return true; } public final boolean payLife(final int lifePayment, final Card source) { if (!canPayLife(lifePayment)) { return false; } if (lifePayment <= 0) return true; // rule 118.8 if (life >= lifePayment) { return (loseLife(lifePayment) > 0); } return false; } // This function handles damage after replacement and prevention effects are applied @Override public final boolean addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { if (amount <= 0) { return false; } //String additionalLog = ""; source.addDealtDamageToPlayerThisTurn(getName(), amount); if (isCombat) { game.getCombat().addDealtDamageTo(source, this); } boolean infect = source.hasKeyword("Infect") || hasKeyword("All damage is dealt to you as though its source had infect."); if (infect) { addPoisonCounters(amount, source); } else { // Worship does not reduce the damage dealt but changes the effect // of the damage if (hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.") && life <= amount) { loseLife(Math.min(amount, life - 1)); } else { // rule 118.2. Damage dealt to a player normally causes that player to lose that much life. loseLife(amount); } } //Tiny Leaders ignore commander damage rule. if (source.isCommander() && isCombat && this.getGame().getRules().getGameType() != GameType.TinyLeaders) { commanderDamage.put(source, getCommanderDamage(source) + amount); view.updateCommanderDamage(this); } assignedDamage.put(source, amount); if (source.hasKeyword("Lifelink")) { source.getController().gainLife(amount, source); } source.getDamageHistory().registerDamage(this); if (isCombat) { for (final String type : source.getType()) { source.getController().addProwlType(type); } } // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("DamageSource", source); runParams.put("DamageTarget", this); runParams.put("DamageAmount", amount); runParams.put("IsCombatDamage", isCombat); // Defending player at the time the damage was dealt runParams.put("DefendingPlayer", game.getCombat() != null ? game.getCombat().getDefendingPlayerRelatedTo(source) : null); game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); return true; } // This should be also usable by the AI to forecast an effect (so it must not change the game state) @Override public final int staticDamagePrevention(final int damage, final Card source, final boolean isCombat, final boolean isTest) { if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { return damage; } if (isCombat && game.getPhaseHandler().isPreventCombatDamageThisTurn()) { return 0; } if (hasProtectionFrom(source)) { return 0; } int restDamage = damage; for (String kw : source.getKeywords()) { if (isCombat) { if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { return 0; } if (kw.equals("Prevent all combat damage that would be dealt by CARDNAME.")) { return 0; } } if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { return 0; } if (kw.equals("Prevent all damage that would be dealt by CARDNAME.")) { return 0; } } // Prevent Damage static abilities for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { final Iterable<StaticAbility> staticAbilities = ca.getStaticAbilities(); for (final StaticAbility stAb : staticAbilities) { restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest); } } if (restDamage > 0) { return restDamage; } return 0; } // This is usable by the AI to forecast an effect (so it must // not change the game state) // 2012/01/02: No longer used in calculating the finalized damage, but // retained for damageprediction. -Hellfish @Override public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { int restDamage = damage; if (hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.")) { restDamage = Math.min(restDamage, life - 1); } for (Card c : game.getCardsIn(ZoneType.Battlefield)) { if (c.getName().equals("Sulfuric Vapors")) { if (source.isSpell() && source.isRed()) { restDamage += 1; } } else if (c.getName().equals("Pyromancer's Swath")) { if (c.getController().equals(source.getController()) && (source.isInstant() || source.isSorcery())) { restDamage += 2; } } else if (c.getName().equals("Pyromancer's Gauntlet")) { if (c.getController().equals(source.getController()) && source.isRed() && (source.isInstant() || source.isSorcery() || source.isPlaneswalker())) { restDamage += 2; } } else if (c.getName().equals("Furnace of Rath") || c.getName().equals("Dictate of the Twin Gods")) { restDamage += restDamage; } else if (c.getName().equals("Gratuitous Violence")) { if (c.getController().equals(source.getController()) && source.isCreature()) { restDamage += restDamage; } } else if (c.getName().equals("Fire Servant")) { if (c.getController().equals(source.getController()) && source.isRed() && (source.isInstant() || source.isSorcery())) { restDamage *= 2; } } else if (c.getName().equals("Curse of Bloodletting")) { if (c.getEnchanting().equals(this)) { restDamage *= 2; } } else if (c.getName().equals("Gisela, Blade of Goldnight")) { if (!c.getController().equals(this)) { restDamage *= 2; } } else if (c.getName().equals("Inquisitor's Flail")) { if (isCombat && c.getEquipping() != null && c.getEquipping().equals(source)) { restDamage *= 2; } } else if (c.getName().equals("Ghosts of the Innocent")) { restDamage = restDamage / 2; } else if (c.getName().equals("Benevolent Unicorn")) { if (source.isSpell()) { restDamage -= 1; } } else if (c.getName().equals("Divine Presence")) { if (restDamage > 3) { restDamage = 3; } } else if (c.getName().equals("Forethought Amulet")) { if (c.getController().equals(this) && (source.isInstant() || source.isSorcery()) && restDamage > 2) { restDamage = 2; } } else if (c.getName().equals("Elderscale Wurm")) { if (c.getController().equals(this) && getLife() - restDamage < 7) { restDamage = getLife() - 7; if (restDamage < 0) { restDamage = 0; } } } } return restDamage; } @Override public final int replaceDamage(final int damage, final Card source, final boolean isCombat) { // Replacement effects final HashMap<String, Object> repParams = new HashMap<String, Object>(); repParams.put("Event", "DamageDone"); repParams.put("Affected", this); repParams.put("DamageSource", source); repParams.put("DamageAmount", damage); repParams.put("IsCombat", isCombat); if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { return 0; } return damage; } @Override public final int preventDamage(final int damage, final Card source, final boolean isCombat) { if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) || source.hasKeyword("Damage that would be dealt by CARDNAME can't be prevented.")) { return damage; } int restDamage = damage; boolean DEBUGShieldsWithEffects = false; while (!getPreventNextDamageWithEffect().isEmpty() && restDamage != 0) { Map<Card, Map<String, String>> shieldMap = getPreventNextDamageWithEffect(); CardCollection preventionEffectSources = new CardCollection(shieldMap.keySet()); Card shieldSource = preventionEffectSources.get(0); if (preventionEffectSources.size() > 1) { Map<String, Card> choiceMap = new TreeMap<String, Card>(); List<String> choices = new ArrayList<String>(); for (final Card key : preventionEffectSources) { String effDesc = shieldMap.get(key).get("EffectString"); int descIndex = effDesc.indexOf("SpellDescription"); effDesc = effDesc.substring(descIndex + 18); String shieldDescription = key.toString() + " - " + shieldMap.get(shieldSource).get("ShieldAmount") + " shields - " + effDesc; choices.add(shieldDescription); choiceMap.put(shieldDescription, key); } shieldSource = getController().chooseProtectionShield(this, choices, choiceMap); } if (DEBUGShieldsWithEffects) { System.out.println("Prevention shield source: " + shieldSource); } int shieldAmount = Integer.valueOf(shieldMap.get(shieldSource).get("ShieldAmount")); int dmgToBePrevented = Math.min(restDamage, shieldAmount); if (DEBUGShieldsWithEffects) { System.out.println("Selected source initial shield amount: " + shieldAmount); System.out.println("Incoming damage: " + restDamage); System.out.println("Damage to be prevented: " + dmgToBePrevented); } //Set up ability SpellAbility shieldSA = null; String effectAbString = shieldMap.get(shieldSource).get("EffectString"); effectAbString = effectAbString.replace("PreventedDamage", Integer.toString(dmgToBePrevented)); effectAbString = effectAbString.replace("ShieldEffectTarget", shieldMap.get(shieldSource).get("ShieldEffectTarget")); if (DEBUGShieldsWithEffects) { System.out.println("Final shield ability string: " + effectAbString); } shieldSA = AbilityFactory.getAbility(effectAbString, shieldSource); if (shieldSA.usesTargeting()) { System.err.println(shieldSource + " - Targeting for prevention shield's effect should be done with initial spell"); } boolean apiIsEffect = (shieldSA.getApi() == ApiType.Effect); CardCollectionView cardsInCommand = null; if (apiIsEffect) { cardsInCommand = getGame().getCardsIn(ZoneType.Command); } getController().playSpellAbilityNoStack(shieldSA, true); if (apiIsEffect) { CardCollection newCardsInCommand = new CardCollection(getGame().getCardsIn(ZoneType.Command)); newCardsInCommand.removeAll(cardsInCommand); if (!newCardsInCommand.isEmpty()) { newCardsInCommand.get(0).setSVar("PreventedDamage", "Number$" + Integer.toString(dmgToBePrevented)); } } subtractPreventNextDamageWithEffect(shieldSource, restDamage); restDamage = restDamage - dmgToBePrevented; if (DEBUGShieldsWithEffects) { System.out.println("Remaining shields: " + (shieldMap.containsKey(shieldSource) ? shieldMap.get(shieldSource).get("ShieldAmount") : "all shields used")); System.out.println("Remaining damage: " + restDamage); } } final HashMap<String, Object> repParams = new HashMap<String, Object>(); repParams.put("Event", "DamageDone"); repParams.put("Affected", this); repParams.put("DamageSource", source); repParams.put("DamageAmount", damage); repParams.put("IsCombat", isCombat); repParams.put("Prevention", true); if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { return 0; } restDamage = staticDamagePrevention(restDamage, source, isCombat, false); if (restDamage >= getPreventNextDamage()) { restDamage = restDamage - getPreventNextDamage(); setPreventNextDamage(0); } else { setPreventNextDamage(getPreventNextDamage() - restDamage); restDamage = 0; } return restDamage; } public final void clearAssignedDamage() { assignedDamage.clear(); } public final int getAssignedDamage() { int num = 0; for (final Integer value : assignedDamage.values()) { num += value; } return num; } public final Iterable<Card> getAssignedDamageSources() { return assignedDamage.keySet(); } public final int getAssignedDamage(final String type) { final Map<Card, Integer> valueMap = new HashMap<Card, Integer>(); for (final Card c : assignedDamage.keySet()) { if (c.getType().hasStringType(type)) { valueMap.put(c, assignedDamage.get(c)); } } int num = 0; for (final Integer value : valueMap.values()) { num += value; } return num; } /** * Get the total damage assigned to this player's opponents this turn. */ public final int getOpponentsAssignedDamage() { return Aggregates.sum(getOpponents(), Accessors.FN_GET_ASSIGNED_DAMAGE); } /** * Get the greatest amount of damage assigned to a single opponent this turn. */ public final int getMaxOpponentAssignedDamage() { return Aggregates.max(getOpponents(), Accessors.FN_GET_ASSIGNED_DAMAGE); } public final boolean addCombatDamage(final int damage, final Card source) { int damageToDo = damage; damageToDo = replaceDamage(damageToDo, source, true); damageToDo = preventDamage(damageToDo, source, true); addDamageAfterPrevention(damageToDo, source, true); // damage prevention is already checked if (damageToDo > 0) { GameActionUtil.executeCombatDamageToPlayerEffects(this, source, damageToDo); return true; } return false; } public final int getPoisonCounters() { return poisonCounters; } public final void setPoisonCounters(final int num, Card source) { if (poisonCounters == num) { return; } int oldPoison = poisonCounters; poisonCounters = num; view.updatePoisonCounters(this); game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num)); } public final void addPoisonCounters(final int num, final Card source) { if (!hasKeyword("You can't get poison counters")) { setPoisonCounters(poisonCounters + num, source); } } public final void removePoisonCounters(final int num, final Card source) { setPoisonCounters(poisonCounters - num, source); } public final void addChangedKeywords(final String[] addKeywords, final String[] removeKeywords, final Long timestamp) { addChangedKeywords(ImmutableList.copyOf(addKeywords), ImmutableList.copyOf(removeKeywords), timestamp); } public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) { // if the key already exists - merge entries if (changedKeywords.containsKey(timestamp)) { final List<String> kws = addKeywords == null ? Lists.<String>newArrayList() : Lists.newArrayList(addKeywords); final List<String> rkws = removeKeywords == null ? Lists.<String>newArrayList() : Lists.newArrayList(removeKeywords); final KeywordsChange cks = changedKeywords.get(timestamp); kws.addAll(cks.getKeywords()); rkws.addAll(cks.getRemoveKeywords()); changedKeywords.put(timestamp, new KeywordsChange(kws, rkws, cks.isRemoveAllKeywords())); updateKeywords(); return; } changedKeywords.put(timestamp, new KeywordsChange(addKeywords, removeKeywords, false)); updateKeywords(); game.fireEvent(new GameEventPlayerStatsChanged(this)); } public final KeywordsChange removeChangedKeywords(final Long timestamp) { KeywordsChange change = changedKeywords.remove(Long.valueOf(timestamp)); if (change != null) { updateKeywords(); game.fireEvent(new GameEventPlayerStatsChanged(this)); } return change; } /** * Append a keyword change which adds the specified keyword. * @param keyword the keyword to add. */ public final void addKeyword(final String keyword) { addChangedKeywords(ImmutableList.of(keyword), ImmutableList.<String>of(), getGame().getNextTimestamp()); } /** * Replace all instances of added keywords. * @param oldKeyword the keyword to replace. * @param newKeyword the keyword with which to replace. */ private final void replaceAllKeywordInstances(final String oldKeyword, final String newKeyword) { boolean keywordReplaced = false; for (final KeywordsChange ck : changedKeywords.values()) { if (ck.getKeywords().remove(oldKeyword)) { ck.getKeywords().add(newKeyword); keywordReplaced = true; } } if (keywordReplaced) { updateKeywords(); } } /** * Remove all keyword changes which grant this {@link Player} the specified * keyword. * @param keyword the keyword to remove. */ public final void removeKeyword(final String keyword) { boolean keywordRemoved = false; for (final KeywordsChange ck : changedKeywords.values()) { if (ck.getKeywords().remove(keyword)) { keywordRemoved = true; } } // Remove the empty changes for (final Entry<Long, KeywordsChange> ck : ImmutableList.copyOf(changedKeywords.entrySet())) { if (ck.getValue().isEmpty() && changedKeywords.remove(ck.getKey()) != null) { keywordRemoved = true; } } if (keywordRemoved) { updateKeywords(); game.fireEvent(new GameEventPlayerStatsChanged(this)); } } @Override public final boolean hasKeyword(final String keyword) { return keywords.contains(keyword); } private void updateKeywords() { keywords.clear(); // see if keyword changes are in effect for (final KeywordsChange ck : changedKeywords.values()) { if (ck.isRemoveAllKeywords()) { keywords.clear(); } else if (ck.getRemoveKeywords() != null) { keywords.removeAll(ck.getRemoveKeywords()); } if (ck.getKeywords() != null) { keywords.addAll(ck.getKeywords()); } } view.updateKeywords(this); } public final KeywordCollectionView getKeywords() { return keywords.getView(); } @Override public final boolean canBeTargetedBy(final SpellAbility sa) { if (hasKeyword("Shroud") || (!equals(sa.getActivatingPlayer()) && hasKeyword("Hexproof")) || hasProtectionFrom(sa.getHostCard())) { return false; } return true; } @Override public boolean hasProtectionFrom(final Card source) { for (String kw : keywords) { if (kw.startsWith("Protection")) { if (kw.startsWith("Protection:")) { // uses isValid final String characteristic = kw.split(":")[1]; final String[] characteristics = characteristic.split(","); if (source.isValid(characteristics, this, null)) { return true; } } else { switch (kw) { case "Protection from white": if (source.isWhite()) { return true; } break; case "Protection from blue": if (source.isBlue()) { return true; } break; case "Protection from black": if (source.isBlack()) { return true; } break; case "Protection from red": if (source.isRed()) { return true; } break; case "Protection from green": if (source.isGreen()) { return true; } break; } } } } return false; } public final boolean canDraw() { if (hasKeyword("You can't draw cards.")) { return false; } if (hasKeyword("You can't draw more than one card each turn.")) { return numDrawnThisTurn < 1; } return true; } public final CardCollectionView drawCard() { return drawCards(1); } public void scry(final int numScry) { final CardCollection topN = new CardCollection(); final PlayerZone library = getZone(ZoneType.Library); final int actualNumScry = Math.min(numScry, library.size()); if (actualNumScry == 0) { return; } for (int i = 0; i < actualNumScry; i++) { topN.add(library.get(i)); } final ImmutablePair<CardCollection, CardCollection> lists = getController().arrangeForScry(topN); final CardCollection toTop = lists.getLeft(); final CardCollection toBottom = lists.getRight(); int numToBottom = 0; int numToTop = 0; if (toBottom != null) { for (Card c : toBottom) { getGame().getAction().moveToBottomOfLibrary(c); numToBottom++; } } if (toTop != null) { Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. for (Card c : toTop) { getGame().getAction().moveToLibrary(c); numToTop++; } } getGame().fireEvent(new GameEventScry(this, numToTop, numToBottom)); final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Player", this); getGame().getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false); } public boolean canMulligan() { return !getZone(ZoneType.Hand).isEmpty(); } public final CardCollectionView drawCards(final int n) { final CardCollection drawn = new CardCollection(); final CardCollection toReveal = new CardCollection(); for (int i = 0; i < n; i++) { if (!canDraw()) { return drawn; } drawn.addAll(doDraw(toReveal)); } if (toReveal.size() > 1) { // reveal multiple drawn cards when playing with the top of the library revealed game.getAction().reveal(toReveal, this, true, "Revealing cards drawn from "); } return drawn; } /** * @return a CardCollectionView of cards actually drawn */ private CardCollectionView doDraw(CardCollection revealed) { final CardCollection drawn = new CardCollection(); final PlayerZone library = getZone(ZoneType.Library); // Replacement effects final HashMap<String, Object> repRunParams = new HashMap<String, Object>(); repRunParams.put("Event", "Draw"); repRunParams.put("Affected", this); if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { return drawn; } if (!library.isEmpty()) { Card c = library.get(0); boolean topCardRevealed = c.hasKeyword("Your opponent may look at this card."); c = game.getAction().moveToHand(c); drawn.add(c); if (topCardRevealed) { revealed.add(c); } if (numDrawnThisTurn == 0) { boolean reveal = false; final CardCollectionView cards = getCardsIn(ZoneType.Battlefield); for (final Card card : cards) { if (card.hasKeyword("Reveal the first card you draw each turn") || (card.hasKeyword("Reveal the first card you draw on each of your turns") && game.getPhaseHandler().isPlayerTurn(this))) { reveal = true; break; } } if (reveal) { game.getAction().reveal(drawn, this, true, "Revealing the first card drawn from "); if (revealed.contains(c)) { revealed.remove(c); } } } setLastDrawnCard(c); c.setDrawnThisTurn(true); numDrawnThisTurn++; if (game.getPhaseHandler().is(PhaseType.DRAW)) { numDrawnThisDrawStep++; } view.updateNumDrawnThisTurn(this); // Miracle draws if (numDrawnThisTurn == 1 && game.getAge() != GameStage.Mulligan) { drawMiracle(c); } // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Card", c); runParams.put("Number", numDrawnThisTurn); runParams.put("Player", this); game.getTriggerHandler().runTrigger(TriggerType.Drawn, runParams, false); } else { // Lose by milling is always on. Give AI many cards it cannot play if you want it not to undertake actions triedToDrawFromEmptyLibrary = true; } return drawn; } /** * Returns PlayerZone corresponding to the given zone of game. */ public final PlayerZone getZone(final ZoneType zone) { return zones.get(zone); } public void updateZoneForView(PlayerZone zone) { view.updateZone(zone); } public final CardCollectionView getCardsIn(final ZoneType zoneType) { return getCardsIn(zoneType, true); } /** * gets a list of all cards in the requested zone. This function makes a CardCollectionView from Card[]. */ public final CardCollectionView getCardsIn(final ZoneType zoneType, boolean filterOutPhasedOut) { if (zoneType == ZoneType.Stack) { CardCollection cards = new CardCollection(); for (Card c : game.getStackZone().getCards()) { if (c.getOwner().equals(this)) { cards.add(c); } } return cards; } else if (zoneType == ZoneType.Flashback) { return getCardsActivableInExternalZones(true); } PlayerZone zone = getZone(zoneType); return zone == null ? CardCollection.EMPTY : zone.getCards(filterOutPhasedOut); } public final CardCollectionView getCardsIncludePhasingIn(final ZoneType zone) { return getCardsIn(zone, false); } /** * gets a list of first N cards in the requested zone. This function makes a CardCollectionView from Card[]. */ public final CardCollectionView getCardsIn(final ZoneType zone, final int n) { return new CardCollection(Iterables.limit(getCardsIn(zone), n)); } /** * gets a list of all cards in a given player's requested zones. */ public final CardCollectionView getCardsIn(final Iterable<ZoneType> zones) { final CardCollection result = new CardCollection(); for (final ZoneType z : zones) { result.addAll(getCardsIn(z)); } return result; } public final CardCollectionView getCardsIn(final ZoneType[] zones) { final CardCollection result = new CardCollection(); for (final ZoneType z : zones) { result.addAll(getCardsIn(z)); } return result; } /** * gets a list of all cards with requested cardName in a given player's * requested zone. This function makes a CardCollectionView from Card[]. */ public final CardCollectionView getCardsIn(final ZoneType zone, final String cardName) { return CardLists.filter(getCardsIn(zone), CardPredicates.nameEquals(cardName)); } public CardCollectionView getCardsActivableInExternalZones(boolean includeCommandZone) { final CardCollection cl = new CardCollection(); cl.addAll(getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); cl.addAll(getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); cl.addAll(getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); if (includeCommandZone) { cl.addAll(getZone(ZoneType.Command).getCardsPlayerCanActivate(this)); } //External activatables from all opponents for (final Player opponent : getOpponents()) { cl.addAll(opponent.getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); cl.addAll(opponent.getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); cl.addAll(opponent.getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); if (opponent.hasKeyword("Play with your hand revealed.")) { cl.addAll(opponent.getZone(ZoneType.Hand).getCardsPlayerCanActivate(this)); } } cl.addAll(getGame().getCardsPlayerCanActivateInStack()); return cl; } public final CardCollectionView getAllCards() { return CardCollection.combine(getCardsIn(Player.ALL_ZONES), getCardsIn(ZoneType.Stack), inboundTokens); } protected final CardCollectionView getDredge() { final CardCollection dredge = new CardCollection(); int cntLibrary = getCardsIn(ZoneType.Library).size(); for (final Card c : getCardsIn(ZoneType.Graveyard)) { int nDr = getDredgeNumber(c); if (nDr > 0 && cntLibrary >= nDr) { dredge.add(c); } } return dredge; } private static int getDredgeNumber(final Card c) { for (final String s : c.getKeywords()) { if (s.startsWith("Dredge")) { return Integer.parseInt("" + s.charAt(s.length() - 1)); } } return 0; } public final void resetNumDrawnThisTurn() { numDrawnThisTurn = 0; numDrawnThisDrawStep = 0; view.updateNumDrawnThisTurn(this); } public final int getNumDrawnThisTurn() { return numDrawnThisTurn; } public final int numDrawnThisDrawStep() { return numDrawnThisDrawStep; } public final Card discard(final Card c, final SpellAbility sa) { // TODO: This line should be moved inside CostPayment somehow /*if (sa != null) { sa.addCostToHashList(c, "Discarded"); }*/ final Card source = sa != null ? sa.getHostCard() : null; // Replacement effects final HashMap<String, Object> repRunParams = new HashMap<String, Object>(); repRunParams.put("Event", "Discard"); repRunParams.put("Card", c); repRunParams.put("Source", source); repRunParams.put("Affected", this); if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { return null; } boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary"); boolean discardMadness = sa != null && sa.hasParam("Madness"); StringBuilder sb = new StringBuilder(); sb.append(this).append(" discards ").append(c); final Card newCard; if (discardToTopOfLibrary) { newCard = game.getAction().moveToLibrary(c, 0); sb.append(" to the library"); // Play the Discard sound } else if (discardMadness) { newCard = game.getAction().exile(c); sb.append(" with Madness"); } else { newCard = game.getAction().moveToGraveyard(c); // Play the Discard sound } sb.append("."); numDiscardedThisTurn++; // Run triggers Card cause = null; if (sa != null) { cause = sa.getHostCard(); } final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Player", this); runParams.put("Card", c); runParams.put("Cause", cause); runParams.put("IsMadness", Boolean.valueOf(discardMadness)); game.getTriggerHandler().runTrigger(TriggerType.Discarded, runParams, false); game.getGameLog().add(GameLogEntryType.DISCARD, sb.toString()); return newCard; } public final int getNumDiscardedThisTurn() { return numDiscardedThisTurn; } public final void resetNumDiscardedThisTurn() { numDiscardedThisTurn = 0; } public int getNumCardsInHandStartedThisTurnWith() { return numCardsInHandStartedThisTurnWith; } public void setNumCardsInHandStartedThisTurnWith(int num) { numCardsInHandStartedThisTurnWith = num; } public final CardCollectionView mill(final int n) { return mill(n, ZoneType.Graveyard, false); } public final CardCollectionView mill(final int n, final ZoneType zone, final boolean bottom) { final CardCollectionView lib = getCardsIn(ZoneType.Library); final CardCollection milled = new CardCollection(); final int max = Math.min(n, lib.size()); final ZoneType destination = getZone(zone).getZoneType(); for (int i = 0; i < max; i++) { if (bottom) { milled.add(game.getAction().moveTo(destination, lib.getLast())); } else { milled.add(game.getAction().moveTo(destination, lib.getFirst())); } } return milled; } public final CardCollection getTopXCardsFromLibrary(int amount) { final CardCollection topCards = new CardCollection(); final PlayerZone lib = this.getZone(ZoneType.Library); int maxCards = lib.size(); // If library is smaller than N, only get that many cards maxCards = Math.min(maxCards, amount); // show top n cards: for (int j = 0; j < maxCards; j++) { topCards.add(lib.get(j)); } return topCards; } public final void shuffle(final SpellAbility sa) { final CardCollection list = new CardCollection(getCardsIn(ZoneType.Library)); if (list.size() <= 1) { return; } // overdone but wanted to make sure it was really random final Random random = MyRandom.getRandom(); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); int s = list.size(); for (int i = 0; i < s; i++) { list.add(random.nextInt(s - 1), list.remove(random.nextInt(s))); } Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); Collections.shuffle(list, random); getZone(ZoneType.Library).setCards(getController().cheatShuffle(list)); // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Player", this); runParams.put("Source", sa); game.getTriggerHandler().runTrigger(TriggerType.Shuffled, runParams, false); // Play the shuffle sound game.fireEvent(new GameEventShuffle(this)); } public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { // Dakkon Blackblade Avatar will use a similar effect if (canPlayLand(land, ignoreZoneAndTiming)) { land.setController(this, 0); if (land.isFaceDown()) { land.turnFaceUp(); } game.getAction().moveTo(getZone(ZoneType.Battlefield), land); // play a sound game.fireEvent(new GameEventLandPlayed(this, land)); // Run triggers final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Card", land); game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); game.getStack().unfreezeStack(); addLandPlayedThisTurn(); return true; } game.getStack().unfreezeStack(); return false; } public final boolean canPlayLand(final Card land) { return canPlayLand(land, false); } public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming) { if (!ignoreZoneAndTiming && !canCastSorcery()) { return false; } // CantBeCast static abilities for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { final Iterable<StaticAbility> staticAbilities = ca.getStaticAbilities(); for (final StaticAbility stAb : staticAbilities) { if (stAb.applyAbility("CantPlayLand", land, this)) { return false; } } } if (land != null && !ignoreZoneAndTiming) { final boolean mayPlay = land.mayPlay(this) != null; if (land.getOwner() != this && !mayPlay) { return false; } final Zone zone = game.getZoneOf(land); if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !(mayPlay || land.hasStartOfKeyword("May be played"))))) { return false; } } // **** Check for land play limit per turn **** // Dev Mode if (getController().canPlayUnlimitedLands() || hasKeyword("You may play any number of additional lands on each of your turns.")) { return true; } // check for adjusted max lands play per turn int adjMax = 1; for (String keyword : keywords) { if (keyword.startsWith("AdjustLandPlays")) { final String[] k = keyword.split(":"); adjMax += Integer.valueOf(k[1]); } } if (landsPlayedThisTurn < adjMax) { return true; } return false; } public final ManaPool getManaPool() { return manaPool; } public void updateManaForView() { view.updateMana(this); } public final int getNumPowerSurgeLands() { return numPowerSurgeLands; } public final int setNumPowerSurgeLands(final int n) { numPowerSurgeLands = n; return numPowerSurgeLands; } public final Card getLastDrawnCard() { return lastDrawnCard; } private final Card setLastDrawnCard(final Card c) { lastDrawnCard = c; return lastDrawnCard; } public final String getNamedCard() { return namedCard; } public final void setNamedCard(final String s) { namedCard = s; } public final int getTurn() { return stats.getTurnsPlayed(); } public final void incrementTurn() { stats.nextTurn(); } public final boolean getActivateLoyaltyAbilityThisTurn() { return activateLoyaltyAbilityThisTurn; } public final void setActivateLoyaltyAbilityThisTurn(final boolean b) { activateLoyaltyAbilityThisTurn = b; } public final boolean getAttackedWithCreatureThisTurn() { return attackedWithCreatureThisTurn; } public final void setAttackedWithCreatureThisTurn(final boolean b) { attackedWithCreatureThisTurn = b; } public final int getAttackersDeclaredThisTurn() { return attackersDeclaredThisTurn; } public final void incrementAttackersDeclaredThisTurn() { attackersDeclaredThisTurn++; } public final void resetAttackersDeclaredThisTurn() { attackersDeclaredThisTurn = 0; } public final void altWinBySpellEffect(final String sourceName) { if (cantWin()) { System.out.println("Tried to win, but currently can't."); return; } setOutcome(PlayerOutcome.altWin(sourceName)); } public final boolean loseConditionMet(final GameLossReason state, final String spellName) { if (state != GameLossReason.OpponentWon) { if (cantLose()) { System.out.println("Tried to lose, but currently can't."); return false; } // Replacement effects final HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Affected", this); runParams.put("Event", "GameLoss"); if (game.getReplacementHandler().run(runParams) != ReplacementResult.NotReplaced) { return false; } } setOutcome(PlayerOutcome.loss(state, spellName)); return true; } public final void concede() { // No cantLose checks - just lose setOutcome(PlayerOutcome.concede()); } public final boolean cantLose() { if (getOutcome() != null && getOutcome().lossState == GameLossReason.Conceded) { return false; } return (hasKeyword("You can't lose the game.") || Iterables.any(getOpponents(), Predicates.CANT_WIN)); } public final boolean cantLoseForZeroOrLessLife() { return (hasKeyword("You don't lose the game for having 0 or less life.")); } public final boolean cantWin() { boolean isAnyOppLoseProof = false; for (Player p : game.getPlayers()) { if (p == this || p.getOutcome() != null) { continue; // except self and already dead } isAnyOppLoseProof |= p.hasKeyword("You can't lose the game."); } return hasKeyword("You can't win the game.") || isAnyOppLoseProof; } public final boolean checkLoseCondition() { // Just in case player already lost if (getOutcome() != null) { return getOutcome().lossState != null; } // Rule 704.5a - If a player has 0 or less life, he or she loses the game. final boolean hasNoLife = getLife() <= 0; if (hasNoLife && !cantLoseForZeroOrLessLife()) { return loseConditionMet(GameLossReason.LifeReachedZero, null); } // Rule 704.5b - If a player attempted to draw a card from a library with no cards in it // since the last time state-based actions were checked, he or she loses the game. if (triedToDrawFromEmptyLibrary) { triedToDrawFromEmptyLibrary = false; // one-shot check return loseConditionMet(GameLossReason.Milled, null); } // Rule 704.5c - If a player has ten or more poison counters, he or she loses the game. if (poisonCounters >= 10) { return loseConditionMet(GameLossReason.Poisoned, null); } if (game.getRules().hasAppliedVariant(GameType.Commander)) { for (Entry<Card, Integer> entry : getCommanderDamage()) { if (entry.getValue() >= 21) { return loseConditionMet(GameLossReason.CommanderDamage, null); } } } return false; } public final boolean hasLost() { return getOutcome() != null && getOutcome().lossState != null; } public final boolean hasWon() { if (cantWin()) { return false; } // in multiplayer game one player's win is replaced by all other's lose (rule 103.4h) // so if someone cannot lose, the game appears to continue return getOutcome() != null && getOutcome().lossState == null; } public final boolean hasMetalcraft() { final CardCollectionView list = CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.ARTIFACTS); return list.size() >= 3; } public final boolean hasThreshold() { return getZone(ZoneType.Graveyard).size() >= 7; } public final boolean hasHellbent() { return getZone(ZoneType.Hand).isEmpty(); } public final boolean hasLandfall() { final CardCollectionView list = getZone(ZoneType.Battlefield).getCardsAddedThisTurn(null); return Iterables.any(list, CardPredicates.Presets.LANDS); } public final boolean hasBloodthirst() { for (Player opp : getOpponents()) { if (opp.getAssignedDamage() > 0) { return true; } } return false; } public boolean hasFerocious() { final CardCollectionView list = getCreaturesInPlay(); final CardCollectionView ferocious = CardLists.filter(list, new Predicate<Card>() { @Override public boolean apply(final Card c) { return c.getNetPower() > 3; } }); return !ferocious.isEmpty(); } public final int getBloodthirstAmount() { int blood = 0; for (Player opp : getOpponents()) { blood += opp.getAssignedDamage(); } return blood; } public final boolean hasProwl(final String type) { if (prowl.contains("AllCreatureTypes")) { return true; } return prowl.contains(type); } public final void addProwlType(final String type) { prowl.add(type); } public final void resetProwl() { prowl = new ArrayList<String>(); } public final void setLibrarySearched(final int l) { numLibrarySearchedOwn = l; } public final int getLibrarySearched() { return numLibrarySearchedOwn; } public final void incLibrarySearched() { numLibrarySearchedOwn++; } public final void setNumManaConversion(final int l) { numManaConversion = l; } public final boolean hasManaConversion() { return numManaConversion < keywords.getAmount( "You may spend mana as though" + " it were mana of any color to cast a spell this turn."); } public final void incNumManaConversion() { numManaConversion++; } public final void decNumManaConversion() { numManaConversion--; } @Override public final boolean isValid(final String restriction, final Player sourceController, final Card source) { final String[] incR = restriction.split("\\.", 2); if (incR[0].equals("Opponent")) { if (equals(sourceController) || !isOpponentOf(sourceController)) { return false; } } else if (incR[0].equals("You")) { if (!equals(sourceController)) { return false; } } else if (incR[0].equals("EnchantedController")) { final GameEntity enchanted = source.getEnchanting(); if ((enchanted == null) || !(enchanted instanceof Card)) { return false; } final Card enchantedCard = (Card) enchanted; if (!equals(enchantedCard.getController())) { return false; } } else { if (!incR[0].equals("Player")) { return false; } } if (incR.length > 1) { final String excR = incR[1]; final String[] exR = excR.split("\\+"); // Exclusive Restrictions // are ... for (int j = 0; j < exR.length; j++) { if (!hasProperty(exR[j], sourceController, source)) { return false; } } } return true; } @Override public final boolean hasProperty(final String property, final Player sourceController, final Card source) { if (property.equals("You")) { if (!equals(sourceController)) { return false; } } else if (property.equals("Opponent")) { if (equals(sourceController) || !isOpponentOf(sourceController)) { return false; } } else if (property.equals("Allies")) { if (equals(sourceController) || isOpponentOf(sourceController)) { return false; } } else if (property.equals("Active")) { if (!equals(game.getPhaseHandler().getPlayerTurn())) { return false; } } else if (property.equals("NonActive")) { if (equals(game.getPhaseHandler().getPlayerTurn())) { return false; } } else if (property.equals("OpponentToActive")) { final Player active = game.getPhaseHandler().getPlayerTurn(); if (equals(active) || !isOpponentOf(active)) { return false; } } else if (property.equals("Other")) { if (equals(sourceController)) { return false; } } else if (property.equals("OtherThanSourceOwner")) { if (equals(source.getOwner())) { return false; } } else if (property.equals("wasDealtDamageBySourceThisGame")) { if (!source.getDamageHistory().getThisGameDamaged().contains(this)) { return false; } } else if (property.equals("wasDealtDamageBySourceThisTurn")) { if (!source.getDamageHistory().getThisTurnDamaged().contains(this)) { return false; } } else if (property.equals("attackedBySourceThisCombat")) { if (game.getCombat() == null || !equals(game.getCombat().getDefenderPlayerByAttacker(source))) { return false; } } else if (property.startsWith("wasDealtDamageThisTurn")) { if (assignedDamage.isEmpty()) { return false; } } else if (property.startsWith("LostLifeThisTurn")) { if (lifeLostThisTurn <= 0) { return false; } } else if (property.startsWith("DeclaredAttackerThisTurn")) { if (attackersDeclaredThisTurn <= 0) { return false; } } else if (property.startsWith("NoCardsInHandAtBeginningOfTurn")) { if (numCardsInHandStartedThisTurnWith > 0) { return false; } } else if (property.startsWith("CardsInHandAtBeginningOfTurn")) { if (numCardsInHandStartedThisTurnWith <= 0) { return false; } } else if (property.startsWith("WithCardsInHand")) { if (property.contains("AtLeast")) { int amount = Integer.parseInt(property.split("AtLeast")[1]); if (getCardsIn(ZoneType.Hand).size() < amount) { return false; } } } else if (property.equals("IsRemembered")) { if (!source.isRemembered(this)) { return false; } } else if (property.equals("IsNotRemembered")) { if (source.isRemembered(this)) { return false; } } else if (property.startsWith("EnchantedBy")) { if (!isEnchantedBy(source)) { return false; } } else if (property.startsWith("Chosen")) { if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(this)) { return false; } } else if (property.startsWith("LifeEquals_")) { int life = AbilityUtils.calculateAmount(source, property.substring(11), null); if (getLife() != life) { return false; } } else if (property.equals("IsPoisoned")) { if (getPoisonCounters() <= 0) { return false; } } else if (property.startsWith("controls")) { final String[] type = property.substring(8).split("_"); final CardCollectionView list = CardLists.getValidCards(getCardsIn(ZoneType.Battlefield), type[0], sourceController, source); String comparator = type[1]; String compareTo = comparator.substring(2); int y = StringUtils.isNumeric(compareTo) ? Integer.parseInt(compareTo) : 0; if (!Expressions.compare(list.size(), comparator, y)) { return false; } } else if (property.startsWith("withMore")) { final String cardType = property.split("sThan")[0].substring(8); final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; final CardCollectionView oppList = CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); final CardCollectionView yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); if (oppList.size() <= yourList.size()) { return false; } } else if (property.startsWith("withAtLeast")) { final String cardType = property.split("More")[1].split("sThan")[0]; final int amount = Integer.parseInt(property.substring(11, 12)); final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; final CardCollectionView oppList = CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); final CardCollectionView yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); System.out.println(yourList.size()); if (oppList.size() < yourList.size() + amount) { return false; } } else if (property.startsWith("hasMore")) { final Player controller = property.contains("Than") && "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; if (property.substring(7).startsWith("Life") && getLife() <= controller.getLife()) { return false; } else if (property.substring(7).startsWith("CardsInHand") && getCardsIn(ZoneType.Hand).size() <= controller.getCardsIn(ZoneType.Hand).size()) { return false; } } else if (property.startsWith("hasFewer")) { final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; if (property.substring(8).startsWith("CreaturesInYard")) { final CardCollectionView oppList = CardLists.filter(getCardsIn(ZoneType.Graveyard), Presets.CREATURES); final CardCollectionView yourList = CardLists.filter(controller.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); if (oppList.size() >= yourList.size()) { return false; } } } else if (property.startsWith("withMost")) { if (property.substring(8).equals("Life")) { int highestLife = getLife(); // Negative base just in case a few Lich's are running around for (final Player p : game.getPlayers()) { if (p.getLife() > highestLife) { highestLife = p.getLife(); } } if (getLife() != highestLife) { return false; } } else if (property.substring(8).equals("CardsInHand")) { int largestHand = 0; Player withLargestHand = null; for (final Player p : game.getPlayers()) { if (p.getCardsIn(ZoneType.Hand).size() > largestHand) { largestHand = p.getCardsIn(ZoneType.Hand).size(); withLargestHand = p; } } if (!equals(withLargestHand)) { return false; } } else if (property.substring(8).startsWith("Type")) { String type = property.split("Type")[1]; boolean checkOnly = false; if (type.endsWith("Only")) { checkOnly = true; type = type.replace("Only", ""); } int typeNum = 0; List<Player> controlmost = new ArrayList<Player>(); for (final Player p : game.getPlayers()) { final int num = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), type).size(); if (num > typeNum) { typeNum = num; controlmost.clear(); } if (num == typeNum) { controlmost.add(p); } } if (checkOnly && controlmost.size() != 1) { return false; } if (!controlmost.contains(this)) { return false; } } } else if (property.startsWith("withLowest")) { if (property.substring(10).equals("Life")) { int lowestLife = getLife(); List<Player> lowestlifep = new ArrayList<Player>(); for (final Player p : game.getPlayers()) { if (p.getLife() == lowestLife) { lowestlifep.add(p); } else if (p.getLife() < lowestLife) { lowestLife = p.getLife(); lowestlifep.clear(); lowestlifep.add(p); } } if (!lowestlifep.contains(this)) { return false; } } } return true; } public final int getMaxHandSize() { return maxHandSize; } public final void setMaxHandSize(int size) { if (maxHandSize == size) { return; } maxHandSize = size; view.updateMaxHandSize(this); } public boolean isUnlimitedHandSize() { return unlimitedHandSize; } public void setUnlimitedHandSize(boolean unlimited) { if (unlimitedHandSize == unlimited) { return; } unlimitedHandSize = unlimited; view.updateUnlimitedHandSize(this); } public final int getLandsPlayedThisTurn() { return landsPlayedThisTurn; } public final void addLandPlayedThisTurn() { landsPlayedThisTurn++; achievementTracker.landsPlayed++; } public final void resetLandsPlayedThisTurn() { landsPlayedThisTurn = 0; } public final int getSpellsCastThisTurn() { return spellsCastThisTurn; } public final void addSpellCastThisTurn() { spellsCastThisTurn++; achievementTracker.spellsCast++; if (spellsCastThisTurn > achievementTracker.maxStormCount) { achievementTracker.maxStormCount = spellsCastThisTurn; } } public final void resetSpellsCastThisTurn() { spellsCastThisTurn = 0; } public final int getLifeGainedThisTurn() { return lifeGainedThisTurn; } public final void setLifeGainedThisTurn(final int n) { lifeGainedThisTurn = n; } public final int getLifeLostThisTurn() { return lifeLostThisTurn; } public final void setLifeLostThisTurn(final int n) { lifeLostThisTurn = n; } public final int getLifeLostLastTurn() { return lifeLostLastTurn; } public final void setLifeLostLastTurn(final int n) { lifeLostLastTurn = n; } /** * get the Player object or Card (Planeswalker) object that this Player must * attack this combat. * * @return the Player or Card (Planeswalker) * @since 1.1.01 */ public final GameEntity getMustAttackEntity() { return mustAttackEntity; } public final void setMustAttackEntity(final GameEntity o) { mustAttackEntity = o; } @Override public int compareTo(Player o) { if (o == null) { return 1; } return getName().compareTo(o.getName()); } public static class Predicates { public static final Predicate<Player> NOT_LOST = new Predicate<Player>() { @Override public boolean apply(Player p) { return p.getOutcome() == null || p.getOutcome().hasWon(); } }; public static final Predicate<Player> CANT_WIN = new Predicate<Player>() { @Override public boolean apply(final Player p) { return p.hasKeyword("You can't win the game."); } }; } public static class Accessors { public static Function<Player, String> FN_GET_NAME = new Function<Player, String>() { @Override public String apply(Player input) { return input.getName(); } }; public static Function<Player, Integer> FN_GET_LIFE_TOTAL = new Function<Player, Integer>() { @Override public Integer apply(Player input) { return input.getLife(); } }; public static Function<Player, Integer> FN_GET_POISON_COUNTERS = new Function<Player, Integer>() { @Override public Integer apply(Player input) { return input.getPoisonCounters(); } }; public static final Function<Player, Integer> FN_GET_ASSIGNED_DAMAGE = new Function<Player, Integer>() { @Override public Integer apply(Player input) { return input.getAssignedDamage(); } }; } public final LobbyPlayer getLobbyPlayer() { return getController().getLobbyPlayer(); } public final LobbyPlayer getOriginalLobbyPlayer() { return controllerCreator.getLobbyPlayer(); } public final RegisteredPlayer getRegisteredPlayer() { return game.getMatch().getPlayers().get(game.getRegisteredPlayers().indexOf(this)); } public final boolean isMindSlaved() { return mindSlaveMaster != null; } public final Player getMindSlaveMaster() { return mindSlaveMaster; } public final void setMindSlaveMaster(final Player mindSlaveMaster0) { if (mindSlaveMaster == mindSlaveMaster0) { return; } mindSlaveMaster = mindSlaveMaster0; view.updateMindSlaveMaster(this); if (mindSlaveMaster != null) { final LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); final PlayerController oldController = getController(); final IGameEntitiesFactory master = (IGameEntitiesFactory) mindSlaveMaster.getLobbyPlayer(); controller = master.createMindSlaveController(mindSlaveMaster, this); game.fireEvent( new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), controller)); } else { controller = controllerCreator; game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), controller, null, null)); } } private void setOutcome(PlayerOutcome outcome) { stats.setOutcome(outcome); } public void onGameOver() { if (null == stats.getOutcome()) { setOutcome(PlayerOutcome.win()); } } /** * use to get a list of creatures in play for a given player. */ public CardCollection getCreaturesInPlay() { return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); } /** * use to get a list of all lands a given player has on the battlefield. */ public CardCollection getLandsInPlay() { return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.LANDS); } public boolean isCardInPlay(final String cardName) { return getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(cardName)); } public boolean isCardInCommand(final String cardName) { return getZone(ZoneType.Command).contains(CardPredicates.nameEquals(cardName)); } public CardCollectionView getColoredCardsInPlay(final String color) { return CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isColor(MagicColor.fromName(color))); } public int getTokenDoublersMagnitude() { int tokenDoublers = keywords.getAmount("TokenDoubler"); return 1 << tokenDoublers; // pow(a,0) = 1; pow(a,1) = a } public final int getAmountOfKeyword(final String k) { return keywords.getAmount(k); } public void onCleanupPhase() { for (Card c : getCardsIn(ZoneType.Hand)) { c.setDrawnThisTurn(false); } resetPreventNextDamage(); resetPreventNextDamageWithEffect(); resetNumDrawnThisTurn(); resetNumDiscardedThisTurn(); setNumCardsInHandStartedThisTurnWith(getCardsIn(ZoneType.Hand).size()); setAttackedWithCreatureThisTurn(false); setActivateLoyaltyAbilityThisTurn(false); resetLandsPlayedThisTurn(); clearAssignedDamage(); resetAttackersDeclaredThisTurn(); } public boolean canCastSorcery() { PhaseHandler now = game.getPhaseHandler(); return now.isPlayerTurn(this) && now.getPhase().isMain() && game.getStack().isEmpty(); } //NOTE: for conditions the stack must only have the sa being checked public boolean couldCastSorcery(final SpellAbility sa) { final Card source = sa.getRootAbility().getHostCard(); boolean onlyThis = true; for (final Card card : game.getCardsIn(ZoneType.Stack)) { if (!card.equals(source)) { onlyThis = false; //System.out.println("StackCard: " + card + " vs SourceCard: " + source); } } PhaseHandler now = game.getPhaseHandler(); //System.out.println("now.isPlayerTurn(player) - " + now.isPlayerTurn(player)); //System.out.println("now.getPhase().isMain() - " + now.getPhase().isMain()); //System.out.println("onlyThis - " + onlyThis); return onlyThis && now.isPlayerTurn(this) && now.getPhase().isMain(); } public final PlayerController getController() { return controller; } public final void setFirstController(PlayerController ctrlr) { if (controllerCreator != null) { throw new IllegalStateException("Controller creator already assigned"); } controllerCreator = ctrlr; controller = ctrlr; updateAvatar(); view.updateIsAI(this); view.updateLobbyPlayerName(this); } public void updateAvatar() { view.updateAvatarIndex(this); view.updateAvatarCardImageKey(this); } /** * Run a procedure using a different controller */ public void runWithController(Runnable proc, PlayerController tempController) { PlayerController oldController = controller; controller = tempController; try { proc.run(); } finally { controller = oldController; } } public boolean isSkippingCombat() { if (hasKeyword("Skip your next combat phase.")) { return true; } if (hasKeyword("Skip your combat phase.")) { return true; } if (hasKeyword("Skip all combat phases of your next turn.")) { replaceAllKeywordInstances("Skip all combat phases of your next turn.", "Skip all combat phases of this turn."); return true; } if (hasKeyword("Skip all combat phases of this turn.")) { return true; } return false; } public boolean isSkippingMain() { return hasKeyword("Skip your main phase."); } public int getStartingHandSize() { return startingHandSize; } public void setStartingHandSize(int shs) { startingHandSize = shs; } /** * Takes the top plane of the planar deck and put it face up in the command zone. * Then runs triggers. */ public void planeswalk() { planeswalkTo(new CardCollection(getZone(ZoneType.PlanarDeck).get(0))); } /** * Puts the planes in the argument and puts them face up in the command zone. * Then runs triggers. */ public void planeswalkTo(final CardCollectionView destinations) { System.out.println(getName() + ": planeswalk to " + destinations.toString()); currentPlanes.addAll(destinations); for (Card c : currentPlanes) { game.getAction().moveTo(ZoneType.Command, c); //getZone(ZoneType.PlanarDeck).remove(c); //getZone(ZoneType.Command).add(c); } //DBG //System.out.println("CurrentPlanes: " + currentPlanes); //System.out.println("ActivePlanes: " + game.getActivePlanes()); //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); game.setActivePlanes(currentPlanes); //Run PlaneswalkedTo triggers here. HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Cards", currentPlanes); game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedTo, runParams, false); } /** * Puts my currently active planes, if any, at the bottom of my planar deck. */ public void leaveCurrentPlane() { final Map<String, Object> runParams = new ImmutableMap.Builder<String, Object>() .put("Cards", new CardCollection(currentPlanes)).build(); game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedFrom, runParams, false); for (final Card plane : currentPlanes) { //game.getZoneOf(plane).remove(plane); plane.clearControllers(); //getZone(ZoneType.PlanarDeck).add(plane); game.getAction().moveTo(ZoneType.PlanarDeck, plane, -1); } currentPlanes.clear(); //DBG //System.out.println("CurrentPlanes: " + currentPlanes); //System.out.println("ActivePlanes: " + game.getActivePlanes()); //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); } /** * Sets up the first plane of a round. */ public void initPlane() { Card firstPlane = null; while (true) { firstPlane = getZone(ZoneType.PlanarDeck).get(0); getZone(ZoneType.PlanarDeck).remove(firstPlane); if (firstPlane.getType().isPhenomenon()) { getZone(ZoneType.PlanarDeck).add(firstPlane); } else { currentPlanes.add(firstPlane); getZone(ZoneType.Command).add(firstPlane); break; } } game.setActivePlanes(currentPlanes); } public final void resetAttackedThisCombat() { // resets the status of attacked/blocked this phase CardCollectionView list = CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (int i = 0; i < list.size(); i++) { final Card c = list.get(i); if (c.getDamageHistory().getCreatureAttackedThisCombat()) { c.getDamageHistory().setCreatureAttackedThisCombat(false); } if (c.getDamageHistory().getCreatureBlockedThisCombat()) { c.getDamageHistory().setCreatureBlockedThisCombat(false); } if (c.getDamageHistory().getCreatureGotBlockedThisCombat()) { c.getDamageHistory().setCreatureGotBlockedThisCombat(false); } } } public final void drawMiracle(final Card card) { // Whenever a card with miracle is the first card drawn in a turn, // you may cast it for it's miracle cost if (card.getMiracleCost() == null) { return; } final SpellAbility playForMiracleCost = card.getFirstSpellAbility().copy(); playForMiracleCost.setPayCosts(card.getMiracleCost()); playForMiracleCost.setStackDescription(card.getName() + " - Cast via Miracle"); // TODO Convert this to a Trigger final Ability miracleTrigger = new MiracleTrigger(card, ManaCost.ZERO, playForMiracleCost); miracleTrigger.setStackDescription(card.getName() + " - Miracle."); miracleTrigger.setActivatingPlayer(card.getOwner()); miracleTrigger.setTrigger(true); game.getStack().add(miracleTrigger); } public boolean isSkippingDraw() { if (hasKeyword("Skip your next draw step.")) { removeKeyword("Skip your next draw step."); return true; } if (hasKeyword("Skip your draw step.")) { return true; } return false; } public void addInboundToken(Card c) { inboundTokens.add(c); } public void removeInboundToken(Card c) { inboundTokens.remove(c); } private final class MiracleTrigger extends Ability { private final SpellAbility miracle; private MiracleTrigger(Card sourceCard0, ManaCost manaCost0, SpellAbility miracle0) { super(sourceCard0, manaCost0); miracle = miracle0; } @Override public void resolve() { miracle.setActivatingPlayer(getHostCard().getOwner()); // pay miracle cost here. getHostCard().getOwner().getController().playMiracle(miracle, getHostCard()); } } public void onMulliganned() { game.fireEvent(new GameEventMulligan(this)); // quest listener may interfere here final int newHand = getCardsIn(ZoneType.Hand).size(); stats.notifyHasMulliganed(); stats.notifyOpeningHandSize(newHand); achievementTracker.mulliganTo = newHand; } public Card getCommander() { return commander; } public void setCommander(Card commander0) { if (commander == commander0) { return; } commander = commander0; view.updateCommander(this); } public Iterable<Entry<Card, Integer>> getCommanderDamage() { return commanderDamage.entrySet(); } public int getCommanderDamage(Card commander) { Integer damage = commanderDamage.get(commander); return damage == null ? 0 : damage.intValue(); } public boolean isPlayingExtraTurn() { return isPlayingExtraTrun; } public void setExtraTurn(boolean b) { isPlayingExtraTrun = b; } public void initVariantsZones(RegisteredPlayer registeredPlayer) { PlayerZone bf = getZone(ZoneType.Battlefield); Iterable<? extends IPaperCard> cards = registeredPlayer.getCardsOnBattlefield(); if (cards != null) { for (final IPaperCard cp : cards) { Card c = Card.fromPaperCard(cp, this); bf.add(c); c.setSickness(true); c.setStartsGameInPlay(true); } } PlayerZone com = getZone(ZoneType.Command); // Mainly for avatar, but might find something else here for (final IPaperCard cp : registeredPlayer.getCardsInCommand()) { com.add(Card.fromPaperCard(cp, this)); } // Schemes CardCollection sd = new CardCollection(); for (IPaperCard cp : registeredPlayer.getSchemes()) { sd.add(Card.fromPaperCard(cp, this)); } if (!sd.isEmpty()) { for (Card c : sd) { getZone(ZoneType.SchemeDeck).add(c); } getZone(ZoneType.SchemeDeck).shuffle(); } // Planes CardCollection l = new CardCollection(); for (IPaperCard cp : registeredPlayer.getPlanes()) { l.add(Card.fromPaperCard(cp, this)); } if (!l.isEmpty()) { for (Card c : l) { getZone(ZoneType.PlanarDeck).add(c); } getZone(ZoneType.PlanarDeck).shuffle(); } // Commander if (registeredPlayer.getCommander() != null) { Card cmd = Card.fromPaperCard(registeredPlayer.getCommander(), this); cmd.setCommander(true); com.add(cmd); setCommander(cmd); DetachedCardEffect eff = new DetachedCardEffect(cmd, "Commander Effect"); eff.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library,Hand | Destination$ Command | Defined$ ReplacedCard"); eff.setSVar("DBCommanderIncCast", "DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2"); eff.setSVar("CommanderCostRaise", "Number$0"); Trigger t = TriggerHandler.parseTrigger( "Mode$ SpellCast | Static$ True | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | Execute$ DBCommanderIncCast", eff, true); eff.addTrigger(t); ReplacementEffect r = ReplacementHandler.parseReplacement( "Event$ Moved | Destination$ Graveyard,Exile,Hand,Library | ValidCard$ Card.IsCommander+YouOwn | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead.", eff, true); eff.addReplacementEffect(r); eff.addStaticAbility( "Mode$ Continuous | EffectZone$ Command | AddKeyword$ May be played | Affected$ Card.YouOwn+IsCommander | AffectedZone$ Command"); eff.addStaticAbility( "Mode$ RaiseCost | EffectZone$ Command | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | EffectZone$ All | AffectedZone$ Command,Stack"); com.add(eff); } for (IPaperCard cp : registeredPlayer.getConspiracies()) { Card conspire = Card.fromPaperCard(cp, this); if (conspire.hasKeyword("Hidden agenda")) { if (!CardFactoryUtil.handleHiddenAgenda(this, conspire)) { continue; } } com.add(conspire); } } public void changeOwnership(Card card) { // If lost then gained, just clear out of lost. // If gained then lost, just clear out of gained. Player oldOwner = card.getOwner(); if (equals(oldOwner)) { return; } card.setOwner(this); if (lostOwnership.contains(card)) { lostOwnership.remove(card); } else { gainedOwnership.add(card); } if (oldOwner.gainedOwnership.contains(card)) { oldOwner.gainedOwnership.remove(card); } else { oldOwner.lostOwnership.add(card); } } public CardCollectionView getLostOwnership() { return lostOwnership; } public CardCollectionView getGainedOwnership() { return gainedOwnership; } @Override public PlayerView getView() { return view; } }