foul-magics/Mage/src/mage/players/PlayerImpl.java

2787 lines
112 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in ability and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of ability code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.players;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.Mode;
import mage.abilities.PlayLandAbility;
import mage.abilities.SpecialAction;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility;
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
import mage.abilities.costs.AdjustingSourceCosts;
import mage.abilities.costs.AlternativeCost;
import mage.abilities.costs.AlternativeCostSourceAbility;
import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.RestrictionUntapNotMoreThanEffect;
import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect;
import mage.abilities.keyword.ConvokeAbility;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.keyword.HexproofAbility;
import mage.abilities.keyword.InfectAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.keyword.ShroudAbility;
import mage.abilities.mana.ManaAbility;
import mage.abilities.mana.ManaOptions;
import mage.actions.MageDrawAction;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCard;
import mage.cards.decks.Deck;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.CardType;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.PlayerAction;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.Table;
import mage.game.combat.CombatGroup;
import mage.game.command.CommandObject;
import mage.game.command.Commander;
import mage.game.events.DamagePlayerEvent;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
import mage.game.turn.Step;
import mage.players.net.UserData;
import mage.target.Target;
import mage.target.TargetAmount;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetDiscard;
import mage.util.CardUtil;
import mage.watchers.common.BloodthirstWatcher;
import org.apache.log4j.Logger;
public abstract class PlayerImpl implements Player, Serializable {
private static final transient Logger logger = Logger.getLogger(PlayerImpl.class);
private static Random rnd = new Random();
/**
* Used to cancel waiting requests send to the player
*/
protected boolean abort;
protected final UUID playerId;
protected String name;
protected boolean human;
protected int life;
protected boolean wins;
protected boolean loses;
protected Library library;
protected Cards sideboard;
protected Cards hand;
protected Cards graveyard;
protected UUID commanderId;
protected Abilities<Ability> abilities;
protected Counters counters;
protected int landsPlayed;
protected int landsPerTurn = 1;
protected int loyaltyUsePerTurn = 1;
protected int maxHandSize = 7;
protected int maxAttackedBy = Integer.MAX_VALUE;
protected ManaPool manaPool;
// priority control
protected boolean passed; // player passed priority
protected boolean passedTurn; // F4
protected boolean passedUntilEndOfTurn; // F5
protected boolean passedUntilNextMain; // F6
protected boolean passedUntilStackResolved; // F8
protected Date dateLastAddedToStack; // F8
protected boolean skippedAtLeastOnce; // used to track if passed started in specific phase
/**
* This indicates that player passed all turns until his own turn starts (F9).
* Note! This differs from passedTurn as it doesn't care about spells and abilities in the stack and will pass them as well.
*/
protected boolean passedAllTurns; // F9
protected int turns;
protected int storedBookmark = -1;
protected int priorityTimeLeft = Integer.MAX_VALUE;
//
// conceded or connection lost game
protected boolean left;
// set if the player quits the complete match
protected boolean quit;
// set if the player lost match because of priority timeout
protected boolean timerTimeout;
// set if the player lost match because of idle timeout
protected boolean idleTimeout;
protected RangeOfInfluence range;
protected Set<UUID> inRange = new HashSet<>();
protected boolean isTestMode = false;
protected boolean canGainLife = true;
protected boolean canLoseLife = true;
protected boolean canPayLifeCost = true;
protected boolean canPaySacrificeCost = true;
protected boolean loseByZeroOrLessLife = true;
protected boolean canPlayCardsFromGraveyard = true;
protected final List<AlternativeSourceCosts> alternativeSourceCosts = new ArrayList<>();
protected boolean isGameUnderControl = true;
protected UUID turnController;
protected Set<UUID> playersUnderYourControl = new HashSet<>();
protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>();
protected boolean requestsAllowedToSeeHandCards = true;
protected List<UUID> attachments = new ArrayList<>();
protected boolean topCardRevealed = false;
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
protected boolean reachedNextTurnAfterLeaving = false;
// indicates that a sourceId will be cast without paying mana
protected UUID castSourceIdWithoutMana;
protected UserData userData;
/**
* During some steps we can't play anything
*/
protected final Map<PhaseStep, Step.StepPart> silentPhaseSteps = new HashMap<PhaseStep, Step.StepPart>()
{{ put(PhaseStep.DECLARE_ATTACKERS, Step.StepPart.PRE);}};
public PlayerImpl(String name, RangeOfInfluence range) {
this(UUID.randomUUID());
this.name = name;
this.range = range;
hand = new CardsImpl(Zone.HAND);
graveyard = new CardsImpl(Zone.GRAVEYARD);
abilities = new AbilitiesImpl<>();
counters = new Counters();
manaPool = new ManaPool(playerId);
library = new Library(playerId);
sideboard = new CardsImpl(Zone.OUTSIDE);
}
protected PlayerImpl(UUID id) {
this.playerId = id;
}
public PlayerImpl(final PlayerImpl player) {
this.abort = player.abort;
this.playerId = player.playerId;
this.name = player.name;
this.human = player.human;
this.life = player.life;
this.wins = player.wins;
this.loses = player.loses;
this.library = player.library.copy();
this.sideboard = player.sideboard.copy();
this.hand = player.hand.copy();
this.graveyard = player.graveyard.copy();
this.commanderId = player.commanderId;
this.abilities = player.abilities.copy();
this.counters = player.counters.copy();
this.landsPlayed = player.landsPlayed;
this.landsPerTurn = player.landsPerTurn;
this.loyaltyUsePerTurn = player.loyaltyUsePerTurn;
this.maxHandSize = player.maxHandSize;
this.maxAttackedBy = player.maxAttackedBy;
this.manaPool = player.manaPool.copy();
this.turns = player.turns;
this.left = player.left;
this.quit = player.quit;
this.timerTimeout = player.timerTimeout;
this.idleTimeout = player.idleTimeout;
this.range = player.range;
this.canGainLife = player.canGainLife;
this.canLoseLife = player.canLoseLife;
this.loseByZeroOrLessLife = player.loseByZeroOrLessLife;
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard;
this.attachments.addAll(player.attachments);
this.inRange.addAll(player.inRange);
this.userData = player.userData;
this.canPayLifeCost = player.canPayLifeCost;
this.canPaySacrificeCost = player.canPaySacrificeCost;
this.alternativeSourceCosts.addAll(player.alternativeSourceCosts);
this.storedBookmark = player.storedBookmark;
this.topCardRevealed = player.topCardRevealed;
this.playersUnderYourControl.clear();
this.playersUnderYourControl.addAll(player.playersUnderYourControl);
this.usersAllowedToSeeHandCards.addAll(player.usersAllowedToSeeHandCards);
this.requestsAllowedToSeeHandCards = player.requestsAllowedToSeeHandCards;
this.isTestMode = player.isTestMode;
this.isGameUnderControl = player.isGameUnderControl;
this.turnController = player.turnController;
this.passed = player.passed;
this.passedTurn = player.passedTurn;
this.passedUntilEndOfTurn = player.passedUntilEndOfTurn;
this.passedUntilNextMain = player.passedUntilNextMain;
this.skippedAtLeastOnce = player.skippedAtLeastOnce;
this.passedUntilStackResolved = player.passedUntilStackResolved;
this.dateLastAddedToStack = player.dateLastAddedToStack;
this.passedAllTurns = player.passedAllTurns;
this.priorityTimeLeft = player.getPriorityTimeLeft();
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
this.castSourceIdWithoutMana = player.castSourceIdWithoutMana;
}
@Override
public void restore(Player player) {
this.name = player.getName();
this.human = player.isHuman();
this.life = player.getLife();
this.wins = player.hasWon();
this.loses = player.hasLost();
this.library = player.getLibrary().copy();
this.sideboard = player.getSideboard().copy();
this.hand = player.getHand().copy();
this.graveyard = player.getGraveyard().copy();
this.commanderId = player.getCommanderId();
this.abilities = player.getAbilities().copy();
this.counters = player.getCounters().copy();
this.landsPlayed = player.getLandsPlayed();
this.landsPerTurn = player.getLandsPerTurn();
this.loyaltyUsePerTurn = player.getLoyaltyUsePerTurn();
this.maxHandSize = player.getMaxHandSize();
this.maxAttackedBy = player.getMaxAttackedBy();
this.manaPool = player.getManaPool().copy();
this.turns = player.getTurns();
this.left = player.hasLeft();
this.quit = player.hasQuit();
this.timerTimeout = player.hasTimerTimeout();
this.idleTimeout = player.hasIdleTimeout();
this.range = player.getRange();
this.canGainLife = player.isCanGainLife();
this.canLoseLife = player.isCanLoseLife();
this.attachments.clear();
this.attachments.addAll(player.getAttachments());
this.inRange.clear();
this.inRange.addAll(player.getInRange());
this.userData = player.getUserData();
this.canPayLifeCost = player.canPayLifeCost();
this.canPaySacrificeCost = player.canPaySacrificeCost();
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard();
this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
this.storedBookmark = player.getStoredBookmark();
this.topCardRevealed = player.isTopCardRevealed();
this.playersUnderYourControl.clear();
this.playersUnderYourControl.addAll(player.getPlayersUnderYourControl());
this.isTestMode = player.isTestMode();
this.isGameUnderControl = player.isGameUnderControl();
this.turnController = player.getTurnControlledBy();
this.passed = player.isPassed();
this.priorityTimeLeft = player.getPriorityTimeLeft();
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
this.castSourceIdWithoutMana = player.getCastSourceIdWithoutMana();
this.usersAllowedToSeeHandCards.addAll(player.getUsersAllowedToSeeHandCards());
this.requestsAllowedToSeeHandCards = player.isRequestToShowHandCardsAllowed();
}
@Override
public void useDeck(Deck deck, Game game) {
library.clear();
library.addAll(deck.getCards(), game);
sideboard.clear();
for (Card card : deck.getSideboard()) {
sideboard.add(card);
}
}
/**
* Cast e.g. from Karn Liberated to restart the current game
* @param game
*/
@Override
public void init(Game game) {
init(game, false);
}
@Override
public void init(Game game, boolean testMode) {
this.abort = false;
if (!testMode) {
this.hand.clear();
this.graveyard.clear();
}
this.library.reset();
this.abilities.clear();
this.counters.clear();
this.wins = false;
this.loses = false;
this.left = false;
// reset is neccessary because in tournament player will be used for each round
this.quit = false;
this.timerTimeout = false;
this.idleTimeout = false;
this.turns = 0;
this.isGameUnderControl = true;
this.turnController = this.getId();
this.playersUnderYourControl.clear();
this.passed = false;
this.passedTurn = false;
this.passedUntilEndOfTurn = false;
this.passedUntilNextMain = false;
this.skippedAtLeastOnce = false;
this.passedUntilStackResolved = false;
this.passedAllTurns = false;
this.canGainLife = true;
this.canLoseLife = true;
this.topCardRevealed = false;
this.setLife(game.getLife(), game);
this.setReachedNextTurnAfterLeaving(false);
game.getState().getWatchers().add(new BloodthirstWatcher(playerId));
this.castSourceIdWithoutMana = null;
}
/**
* called before apply effects
*
*/
@Override
public void reset() {
this.abilities.clear();
this.landsPerTurn = 1;
this.loyaltyUsePerTurn = 1;
this.maxHandSize = 7;
this.maxAttackedBy = Integer.MAX_VALUE;
this.canGainLife = true;
this.canLoseLife = true;
this.canPayLifeCost = true;
this.canPaySacrificeCost = true;
this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = false;
this.topCardRevealed = false;
this.alternativeSourceCosts.clear();
this.castSourceIdWithoutMana = null;
this.getManaPool().clearEmptyManaPoolRules();
}
@Override
public Counters getCounters() {
return counters;
}
@Override
public void otherPlayerLeftGame(Game game) {
findRange(game);
}
@Override
public void beginTurn(Game game) {
this.landsPlayed = 0;
findRange(game);
}
@Override
public RangeOfInfluence getRange() {
return range;
}
protected void findRange(Game game) {
//20100423 - 801.2c
inRange.clear();
if (range == RangeOfInfluence.ALL) {
for (Player player: game.getPlayers().values()) {
if (!player.hasLeft()) {
inRange.add(player.getId());
}
}
}
else {
if ((range.getRange() * 2) + 1 >= game.getPlayers().size()) {
for (Player player: game.getPlayers().values()) {
if (!player.hasLeft()) {
inRange.add(player.getId());
}
}
}
else {
inRange.add(playerId);
PlayerList players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getNext(game);
while (player.hasLeft()) {
player = players.getNext(game);
}
inRange.add(player.getId());
}
players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getPrevious(game);
while (player.hasLeft()) {
player = players.getPrevious(game);
}
inRange.add(player.getId());
}
}
}
}
@Override
public Set<UUID> getInRange() {
return inRange;
}
@Override
public Set<UUID> getPlayersUnderYourControl() {
return this.playersUnderYourControl;
}
@Override
public void controlPlayersTurn(Game game, UUID playerId) {
if (!playerId.equals(this.getId())) {
this.playersUnderYourControl.add(playerId);
Player player = game.getPlayer(playerId);
if (!player.hasLeft()&& !player.hasLost()) {
player.setGameUnderYourControl(false);
player.setTurnControlledBy(this.getId());
}
DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility(new LoseControlOnOtherPlayersControllerEffect());
ability.setSourceId(getId());
ability.setControllerId(getId());
game.addDelayedTriggeredAbility(ability);
}
}
@Override
public void setTurnControlledBy(UUID playerId) {
this.turnController = playerId;
}
@Override
public UUID getTurnControlledBy() {
return this.turnController;
}
@Override
public void resetOtherTurnsControlled() {
playersUnderYourControl.clear();
}
@Override
public boolean isGameUnderControl() {
return isGameUnderControl;
}
@Override
public void setGameUnderYourControl(boolean value) {
this.isGameUnderControl = value;
if (isGameUnderControl) {
this.turnController = getId();
}
}
@Override
public void endOfTurn(Game game) {
this.passedTurn = false;
}
@Override
public boolean canBeTargetedBy(MageObject source, Game game) {
if (this.hasLost() || this.hasLeft()) {
return false;
}
if (source != null) {
if (abilities.containsKey(ShroudAbility.getInstance().getId())) {
return false;
}
if (abilities.containsKey(HexproofAbility.getInstance().getId())) {
UUID controllerId = null;
if (source instanceof Permanent) {
controllerId = ((Permanent) source).getControllerId();
} else if (source instanceof StackObject) {
controllerId = ((StackObject) source).getControllerId();
}
if (controllerId != null && this.hasOpponent(controllerId, game) &&
!game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, this.getId(), game)) {
return false;
}
}
if (hasProtectionFrom(source, game)) {
return false;
}
}
return true;
}
@Override
public boolean hasProtectionFrom(MageObject source, Game game) {
for (ProtectionAbility ability: abilities.getProtectionAbilities()) {
if (!ability.canTarget(source, game)) {
return true;
}
}
return false;
}
@Override
public int drawCards(int num, Game game) {
if (num > 0) {
return game.doAction(new MageDrawAction(this, num, null));
}
return 0;
}
@Override
public int drawCards(int num, Game game, ArrayList<UUID> appliedEffects) {
return game.doAction(new MageDrawAction(this, num, appliedEffects));
}
@Override
public void discardToMax(Game game) {
if (hand.size() > this.maxHandSize) {
game.informPlayers(new StringBuilder(getName()).append(" discards down to ").append(this.maxHandSize).append(this.maxHandSize == 1?" hand card":" hand cards").toString());
discard(hand.size() - this.maxHandSize, null, game);
}
}
@Override
public boolean putInHand(Card card, Game game) {
if (card.getOwnerId().equals(playerId)) {
this.hand.add(card);
game.setZone(card.getId(), Zone.HAND);
} else {
return game.getPlayer(card.getOwnerId()).putInHand(card, game);
}
return true;
}
@Override
public boolean removeFromHand(Card card, Game game) {
hand.remove(card);
return true;
}
@Override
public boolean removeFromLibrary(Card card, Game game) {
if (card == null) {
return false;
}
library.remove(card.getId(), game);
return true;
}
@Override
public void discard(int amount, Ability source, Game game) {
discard(amount, false, source, game);
}
@Override
public Card discardOne(boolean random, Ability source, Game game) {
Cards cards = discard(1, random, source, game);
if (cards.isEmpty()) {
return null;
}
return cards.getRandom(game);
}
@Override
public Cards discard(int amount, boolean random, Ability source, Game game) {
Cards discardedCards = new CardsImpl();
if (amount >= this.getHand().size()) {
discardedCards.addAll(this.getHand());
while (this.getHand().size() > 0) {
discard(this.getHand().get(this.getHand().iterator().next(), game), source, game);
}
return discardedCards;
}
if (random) {
for (int i = 0; i < amount; i++) {
Card card = this.getHand().getRandom(game);
discardedCards.add(card);
discard(card, source, game);
}
} else {
TargetDiscard target = new TargetDiscard(amount, amount, new FilterCard(CardUtil.numberToText(amount, "a") + " card" + (amount > 1 ?"s":"")), playerId);
choose(Outcome.Discard, target, source == null?null:source.getSourceId(), game);
for (UUID cardId: target.getTargets()) {
Card card = this.getHand().get(cardId, game);
discardedCards.add(card);
discard(card, source, game);
}
}
return discardedCards;
}
@Override
public boolean discard(Card card, Ability source, Game game) {
//20100716 - 701.7
/* 701.7. Discard #
701.7a To discard a card, move it from its owner’s hand to that player’s graveyard.
701.7b By default, effects that cause a player to discard a card allow the affected
player to choose which card to discard. Some effects, however, require a random
discard or allow another player to choose which card is discarded.
701.7c If a card is discarded, but an effect causes it to be put into a hidden zone
instead of into its owner’s graveyard without being revealed, all values of that
card’s characteristics are considered to be undefined.
TODOD:
If a card is discarded this way to pay a cost that specifies a characteristic
about the discarded card, that cost payment is illegal; the game returns to
the moment before the cost was paid (see rule 717, "Handling Illegal Actions").
*/
if (card != null) {
// write info to game log first so game log infos from triggered or replacement effects follow in the game log
game.informPlayers(new StringBuilder(name).append(" discards ").append(card.getName()).toString());
/* If a card is discarded while Rest in Peace is on the battlefield, abilities that function
* when a card is discarded (such as madness) still work, even though that card never reaches
* a graveyard. In addition, spells or abilities that check the characteristics of a discarded
* card (such as Chandra Ablaze's first ability) can find that card in exile. */
card.moveToZone(Zone.GRAVEYARD, source==null?null:source.getSourceId(), game, false);
// So discard is also successful if card is moved to another zone by replacement effect!
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, card.getId(), source==null?null:source.getSourceId(), playerId));
return true;
}
return false;
}
@Override
public List<UUID> getAttachments() {
return attachments;
}
@Override
public boolean addAttachment(UUID permanentId, Game game) {
if (!this.attachments.contains(permanentId)) {
Permanent aura = game.getPermanent(permanentId);
if (aura != null) {
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ENCHANT_PLAYER, playerId, permanentId, aura.getControllerId()))) {
this.attachments.add(permanentId);
aura.attachTo(playerId, game);
game.fireEvent(new GameEvent(GameEvent.EventType.ENCHANTED_PLAYER, playerId, permanentId, aura.getControllerId()));
return true;
}
}
}
return false;
}
@Override
public boolean removeAttachment(UUID permanentId, Game game) {
if (this.attachments.contains(permanentId)) {
Permanent aura = game.getPermanent(permanentId);
if (aura != null) {
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, playerId, permanentId, aura.getControllerId()))) {
this.attachments.remove(permanentId);
aura.attachTo(null, game);
}
game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, playerId, permanentId, aura.getControllerId()));
return true;
}
}
return false;
}
@Override
public boolean removeFromBattlefield(Permanent permanent, Game game) {
permanent.removeFromCombat(game, false);
game.getBattlefield().removePermanent(permanent.getId());
if (permanent.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(permanent.getAttachedTo());
if (attachedTo != null) {
attachedTo.removeAttachment(permanent.getId(), game);
}
}
if (permanent.getPairedCard() != null) {
Permanent pairedCard = game.getPermanent(permanent.getPairedCard());
if (pairedCard != null) {
pairedCard.clearPairedCard();
}
}
return true;
}
@Override
public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) {
if (card.getOwnerId().equals(playerId)) {
this.graveyard.add(card);
} else {
return game.getPlayer(card.getOwnerId()).putInGraveyard(card, game, fromBattlefield);
}
return true;
}
@Override
public boolean removeFromGraveyard(Card card, Game game) {
this.graveyard.remove(card);
return true;
}
@Override
public boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder) {
if (cards.size() != 0) {
if (!anyOrder) {
for (UUID cardId : cards) {
Card card =game.getCard(cardId);
if (card != null) {
Zone fromZone = game.getState().getZone(cardId);
this.moveCardToLibraryWithInfo(card, source.getSourceId(), game, fromZone, false, false);
}
}
} else {
TargetCard target = new TargetCard(Zone.PICK, new FilterCard("card to put on the bottom of your library (last one chosen will be bottommost)"));
target.setRequired(true);
while (isInGame() && cards.size() > 1) {
this.choose(Outcome.Neutral, cards, target, game);
Card chosenCard = cards.get(target.getFirstTarget(), game);
if (chosenCard != null) {
cards.remove(chosenCard);
Zone fromZone = game.getState().getZone(chosenCard.getId());
this.moveCardToLibraryWithInfo(chosenCard, source.getSourceId(), game, fromZone, false, false);
}
target.clearChosen();
}
if (cards.size() == 1) {
Card chosenCard = cards.get(cards.iterator().next(), game);
Zone fromZone = game.getState().getZone(chosenCard.getId());
this.moveCardToLibraryWithInfo(chosenCard, source.getSourceId(), game, fromZone, false, false);
}
}
}
return true;
}
@Override
public boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder) {
if (cards.size() != 0) {
if (!anyOrder) {
for (UUID cardId : cards) {
Card card =game.getCard(cardId);
if (card != null) {
Zone fromZone = game.getState().getZone(cardId);
this.moveCardToLibraryWithInfo(card, source.getSourceId(), game, fromZone, true, false);
}
}
} else {
TargetCard target = new TargetCard(Zone.PICK, new FilterCard("card to put on the top of your library (last one chosen will be topmost)"));
target.setRequired(true);
while (isInGame() && cards.size() > 1) {
this.choose(Outcome.Neutral, cards, target, game);
Card chosenCard = cards.get(target.getFirstTarget(), game);
if (chosenCard != null) {
cards.remove(chosenCard);
Zone fromZone = game.getState().getZone(chosenCard.getId());
if (fromZone.equals(Zone.BATTLEFIELD)) {
Permanent permanent = game.getPermanent(chosenCard.getId());
this.moveCardToLibraryWithInfo(permanent, source.getSourceId(), game, fromZone, true, false);
} else {
this.moveCardToLibraryWithInfo(chosenCard, source.getSourceId(), game, fromZone, true, false);
}
}
target.clearChosen();
}
if (cards.size() == 1) {
// Card chosenCard = cards.get(cards.iterator().next(), game);
UUID cardId = cards.iterator().next();
Zone fromZone = game.getState().getZone(cardId);
if (fromZone.equals(Zone.BATTLEFIELD)) {
Permanent permanent = game.getPermanent(cardId);
this.moveCardToLibraryWithInfo(permanent, source.getSourceId(), game, fromZone, true, false);
} else {
Card chosenCard = cards.get(cardId, game);
this.moveCardToLibraryWithInfo(chosenCard, source.getSourceId(), game, fromZone, true, false);
}
}
}
}
return true;
}
@Override
public void setCastSourceIdWithoutMana(UUID sourceId) {
castSourceIdWithoutMana = sourceId;
}
@Override
public UUID getCastSourceIdWithoutMana() {
return castSourceIdWithoutMana;
}
@Override
public boolean cast(SpellAbility ability, Game game, boolean noMana) {
if (!ability.getSpellAbilityType().equals(SpellAbilityType.BASE)) {
ability = chooseSpellAbilityForCast(ability, game, noMana);
}
//20091005 - 601.2a
Card card = game.getCard(ability.getSourceId());
if (card != null) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, ability.getId(), ability.getSourceId(), playerId))) {
int bookmark = game.bookmarkState();
Zone fromZone = game.getState().getZone(card.getId());
card.cast(game, fromZone, ability, playerId);
Spell spell = game.getStack().getSpell(ability.getId());
// some effects set sourceId to cast without paying mana costs
if (ability.getSourceId().equals(getCastSourceIdWithoutMana())) {
noMana = true;
}
setCastSourceIdWithoutMana(null);
if (spell.activate(game, noMana)) {
GameEvent event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId);
event.setZone(fromZone);
game.fireEvent(event);
game.informPlayers(new StringBuilder(name).append(spell.getActivatedMessage(game)).toString());
game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;
}
game.restoreState(bookmark, ability.getRule());
}
}
return false;
}
@Override
public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) {
return ability;
}
@Override
public boolean playLand(Card card, Game game) {
//20091005 - 305.1
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId))) {
// int bookmark = game.bookmarkState();
Zone zone = game.getState().getZone(card.getId());
if (card.putOntoBattlefield(game, zone, null, playerId)) {
landsPlayed++;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId));
game.fireInformEvent(name + " plays " + card.getName());
// game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;
}
// putOntoBattlefield retured false if putOntoBattlefield was replaced by replacement effect (e.g. Kjeldorian Outpost).
// But that would undo the effect completely,
// what makes no real sense. So it makes no sense to generally do a restorState here.
// game.restoreState(bookmark);
}
return false;
}
protected boolean playManaAbility(ManaAbility ability, Game game) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY, ability.getId(), ability.getSourceId(), playerId))) {
int bookmark = game.bookmarkState();
if (ability.activate(game, false)) {
if (ability.resolve(game)) {
if (storedBookmark == -1 || storedBookmark > bookmark) { // e.g. usefull for undo Nykthos, Shrine to Nyx
setStoredBookmark(bookmark);
}
return true;
}
}
game.restoreState(bookmark, ability.getRule());
}
return false;
}
protected boolean playAbility(ActivatedAbility ability, Game game) {
//20091005 - 602.2a
if (ability.isUsesStack()) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY, ability.getId(), ability.getSourceId(), playerId))) {
int bookmark = game.bookmarkState();
ability.newId();
game.getStack().push(new StackAbility(ability, playerId));
if (ability.activate(game, false)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, ability.getId(), ability.getSourceId(), playerId));
game.informPlayers(new StringBuilder(name).append(ability.getGameLogMessage(game)).toString());
game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;
}
game.restoreState(bookmark, ability.getRule());
}
} else {
int bookmark = game.bookmarkState();
if (ability.activate(game, false)) {
ability.resolve(game);
game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;
}
game.restoreState(bookmark, ability.getRule());
}
return false;
}
protected boolean specialAction(SpecialAction action, Game game) {
//20091005 - 114
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY, action.getSourceId(), action.getId(), playerId))) {
int bookmark = game.bookmarkState();
if (action.activate(game, false)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, action.getSourceId(), action.getId(), playerId));
game.informPlayers(new StringBuilder(name).append(action.getGameLogMessage(game)).toString());
if (action.resolve(game)) {
game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;
}
}
game.restoreState(bookmark, action.getRule());
}
return false;
}
@Override
public boolean activateAbility(ActivatedAbility ability, Game game) {
boolean result;
if (!ability.canActivate(this.playerId, game)) {
return false;
}
if (ability instanceof PassAbility) {
pass(game);
return true;
}
else if (ability instanceof PlayLandAbility) {
Card card = hand.get(ability.getSourceId(), game);
if (card == null) {
card = game.getCard(ability.getSourceId());
}
result = playLand(card, game);
}
else if (ability instanceof SpecialAction) {
result = specialAction((SpecialAction)ability.copy(), game);
}
else if (ability instanceof ManaAbility) {
result = playManaAbility((ManaAbility)ability.copy(), game);
}
else if (ability instanceof FlashbackAbility){
result = playAbility(ability.copy(), game);
}
else if (ability instanceof SpellAbility) {
result = cast((SpellAbility)ability, game, false);
}
else {
result = playAbility(ability.copy(), game);
}
//if player has taken an action then reset all player passed flags
if (result) {
game.getPlayers().resetPassed();
}
return result;
}
@Override
public boolean triggerAbility(TriggeredAbility source, Game game) {
if (source == null) {
logger.warn("Null source in triggerAbility method");
throw new IllegalArgumentException("source TriggeredAbility must not be null");
}
//20091005 - 603.3c, 603.3d
int bookmark = game.bookmarkState();
TriggeredAbility ability = source.copy();
if (ability != null && ability.canChooseTarget(game)) {
if (ability.isUsesStack()) {
game.getStack().push(new StackAbility(ability, playerId));
}
if (ability.activate(game, false)) {
if (ability.isUsesStack() || ability.getRuleVisible()) {
game.informPlayers(ability.getGameLogMessage(game));
}
if (!ability.isUsesStack()) {
ability.resolve(game);
}
game.removeBookmark(bookmark);
return true;
}
}
game.restoreState(bookmark, source.getRule());
return false;
}
protected LinkedHashMap<UUID, ActivatedAbility> getSpellAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
for (Ability ability: object.getAbilities()) {
if (ability instanceof SpellAbility) {
if (((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {
if (zone.equals(Zone.HAND)) {
// Fix so you don't need to choose Fuse twice
useable.clear();
useable.put(ability.getId(), (SpellAbility) ability);
return useable;
} else {
// Fuse only allowed from hand
continue;
}
}
if (((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT)) {
continue;
}
useable.put(ability.getId(), (SpellAbility) ability);
}
}
return useable;
}
protected LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
if (!(object instanceof Permanent) || ((Permanent)object).canUseActivatedAbilities(game)) {
for (ActivatedAbility ability: object.getAbilities().getActivatedAbilities(zone)) {
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
}
}
if (zone != Zone.HAND) {
if (Zone.GRAVEYARD.equals(zone) && canPlayCardsFromGraveyard()) {
for (ActivatedAbility ability: object.getAbilities().getPlayableAbilities(Zone.HAND)) {
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
}
}
}
if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.CAST_FROM_NON_HAND_ZONE, this.getId(), game)) {
for (Ability ability: object.getAbilities()) {
ability.setControllerId(this.getId());
if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND)
&& ((ActivatedAbility) ability).canActivate(playerId, game)) {
useable.put(ability.getId(), (ActivatedAbility) ability);
}
}
}
}
getOtherUseableActivatedAbilities(object, zone, game, useable);
}
return useable;
}
// Adds special abilities that are given to non permanants by continuous effects
private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game game, Map<UUID, ActivatedAbility> useable) {
Abilities<ActivatedAbility> otherAbilities = game.getState().getActivatedOtherAbilities(object.getId(), zone);
if (otherAbilities != null) {
for (ActivatedAbility ability: otherAbilities) {
Card card = game.getCard(ability.getSourceId());
if (card.isSplitCard() && ability instanceof FlashbackAbility) {
FlashbackAbility flashbackAbility;
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.INSTANT);
}
else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT);
flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName());
useable.put(flashbackAbility.getId(), flashbackAbility);
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.INSTANT);
}
else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT);
flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName());
useable.put(flashbackAbility.getId(), flashbackAbility);
} else {
useable.put(ability.getId(), ability);
}
}
}
}
protected LinkedHashMap<UUID, ManaAbility> getUseableManaAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ManaAbility> useable = new LinkedHashMap<>();
if (!(object instanceof Permanent) || ((Permanent)object).canUseActivatedAbilities(game)) {
for (ManaAbility ability: object.getAbilities().getManaAbilities(zone)) {
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
}
}
}
return useable;
}
@Override
public int getLandsPlayed() {
return landsPlayed;
}
@Override
public boolean canPlayLand() {
//20091005 - 114.2a
return landsPlayed < landsPerTurn;
}
protected boolean isActivePlayer(Game game) {
return game.getActivePlayerId().equals(this.playerId);
}
@Override
public void shuffleLibrary(Game game) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.SHUFFLE_LIBRARY, playerId, playerId))) {
this.library.shuffle();
game.informPlayers(new StringBuilder(this.name).append(" shuffles his or her library.").toString());
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SHUFFLED, playerId, playerId));
}
}
@Override
public void revealCards(String name, Cards cards, Game game) {
revealCards(name, cards, game, true);
}
@Override
public void revealCards(String name, Cards cards, Game game, boolean postToLog) {
game.getState().getRevealed().add(name, cards);
if (postToLog) {
StringBuilder sb = new StringBuilder(this.getName()).append(" reveals ");
int current = 0, last = cards.size();
for (Card card :cards.getCards(game)) {
current++;
sb.append(card.getName());
if (current < last) {
sb.append(", ");
}
}
game.informPlayers(sb.toString());
}
}
@Override
public void lookAtCards(String name, Cards cards, Game game) {
game.getState().getLookedAt(this.playerId).add(name, cards);
game.fireUpdatePlayersEvent();
}
@Override
public void phasing(Game game) {
//20091005 - 502.1
List<Permanent> phasedOut = game.getBattlefield().getPhasedOut(playerId);
for (Permanent permanent: game.getBattlefield().getPhasedIn(playerId)) {
permanent.phaseOut(game);
}
for (Permanent permanent: phasedOut) {
permanent.phaseIn(game);
}
}
@Override
public void untap(Game game) {
// create list of all "notMoreThan" effects to track which one are consumed
HashMap<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffectsUsage = new HashMap<>();
for (Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>> restrictionEffect: game.getContinuousEffects().getApplicableRestrictionUntapNotMoreThanEffects(this, game).entrySet()) {
notMoreThanEffectsUsage.put(restrictionEffect, restrictionEffect.getKey().getNumber());
}
if (!notMoreThanEffectsUsage.isEmpty()) {
// create list of all permanents that can be untapped generally
List<Permanent> canBeUntapped = new ArrayList<>();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean untap = true;
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) {
untap &= effect.canBeUntapped(permanent, game);
}
if (untap) {
canBeUntapped.add(permanent);
}
}
// selected permanents to untap
List<Permanent> selectedToUntap = new ArrayList<>();
// player can cancel the seletion of an effect to use a prefered order of restriction effects
boolean playerCanceledSelection;
do {
playerCanceledSelection = false;
// select permanents to untap to consume the "notMoreThan" effects
for(Map.Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> handledEntry: notMoreThanEffectsUsage.entrySet()) {
// select a permanent to untap for this entry
int numberToUntap = handledEntry.getValue();
if (numberToUntap > 0) {
List<Permanent> leftForUntap = getPermanentsThatCanBeUntapped(game, canBeUntapped, handledEntry.getKey().getKey(), notMoreThanEffectsUsage);
FilterControlledPermanent filter = handledEntry.getKey().getKey().getFilter().copy();
String message = filter.getMessage();
// omitt already from other untap effects selected permanents
for (Permanent permanent: selectedToUntap) {
filter.add(Predicates.not(new PermanentIdPredicate(permanent.getId())));
}
// while targets left and there is still allowed to untap
while (isInGame() && leftForUntap.size() > 0 && numberToUntap > 0) {
// player has to select the permanent he wants to untap for this restriction
Ability ability = handledEntry.getKey().getValue().iterator().next();
if (ability != null) {
StringBuilder sb = new StringBuilder(message).append(" to untap").append(" (").append(Math.min(leftForUntap.size(), numberToUntap)).append(" in total");
MageObject effectSource = game.getObject(ability.getSourceId());
if (effectSource != null) {
sb.append(" from ").append(effectSource.getName()).toString();
}
sb.append(")");
filter.setMessage(sb.toString());
Target target = new TargetPermanent(filter);
if (!this.chooseTarget(Outcome.Untap, target, ability, game)) {
// player canceled, go on with the next effect (if no other effect available, this effect will be active again)
playerCanceledSelection = true;
break;
}
Permanent selectedPermanent = game.getPermanent(target.getFirstTarget());
if (leftForUntap.contains(selectedPermanent)) {
selectedToUntap.add(selectedPermanent);
numberToUntap--;
// don't allow to select same permanent twice
filter.add(Predicates.not(new PermanentIdPredicate(selectedPermanent.getId())));
// reduce available untap numbers from other "UntapNotMoreThan" effects if selected permanent applies to their filter too
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getValue() > 0 && notMoreThanEffect.getKey().getKey().getFilter().match(selectedPermanent, game)) {
notMoreThanEffect.setValue(notMoreThanEffect.getValue() - 1);
}
}
// update the left for untap list
leftForUntap = getPermanentsThatCanBeUntapped(game, canBeUntapped, handledEntry.getKey().getKey(), notMoreThanEffectsUsage);
// remove already selected permanents
for (Permanent permanent :selectedToUntap) {
if (leftForUntap.contains(permanent)) {
leftForUntap.remove(permanent);
}
}
} else {
// player selected an permanent that is restricted by another effect, disallow it (so AI can select another one)
filter.add(Predicates.not(new PermanentIdPredicate(selectedPermanent.getId())));
if (this.isHuman()) {
game.informPlayer(this, "This permanent can't be untapped because of other restricting effect.");
}
}
}
}
}
}
} while (isInGame() && playerCanceledSelection);
// show in log which permanents were selected to untap
for(Permanent permanent :selectedToUntap) {
game.informPlayers(new StringBuilder(this.getName()).append(" untapped ").append(permanent.getName()).toString());
}
// untap if permanent is not concerned by notMoreThan effects or is included in the selectedToUntapList
for (Permanent permanent: canBeUntapped) {
boolean doUntap = true;
if (!selectedToUntap.contains(permanent)) {
// if the permanent is covered by one of the restriction effects, don't untap it
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getKey().getKey().getFilter().match(permanent, game)) {
doUntap = false;
break;
}
}
}
if (permanent != null && doUntap) {
permanent.untap(game);
}
}
} else {
//20091005 - 502.2
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean untap = true;
for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) {
untap &= effect.canBeUntapped(permanent, game);
}
if (untap) {
permanent.untap(game);
}
}
}
}
private List<Permanent> getPermanentsThatCanBeUntapped(Game game, List<Permanent> canBeUntapped, RestrictionUntapNotMoreThanEffect handledEffect, HashMap<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffectsUsage) {
List<Permanent> leftForUntap = new ArrayList<>();
// select permanents that can still be untapped
for (Permanent permanent: canBeUntapped) {
if (handledEffect.getFilter().match(permanent, game)) { // matches the restricted permanents of handled entry
boolean canBeSelected = true;
// check if the permanent is restriced by another restriction that has left no permanent
for (Entry<Entry<RestrictionUntapNotMoreThanEffect, HashSet<Ability>>, Integer> notMoreThanEffect : notMoreThanEffectsUsage.entrySet()) {
if (notMoreThanEffect.getKey().getKey().getFilter().match(permanent, game) && notMoreThanEffect.getValue() == 0) {
canBeSelected = false;
break;
}
}
if (canBeSelected) {
leftForUntap.add(permanent);
}
}
}
return leftForUntap;
}
@Override
public UUID getId() {
return playerId;
}
@Override
public Cards getHand() {
return hand;
}
@Override
public Cards getGraveyard() {
return graveyard;
}
@Override
public ManaPool getManaPool() {
return this.manaPool;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isHuman() {
return human;
}
@Override
public Library getLibrary() {
return library;
}
@Override
public Cards getSideboard() {
return sideboard;
}
@Override
public int getLife() {
return life;
}
@Override
public void initLife(int life) {
this.life = life;
}
@Override
public void setLife(int life, Game game) {
// rule 118.5
if (life > this.life) {
gainLife(life - this.life, game);
} else if (life < this.life) {
loseLife(this.life - life, game);
}
}
@Override
public void setLifeTotalCanChange(boolean lifeTotalCanChange) {
this.canGainLife = lifeTotalCanChange;
this.canLoseLife = lifeTotalCanChange;
}
@Override
public boolean isLifeTotalCanChange() {
return canGainLife | canLoseLife;
}
@Override
public List<AlternativeSourceCosts> getAlternativeSourceCosts() {
return alternativeSourceCosts;
}
@Override
public boolean isCanLoseLife() {
return canLoseLife;
}
@Override
public void setCanLoseLife(boolean canLoseLife) {
this.canLoseLife = canLoseLife;
}
@Override
public int loseLife(int amount, Game game) {
if (!canLoseLife) {
return 0;
}
GameEvent event = new GameEvent(GameEvent.EventType.LOSE_LIFE, playerId, playerId, playerId, amount, false);
if (!game.replaceEvent(event)) {
this.life -= event.getAmount();
game.informPlayers(new StringBuilder(this.getName()).append(" loses ").append(event.getAmount()).append(" life").toString());
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST_LIFE, playerId, playerId, playerId, amount));
return amount;
}
return 0;
}
@Override
public boolean isCanGainLife() {
return canGainLife;
}
@Override
public void setCanGainLife(boolean canGainLife) {
this.canGainLife = canGainLife;
}
@Override
public int gainLife(int amount, Game game) {
if (!canGainLife || amount == 0) {
return 0;
}
GameEvent event = new GameEvent(GameEvent.EventType.GAIN_LIFE, playerId, playerId, playerId, amount, false);
if (!game.replaceEvent(event)) {
this.life += event.getAmount();
game.informPlayers(new StringBuilder(this.getName()).append(" gains ").append(event.getAmount()).append(" life").toString());
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.GAINED_LIFE, playerId, playerId, playerId, event.getAmount()));
return event.getAmount();
}
return 0;
}
@Override
public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable) {
return doDamage(damage, sourceId, game, combatDamage, preventable, null);
}
@Override
public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, ArrayList<UUID> appliedEffects) {
return doDamage(damage, sourceId, game, combatDamage, preventable, appliedEffects);
}
@SuppressWarnings({"null", "ConstantConditions"})
private int doDamage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, ArrayList<UUID> appliedEffects) {
if (damage > 0 && canDamage(game.getObject(sourceId), game)) {
GameEvent event = new DamagePlayerEvent(playerId, sourceId, playerId, damage, preventable, combatDamage);
event.setAppliedEffects(appliedEffects);
if (!game.replaceEvent(event)) {
int actualDamage = event.getAmount();
if (actualDamage > 0) {
Permanent source = game.getPermanent(sourceId);
if(source == null){
MageObject lastKnownInformation = game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD);
if(lastKnownInformation != null && lastKnownInformation instanceof Permanent){
source = (Permanent) lastKnownInformation;
}
}
if (source != null && (source.getAbilities().containsKey(InfectAbility.getInstance().getId()))) {
addCounters(CounterType.POISON.createInstance(actualDamage), game);
} else {
GameEvent damageToLifeLossEvent = new GameEvent(EventType.DAMAGE_CAUSES_LIFE_LOSS, playerId, sourceId, playerId, actualDamage, combatDamage);
if (!game.replaceEvent(damageToLifeLossEvent)) {
this.loseLife(damageToLifeLossEvent.getAmount(), game);
}
}
if (source != null && source.getAbilities().containsKey(LifelinkAbility.getInstance().getId())) {
Player player = game.getPlayer(source.getControllerId());
player.gainLife(actualDamage, game);
}
game.fireEvent(new DamagedPlayerEvent(playerId, sourceId, playerId, actualDamage, combatDamage));
return actualDamage;
}
}
}
return 0;
}
@Override
public void addCounters(Counter counter, Game game) {
int amount = counter.getCount();
for (int i = 0; i < amount; i++) {
Counter eventCounter = counter.copy();
eventCounter.remove(amount - 1);
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, playerId, playerId, counter.getName(), counter.getCount()))) {
counters.addCounter(eventCounter);
game.fireEvent(GameEvent.getEvent(EventType.COUNTER_ADDED, playerId, playerId, counter.getName(), counter.getCount()));
}
}
}
protected boolean canDamage(MageObject source, Game game) {
for (ProtectionAbility ability: abilities.getProtectionAbilities()) {
if (!ability.canTarget(source, game)) {
return false;
}
}
return true;
}
@Override
public Abilities<Ability> getAbilities() {
return this.abilities;
}
@Override
public void addAbility(Ability ability) {
ability.setSourceId(playerId);
this.abilities.add(ability);
}
@Override
public int getLandsPerTurn() {
return this.landsPerTurn;
}
@Override
public void setLandsPerTurn(int landsPerTurn) {
this.landsPerTurn = landsPerTurn;
}
@Override
public int getLoyaltyUsePerTurn() {
return this.loyaltyUsePerTurn;
}
@Override
public void setLoyaltyUsePerTurn(int loyaltyUsePerTurn) {
this.loyaltyUsePerTurn = loyaltyUsePerTurn;
}
@Override
public int getMaxHandSize() {
return maxHandSize;
}
@Override
public void setMaxHandSize(int maxHandSize) {
this.maxHandSize = maxHandSize;
}
@Override
public void setMaxAttackedBy(int maxAttackedBy) {
this.maxAttackedBy = maxAttackedBy;
}
@Override
public int getMaxAttackedBy() {
return maxAttackedBy;
}
@Override
public void setResponseString(String responseString) {}
@Override
public void setResponseManaType(UUID manaTypePlayerId, ManaType responseManaType) {}
@Override
public void setResponseUUID(UUID responseUUID) {}
@Override
public void setResponseBoolean(Boolean responseBoolean) {}
@Override
public void setResponseInteger(Integer responseInteger) {}
@Override
public boolean isPassed() {
return passed;
}
@Override
public void pass(Game game) {
this.passed = true;
resetStoredBookmark(game);
}
@Override
public boolean isEmptyDraw() {
return library.isEmptyDraw();
}
@Override
public void resetPassed() {
this.passed = this.loses || this.hasLeft();
}
@Override
public void quit(Game game) {
quit = true;
this.concede(game);
logger.debug(getName() + " quits the match.");
game.informPlayers(getName() + " quits the match.");
}
@Override
public void timerTimeout(Game game) {
quit = true;
timerTimeout = true;
this.concede(game);
game.informPlayers(getName() + " has run out of time. Loosing the Match.");
}
@Override
public void idleTimeout(Game game) {
quit = true;
idleTimeout = true;
this.concede(game);
game.informPlayers(new StringBuilder(getName()).append(" was idle for too long. Loosing the Match.").toString());
}
@Override
public void concede(Game game) {
game.gameOver(playerId);
lost(game);
this.left = true;
}
@Override
public void sendPlayerAction(PlayerAction playerAction, Game game) {
switch(playerAction) {
case PASS_PRIORITY_UNTIL_MY_NEXT_TURN: // F9
passedUntilNextMain = false;
passedUntilEndOfTurn = false;
passedTurn = false;
passedAllTurns = true;
passedUntilStackResolved = false;
this.skip();
break;
case PASS_PRIORITY_UNTIL_TURN_END_STEP: // F5
passedUntilNextMain = false;
passedTurn = false;
passedAllTurns = false;
passedUntilEndOfTurn = true;
passedUntilStackResolved = false;
skippedAtLeastOnce = !game.getTurn().getStepType().equals(PhaseStep.END_TURN);
this.skip();
break;
case PASS_PRIORITY_UNTIL_NEXT_TURN: // F4
passedUntilNextMain = false;
passedAllTurns = false;
passedUntilEndOfTurn = false;
passedUntilStackResolved = false;
passedTurn = true;
this.skip();
break;
case PASS_PRIORITY_UNTIL_NEXT_MAIN_PHASE: //F7
passedAllTurns = false;
passedTurn = false;
passedUntilEndOfTurn = false;
passedUntilNextMain = true;
passedUntilStackResolved = false;
skippedAtLeastOnce = !(game.getTurn().getStepType().equals(PhaseStep.POSTCOMBAT_MAIN) || game.getTurn().getStepType().equals(PhaseStep.PRECOMBAT_MAIN));
this.skip();
break;
case PASS_PRIORITY_UNTIL_STACK_RESOLVED: //F8
passedAllTurns = false;
passedTurn = false;
passedUntilEndOfTurn = false;
passedUntilNextMain = false;
passedUntilStackResolved = true;
dateLastAddedToStack = game.getStack().getDateLastAdded();
this.skip();
break;
case PASS_PRIORITY_CANCEL_ALL_ACTIONS:
passedAllTurns = false;
passedTurn = false;
passedUntilEndOfTurn = false;
passedUntilNextMain = false;
passedUntilStackResolved = false;
break;
case PERMISSION_REQUESTS_ALLOWED_OFF:
this.setRequestToShowHandCardsAllowed(false);
break;
case PERMISSION_REQUESTS_ALLOWED_ON:
this.setRequestToShowHandCardsAllowed(true);
break;
}
logger.trace("PASS Priority: " + playerAction.toString());
}
@Override
public void leave() {
this.passed = true;
this.loses = true;
this.left = true;
this.abort();
//20100423 - 800.4a
this.hand.clear();
this.graveyard.clear();
this.library.clear();
}
@Override
public boolean hasLeft() {
return this.left;
}
@Override
public void lost(Game game) {
if (canLose(game)) {
logger.debug(this.getName() + " has lost gameId: " + game.getId());
//20100423 - 603.9
if (!this.wins) {
this.loses = true;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST, null, null, playerId));
game.informPlayers(this.getName()+ " has lost the game.");
} else {
logger.debug(this.getName() + " has already won - stop lost");
}
// for draw - first all players that have lost have to be set to lost
if (!hasLeft()) {
logger.debug("Game over playerId: " + playerId);
game.gameOver(playerId);
}
}
}
@Override
public boolean canLose(Game game) {
return !game.replaceEvent(new GameEvent(GameEvent.EventType.LOSES, null, null, playerId));
}
@Override
public void won(Game game) {
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.WINS, null, null, playerId))) {
logger.debug("player won -> start: " + this.getName());
if (!this.loses) {
//20130501 - 800.7, 801.16
// all opponents in range loose the game
for (UUID opponentId: game.getOpponents(playerId)) {
Player opponent = game.getPlayer(opponentId);
if (opponent != null && !opponent.hasLost()) {
logger.debug("player won -> calling opponent lost: " + this.getName() + " opponent: " + opponent.getName());
opponent.lost(game);
}
}
// if no more opponents alive, you win and the game ends
int opponentsAlive = 0;
for (UUID opponentId: game.getOpponents(playerId)) {
Player opponent = game.getPlayer(opponentId);
if (opponent != null && !opponent.hasLost()) {
opponentsAlive++;
}
}
if (opponentsAlive == 0 && !hasWon()) {
logger.debug("player won -> No more opponents alive game won: " + this.getName());
game.informPlayers(new StringBuilder(this.getName()).append(" has won the game").toString());
this.wins = true;
game.end();
}
} else {
logger.debug("player won -> but already lost before: " + this.getName());
}
}
}
@Override
public boolean hasLost() {
return this.loses;
}
@Override
public boolean isInGame() {
return !hasQuit() && !hasLost() && !hasWon() && !hasLeft();
}
@Override
public boolean hasWon() {
if (!this.loses) {
return this.wins;
}
else {
return false;
}
}
@Override
public void declareAttacker(UUID attackerId, UUID defenderId, Game game, boolean allowUndo) {
if (allowUndo) {
setStoredBookmark(game.bookmarkState()); // makes it possible to UNDO a declared attacker with costs from e.g. Propaganda
}
Permanent attacker = game.getPermanent(attackerId);
if (attacker != null && attacker.canAttack(defenderId, game) && attacker.getControllerId().equals(playerId)) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, defenderId, attackerId, playerId))) {
game.getCombat().declareAttacker(attackerId, defenderId, game);
}
}
}
@Override
public void declareBlocker(UUID defenderId, UUID blockerId, UUID attackerId, Game game) {
Permanent blocker = game.getPermanent(blockerId);
CombatGroup group = game.getCombat().findGroup(attackerId);
if (blocker != null && group != null && group.canBlock(blocker, game)) {
group.addBlocker(blockerId, playerId, game);
game.getCombat().addBlockingGroup(blockerId, attackerId, playerId, game);
} else {
if (this.isHuman()) {
game.informPlayer(this, "You can't block this creature.");
}
}
}
@Override
public boolean searchLibrary(TargetCardInLibrary target, Game game) {
return searchLibrary(target, game, playerId);
}
@Override
public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId) {
//20091005 - 701.14c
Library searchedLibrary = null;
if (targetPlayerId.equals(playerId)) {
game.informPlayers(new StringBuilder(getName()).append(" searches his or her library").toString());
searchedLibrary = library;
} else {
Player targetPlayer = game.getPlayer(targetPlayerId);
if (targetPlayer != null) {
game.informPlayers(new StringBuilder(getName()).append(" searches the library of ").append(targetPlayer.getName()).toString());
searchedLibrary = targetPlayer.getLibrary();
}
}
if (searchedLibrary == null) {
return false;
}
GameEvent event = GameEvent.getEvent(GameEvent.EventType.SEARCH_LIBRARY, targetPlayerId, playerId, playerId, Integer.MAX_VALUE);
if (!game.replaceEvent(event)) {
TargetCardInLibrary newTarget = target.copy();
int count;
int librarySearchLimit = event.getAmount();
if (librarySearchLimit == Integer.MAX_VALUE) {
count = searchedLibrary.count(target.getFilter(), game);
} else {
newTarget.setCardLimit(librarySearchLimit);
count = Math.min(searchedLibrary.count(target.getFilter(), game), librarySearchLimit);
}
if (count < target.getNumberOfTargets()) {
newTarget.setMinNumberOfTargets(count);
}
if (newTarget.choose(Outcome.Neutral, playerId, targetPlayerId, game)) {
target.getTargets().clear();
for(UUID targetId: newTarget.getTargets()){
target.add(targetId, game);
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SEARCHED, targetPlayerId, playerId));
}
return true;
}
return false;
}
@Override
public boolean flipCoin(Game game) {
return this.flipCoin(game, null);
}
/**
* @param game
* @param appliedEffects
*
* @return true if player won the toss
*/
@Override
public boolean flipCoin(Game game, ArrayList<UUID> appliedEffects) {
boolean result = rnd.nextBoolean();
game.informPlayers("[Flip a coin] " + getName() + (result ? " won (head)." : " lost (tail)."));
GameEvent event = new GameEvent(GameEvent.EventType.FLIP_COIN, playerId, null, playerId, 0, result);
event.setAppliedEffects(appliedEffects);
game.replaceEvent(event);
return event.getFlag();
}
@Override
public List<Permanent> getAvailableAttackers(Game game) {
// TODO: get available opponents and their planeswalkers, check for each if permanent can attack one
return getAvailableAttackers(null, game);
}
@Override
public List<Permanent> getAvailableAttackers(UUID defenderId, Game game) {
FilterCreatureForCombat filter = new FilterCreatureForCombat();
List<Permanent> attackers = game.getBattlefield().getAllActivePermanents(filter, playerId, game);
for (Iterator<Permanent> i = attackers.iterator(); i.hasNext();) {
Permanent entry = i.next();
if (!entry.canAttack(defenderId, game)) {
i.remove();
}
}
return attackers;
}
@Override
public List<Permanent> getAvailableBlockers(Game game) {
FilterCreatureForCombatBlock blockFilter = new FilterCreatureForCombatBlock();
List<Permanent> blockers = game.getBattlefield().getAllActivePermanents(blockFilter, playerId, game);
return blockers;
}
protected ManaOptions getManaAvailable(Game game) {
ManaOptions available = new ManaOptions();
List<Permanent> manaPerms = this.getAvailableManaProducers(game);
for (Permanent perm: manaPerms) {
available.addMana(perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game), game);
}
List<Permanent> manaPermsWithCost = this.getAvailableManaProducersWithCost(game);
for (Permanent perm: manaPermsWithCost) {
available.addManaWithCost(perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game), game);
}
return available;
}
// returns only mana producers that don't require mana payment
protected List<Permanent> getAvailableManaProducers(Game game) {
List<Permanent> result = new ArrayList<>();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
boolean canAdd = false;
for (ManaAbility ability: permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
if (ability.canActivate(playerId, game)) {
canAdd = true;
}
if (!ability.getManaCosts().isEmpty()) {
canAdd = false;
break;
}
}
if (canAdd) {
result.add(permanent);
}
}
return result;
}
// returns only mana producers that require mana payment
protected List<Permanent> getAvailableManaProducersWithCost(Game game) {
List<Permanent> result = new ArrayList<>();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
for (ManaAbility ability: permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
if (ability.canActivate(playerId, game) && !ability.getManaCosts().isEmpty()) {
result.add(permanent);
break;
}
}
}
return result;
}
protected boolean canPlay(ActivatedAbility ability, ManaOptions available, MageObject sourceObject, Game game) {
if (!(ability instanceof ManaAbility)) {
ActivatedAbility copy = ability.copy();
copy.setCheckPlayableMode(); // prevents from endless loops for asking player to use effects by checking this mode
if (!copy.canActivate(playerId, game)) {
return false;
}
game.getContinuousEffects().costModification(copy, game);
Card card = game.getCard(ability.getSourceId());
if (card != null) {
for (Ability ability0 : card.getAbilities()) {
if (ability0 instanceof AdjustingSourceCosts) {
// A workaround for Issue#457
if (!(ability0 instanceof ConvokeAbility)) {
((AdjustingSourceCosts) ability0).adjustCosts(copy, game);
}
}
}
}
ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions();
if (abilityOptions.size() == 0) {
return true;
}
else {
for (Mana mana: abilityOptions) {
for (Mana avail: available) {
if (mana.enough(avail)) {
return true;
}
}
}
}
MageObject object = game.getObject(ability.getSourceId());
for (Ability objectAbility :object.getAbilities()) {
if (objectAbility instanceof AlternativeCostSourceAbility) {
if (objectAbility.getCosts().canPay(ability, ability.getSourceId(), playerId, game)) {
return true;
}
}
}
for (AlternativeCost cost: ability.getAlternativeCosts()) {
if (cost.isAvailable(game, ability) && cost.canPay(ability, ability.getSourceId(), playerId, game)) {
return true;
}
}
if (!(sourceObject instanceof Permanent)) {
for (Ability alternateSourceCostsAbility : sourceObject.getAbilities()) {
// if cast for noMana no Alternative costs are allowed
if (alternateSourceCostsAbility instanceof AlternativeSourceCosts) {
if (((AlternativeSourceCosts)alternateSourceCostsAbility).isAvailable(ability, game)) {
if (alternateSourceCostsAbility.getCosts().canPay(ability, playerId, playerId, game)) {
ManaCostsImpl manaCosts = new ManaCostsImpl();
for(Cost cost:alternateSourceCostsAbility.getCosts()) {
if (cost instanceof ManaCost) {
manaCosts.add(cost);
}
}
if (manaCosts.size() == 0) {
return true;
}
else {
for (Mana mana: manaCosts.getOptions()) {
for (Mana avail: available) {
if (mana.enough(avail)) {
return true;
}
}
}
}
}
}
}
}
}
}
return false;
}
@Override
public List<Ability> getPlayable(Game game, boolean hidden) {
List<Ability> playable = new ArrayList<>();
if (!shouldSkipGettingPlayable(game)) {
ManaOptions availableMana = getManaAvailable(game);
availableMana.addMana(manaPool.getMana());
if (hidden) {
for (Card card : hand.getUniqueCards(game)) {
for (Ability ability : card.getAbilities().getPlayableAbilities(Zone.HAND)) { // gets this activated ability from hand? (Morph?)
if (ability instanceof ActivatedAbility) {
if (ability instanceof PlayLandAbility) {
if (game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), ability.getSourceId(), playerId), ability, game, true)) {
break;
}
}
if (canPlay((ActivatedAbility) ability, availableMana, card, game)) {
playable.add(ability);
}
}
// if (ability instanceof AlternativeSourceCosts) {
//
// }
}
}
}
for (Card card : graveyard.getUniqueCards(game)) {
boolean asThoughtCast = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.CAST_FROM_NON_HAND_ZONE, this.getId(), game);
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.ALL)) {
boolean possible = false;
if (ability.getZone().match(Zone.GRAVEYARD)) {
possible = true;
} else if (ability.getZone().match(Zone.HAND) && (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) {
if (asThoughtCast || canPlayCardsFromGraveyard()) {
possible = true;
}
}
if (possible && canPlay(ability, availableMana, card, game)) {
playable.add(ability);
}
}
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
for (Ability ability: useable.values()) {
playable.add(ability);
}
}
for (ExileZone exile : game.getExile().getExileZones()) {
for (Card card : exile.getCards(game)) {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.CAST_FROM_NON_HAND_ZONE, this.getId(), game)) {
for (Ability ability : card.getAbilities()) {
if (ability.getZone().match(Zone.HAND)) {
ability.setControllerId(this.getId()); // controller must be set for case owner != caster
if (ability instanceof ActivatedAbility) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
playable.add(ability);
}
}
}
}
}
}
}
for (Cards cards : game.getState().getRevealed().values()) {
for (Card card : cards.getCards(game)) {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.CAST_FROM_NON_HAND_ZONE, this.getId(), game)) {
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) {
playable.add(ability);
}
}
}
}
}
// eliminate duplicate activated abilities
Map<String, Ability> playableActivated = new HashMap<>();
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
for (ActivatedAbility ability : permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
if (!playableActivated.containsKey(ability.toString())) {
if (canPlay(ability, availableMana, permanent, game)) {
playableActivated.put(ability.toString(), ability);
}
}
}
}
// // get cast commander if available
// if (!(this.getCommanderId() == null)) {
// Zone zone = game.getState().getZone(this.getCommanderId());
// if (zone != null && zone.equals(Zone.COMMAND)) {
// MageObject object = game.getObject(this.getCommanderId());
// if (object != null) {
// for (ActivatedAbility ability : ((Commander) object).getAbilities().getActivatedAbilities(Zone.COMMAND)) {
// if (canPlay(ability, availableMana, object, game)) {
// playableActivated.put(ability.toString(), ability);
// }
// }
// }
// }
// }
// activated abilities from objects in the command zone
for (CommandObject commandObject: game.getState().getCommand()) {
for (ActivatedAbility ability: commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) {
if (ability.getControllerId().equals(getId())
&& ability.getAbilityType().equals(AbilityType.ACTIVATED)
&& canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) {
playableActivated.put(ability.toString(), ability);
}
}
}
playable.addAll(playableActivated.values());
}
return playable;
}
/**
* Creates a list of card ids that are currently playable.<br>
* Used to mark the playable cards in GameView
*
* @return A Set of cardIds that are playable
* @see mage.server.GameSessionPlayer#getGameView()
*
* @param game
*/
@Override
public Set<UUID> getPlayableInHand(Game game) {
Set<UUID> playable = new HashSet<>();
if (!shouldSkipGettingPlayable(game)) {
ManaOptions available = getManaAvailable(game);
available.addMana(manaPool.getMana());
for (Card card : hand.getCards(game)) {
for (ActivatedAbility ability : card.getAbilities().getPlayableAbilities(Zone.HAND)) {
if (ability instanceof PlayLandAbility) {
if (game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), ability.getSourceId(), playerId), ability, game, true)) {
break;
}
}
if (canPlay(ability, available, card, game)) {
playable.add(card.getId());
break;
}
}
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
if (!playable.contains(ability.getSourceId()) && canPlay(ability, available, card, game)) {
playable.add(card.getId());
break;
}
}
}
}
return playable;
}
/**
* Skip "silent" phase step when players are not allowed to cast anything.
* E.g. players can't play or cast anything during declaring attackers.
*
* @param game
* @return
*/
private boolean shouldSkipGettingPlayable(Game game) {
if (game.getStep() == null) { // happens at the start of the game
return true;
}
for (Entry<PhaseStep, Step.StepPart> phaseStep : silentPhaseSteps.entrySet()) {
if (game.getPhase() != null && game.getPhase().getStep() != null && phaseStep.getKey().equals(game.getPhase().getStep().getType())) {
if (phaseStep.getValue() == null || phaseStep.getValue().equals(game.getPhase().getStep().getStepPart())) {
return true;
}
}
}
return false;
}
/**
* Only used for AIs
*
* @param ability
* @param game
* @return
*/
@Override
public List<Ability> getPlayableOptions(Ability ability, Game game) {
List<Ability> options = new ArrayList<>();
if (ability.isModal()) {
addModeOptions(options, ability, game);
} else if (ability.getTargets().getUnchosen().size() > 0) {
// TODO: Handle other variable costs than mana costs
if (ability.getManaCosts().getVariableCosts().size() > 0) {
addVariableXOptions(options, ability, 0, game);
} else {
addTargetOptions(options, ability, 0, game);
}
} else if (ability.getChoices().getUnchosen().size() > 0) {
addChoiceOptions(options, ability, 0, game);
} else if (ability.getCosts().getTargets().getUnchosen().size() > 0) {
addCostTargetOptions(options, ability, 0, game);
}
return options;
}
private void addModeOptions(List<Ability> options, Ability option, Game game) {
for (Mode mode: option.getModes().values()) {
Ability newOption = option.copy();
newOption.getModes().setMode(mode);
if (newOption.getTargets().getUnchosen().size() > 0) {
if (newOption.getManaCosts().getVariableCosts().size() > 0) {
addVariableXOptions(options, newOption, 0, game);
} else {
addTargetOptions(options, newOption, 0, game);
}
} else if (newOption.getChoices().getUnchosen().size() > 0) {
addChoiceOptions(options, newOption, 0, game);
} else if (newOption.getCosts().getTargets().getUnchosen().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
}
}
}
protected void addVariableXOptions(List<Ability> options, Ability option, int targetNum, Game game) {
addTargetOptions(options, option, targetNum, game);
}
protected void addTargetOptions(List<Ability> options, Ability option, int targetNum, Game game) {
for (Target target: option.getTargets().getUnchosen().get(targetNum).getTargetOptions(option, game)) {
Ability newOption = option.copy();
if (target instanceof TargetAmount) {
for (UUID targetId: target.getTargets()) {
int amount = target.getTargetAmount(targetId);
newOption.getTargets().get(targetNum).addTarget(targetId, amount, newOption, game, true);
}
}
else {
for (UUID targetId: target.getTargets()) {
newOption.getTargets().get(targetNum).addTarget(targetId, newOption, game, true);
}
}
if (targetNum < option.getTargets().size() - 2) {
addTargetOptions(options, newOption, targetNum + 1, game);
}
else {
if (option.getChoices().size() > 0) {
addChoiceOptions(options, newOption, 0, game);
} else if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
}
}
}
}
private void addChoiceOptions(List<Ability> options, Ability option, int choiceNum, Game game) {
for (String choice: option.getChoices().get(choiceNum).getChoices()) {
Ability newOption = option.copy();
newOption.getChoices().get(choiceNum).setChoice(choice);
if (choiceNum < option.getChoices().size() - 1) {
addChoiceOptions(options, newOption, choiceNum + 1, game);
}
else {
if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
}
}
}
}
private void addCostTargetOptions(List<Ability> options, Ability option, int targetNum, Game game) {
for (UUID targetId: option.getCosts().getTargets().get(targetNum).possibleTargets(option.getSourceId(), playerId, game)) {
Ability newOption = option.copy();
newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game, true);
if (targetNum < option.getCosts().getTargets().size() - 1) {
addCostTargetOptions(options, newOption, targetNum + 1, game);
}
else {
options.add(newOption);
}
}
}
@Override
public boolean isTestMode() {
return isTestMode;
}
@Override
public void setTestMode(boolean value) {
this.isTestMode = value;
}
@Override
public boolean isTopCardRevealed() {
return topCardRevealed;
}
@Override
public void setTopCardRevealed(boolean topCardRevealed) {
this.topCardRevealed = topCardRevealed;
}
@Override
public UserData getUserData() {
return this.userData;
}
@Override
public void setUserData(UserData userData) {
this.userData = userData;
}
@Override
public void addAction(String action) {
// do nothing
}
@Override
public int getActionCount() {
return 0;
}
@Override
public void setAllowBadMoves(boolean allowBadMoves) {
// do nothing
}
@Override
public boolean canPayLifeCost() {
return isLifeTotalCanChange() && canPayLifeCost;
}
@Override
public void setCanPayLifeCost(boolean canPayLifeCost) {
this.canPayLifeCost = canPayLifeCost;
}
@Override
public boolean canPaySacrificeCost() {
return canPaySacrificeCost;
}
@Override
public void setCanPaySacrificeCost(boolean canPaySacrificeCost) {
this.canPaySacrificeCost = canPaySacrificeCost;
}
@Override
public boolean canLoseByZeroOrLessLife() {
return loseByZeroOrLessLife;
}
@Override
public void setLoseByZeroOrLessLife(boolean loseByZeroOrLessLife) {
this.loseByZeroOrLessLife = loseByZeroOrLessLife;
}
@Override
public boolean canPlayCardsFromGraveyard() {
return canPlayCardsFromGraveyard;
}
@Override
public void setPlayCardsFromGraveyard(boolean playCardsFromGraveyard) {
this.canPlayCardsFromGraveyard = playCardsFromGraveyard;
}
@Override
public boolean autoLoseGame() {
return false;
}
@Override
public void becomesActivePlayer() {
this.passedAllTurns = false;
this.turns++;
}
@Override
public int getTurns() {
return turns;
}
@Override
public int getStoredBookmark() {
return storedBookmark;
}
@Override
public void setStoredBookmark(int storedBookmark) {
this.storedBookmark = storedBookmark;
}
@Override
public synchronized void resetStoredBookmark(Game game) {
if (this.storedBookmark != -1) {
game.removeBookmark(this.storedBookmark);
}
setStoredBookmark(-1);
}
@Override
public void revealFaceDownCard(Card card, Game game) {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.REVEAL_FACE_DOWN, this.getId(), game)) {
Cards cards = new CardsImpl(card);
this.revealCards(name, cards, game);
}
}
@Override
public void setPriorityTimeLeft(int timeLeft) {
priorityTimeLeft = timeLeft;
}
@Override
public int getPriorityTimeLeft() {
return priorityTimeLeft;
}
@Override
public boolean hasQuit() {
return quit;
}
@Override
public boolean hasTimerTimeout() {
return timerTimeout;
}
@Override
public boolean hasIdleTimeout() {
return idleTimeout;
}
@Override
public void setReachedNextTurnAfterLeaving(boolean reachedNextTurnAfterLeaving) {
this.reachedNextTurnAfterLeaving = reachedNextTurnAfterLeaving;
}
@Override
public boolean hasReachedNextTurnAfterLeaving() {
return reachedNextTurnAfterLeaving;
}
@Override
public boolean canJoinTable(Table table) {
return true;
}
@Override
public void setCommanderId(UUID commanderId) {
this.commanderId = commanderId;
};
@Override
public UUID getCommanderId() {
return this.commanderId;
}
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, Game game, Zone fromZone) {
boolean result = false;
if (card.moveToZone(Zone.HAND, sourceId, game, false)) {
if (card instanceof PermanentCard) {
card = game.getCard(card.getId());
}
game.informPlayers(new StringBuilder(this.getName())
.append(" puts ").append(card.isFaceDown() ? " a face down card":card.getLogName()).append(" ")
.append(fromZone != null ? new StringBuilder("from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" "):"")
.append(card.getOwnerId().equals(this.getId()) ? "into his or her hand":"into its owner's hand").toString());
result = true;
}
return result;
}
@Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, Game game, Zone fromZone) {
boolean result = false;
if (card.moveToZone(Zone.GRAVEYARD, sourceId, game, fromZone != null ? fromZone.equals(Zone.BATTLEFIELD) : false)) {
if (card instanceof PermanentCard) {
card = game.getCard(card.getId());
}
StringBuilder sb = new StringBuilder(this.getName())
.append(" puts ").append(card.getLogName()).append(" ")
.append(fromZone != null ? new StringBuilder("from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" "):"");
if (card.getOwnerId().equals(getId())) {
sb.append("into his or her graveyard");
} else {
sb.append("it into its owner's graveyard");
}
game.informPlayers(sb.toString());
result = true;
}
return result;
}
@Override
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, Game game, Zone fromZone, boolean toTop, boolean withName) {
boolean result = false;
if (card.moveToZone(Zone.LIBRARY, sourceId, game, toTop)) {
if (card instanceof PermanentCard) {
card = game.getCard(card.getId());
}
StringBuilder sb = new StringBuilder(this.getName())
.append(" puts ").append(withName ? card.getLogName():"a card").append(" ");
if (fromZone != null) {
if (fromZone.equals(Zone.PICK)) {
sb.append("a picked card ");
} else {
sb.append("from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" ");
}
}
sb.append("to the ").append(toTop ? "top":"bottom");
if (card.getOwnerId().equals(getId())) {
sb.append(" of his or her library");
} else {
Player player = game.getPlayer(card.getOwnerId());
if (player != null) {
sb.append(" of ").append(player.getName()).append("'s library");
}
}
game.informPlayers(sb.toString());
result = true;
}
return result;
}
@Override
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, Game game, Zone fromZone) {
boolean result = false;
if (card.moveToExile(exileId, exileName, sourceId, game)) {
if (card instanceof PermanentCard) {
card = game.getCard(card.getId());
}
game.informPlayers(new StringBuilder(this.getName())
.append(" moves ").append(card.getLogName()).append(" ")
.append(fromZone != null ? new StringBuilder("from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" "):"")
.append("to exile").toString());
result = true;
}
return result;
}
@Override
public boolean putOntoBattlefieldWithInfo(Card card, Game game, Zone fromZone, UUID sourceId) {
return this.putOntoBattlefieldWithInfo(card, game, fromZone, sourceId, false);
}
@Override
public boolean putOntoBattlefieldWithInfo(Card card, Game game, Zone fromZone, UUID sourceId, boolean tapped) {
boolean result = false;
if (card.putOntoBattlefield(game, fromZone, sourceId, this.getId(), tapped)) {
game.informPlayers(new StringBuilder(this.getName())
.append(" puts ").append(card.getLogName())
.append(" from ").append(fromZone.toString().toLowerCase(Locale.ENGLISH)).append(" ")
.append("onto the Battlefield").toString());
result = true;
}
return result;
}
@Override
public boolean hasOpponent(UUID playerToCheckId, Game game) {
return !this.getId().equals(playerToCheckId) && game.isOpponent(this, playerToCheckId);
}
@Override
public void cleanUpOnMatchEnd() {
}
@Override
public boolean getPassedAllTurns() {
return passedAllTurns;
}
@Override
public boolean getPassedUntilNextMain() {
return passedUntilNextMain;
}
@Override
public boolean getPassedUntilEndOfTurn() {
return passedUntilEndOfTurn;
}
@Override
public boolean getPassedTurn() {
return passedTurn;
}
@Override
public boolean getPassedUntilStackResolved() {
return passedUntilStackResolved;
}
@Override
public void revokePermissionToSeeHandCards() {
usersAllowedToSeeHandCards.clear();
}
@Override
public void addPermissionToShowHandCards(UUID watcherUserId) {
usersAllowedToSeeHandCards.add(watcherUserId);
}
@Override
public void setRequestToShowHandCardsAllowed(boolean requestAllowed) {
this.requestsAllowedToSeeHandCards = requestAllowed;
}
@Override
public boolean isRequestToShowHandCardsAllowed() {
return requestsAllowedToSeeHandCards;
}
@Override
public boolean hasUserPermissionToSeeHand(UUID userId) {
return usersAllowedToSeeHandCards.contains(userId);
}
@Override
public Set<UUID> getUsersAllowedToSeeHandCards() {
return usersAllowedToSeeHandCards;
}
}