@Override public boolean resolve(Game game) { boolean result = true; // 20100716 - 117.12 if (checkIfClause(game)) { for (Effect effect : getEffects()) { if (effect instanceof OneShotEffect) { boolean effectResult = effect.apply(game, this); result &= effectResult; if (logger.isDebugEnabled()) { if (!this.getAbilityType().equals(AbilityType.MANA)) { if (!effectResult) { if (this.getSourceId() != null) { MageObject mageObject = game.getObject(this.getSourceId()); if (mageObject != null) { logger.debug("AbilityImpl.resolve: object: " + mageObject.getName()); } } logger.debug( "AbilityImpl.resolve: effect returned false -" + effect.getText(this.getModes().getMode())); } } } } else { game.addEffect((ContinuousEffect) effect, this); } /** * All restrained trigger events are fired now. To restrain the events is mainly neccessary * because of the movement of multiple object at once. If the event is fired directly as one * object moved, other objects are not already in the correct zone to check for their * effects. (e.g. Valakut, the Molten Pinnacle) */ game.getState().handleSimultaneousEvent(game); game.resetShortLivingLKI(); /** * game.applyEffects() has to be done at least for every effect that moves cards/permanent * between zones, or changes control of objects so Static effects work as intened if * dependant from the moved objects zone it is in Otherwise for example were static * abilities with replacement effects deactivated too late Example: {@link * org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy} */ if (effect.applyEffectsAfter()) { game.applyEffects(); game.getState().getTriggers().checkStateTriggers(game); } } } return result; }
protected void simulateStep(Game game, Step step) { if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return; } if (!game.gameOver(null)) { game.getPhase().setStep(step); if (!step.skipStep(game, game.getActivePlayerId())) { step.beginStep(game, game.getActivePlayerId()); game.checkStateAndTriggered(); while (!game.getStack().isEmpty()) { game.getStack().resolve(game); game.applyEffects(); } step.endStep(game, game.getActivePlayerId()); } } }
protected void addAbilityNode(SimulationNode parent, Ability ability, Game game) { Game sim = game.copy(); sim.getStack().push(new StackAbility(ability, playerId)); ability.activate(sim, false); sim.applyEffects(); SimulationNode newNode = new SimulationNode(parent, sim, playerId); logger.debug( indent(newNode.getDepth()) + "simulating -- node #:" + SimulationNode.getCount() + " triggered ability option"); for (Target target : ability.getTargets()) { for (UUID targetId : target.getTargets()) { newNode.getTargets().add(targetId); } } for (Choice choice : ability.getChoices()) { newNode.getChoices().add(choice.getChoice()); } parent.children.add(newNode); }
public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId) { Card source = game.getCard(sourceId); String setCode = source != null ? source.getExpansionSetCode() : null; GameEvent event = GameEvent.getEvent(EventType.CREATE_TOKEN, null, sourceId, controllerId, amount); if (!game.replaceEvent(event)) { amount = event.getAmount(); for (int i = 0; i < amount; i++) { PermanentToken permanent = new PermanentToken(this, controllerId, setCode, game); game.getState().addCard(permanent); game.addPermanent(permanent); this.lastAddedTokenId = permanent.getId(); permanent.entersBattlefield(sourceId, game); game.applyEffects(); game.fireEvent( new ZoneChangeEvent(permanent, controllerId, Zone.OUTSIDE, Zone.BATTLEFIELD)); } return true; } return false; }
@Override public boolean triggerAbility(TriggeredAbility source, Game game) { Ability ability = source.copy(); List<Ability> options = getPlayableOptions(ability, game); if (options.isEmpty()) { if (logger.isDebugEnabled()) logger.debug("simulating -- triggered ability:" + ability); game.getStack().push(new StackAbility(ability, playerId)); ability.activate(game, false); game.applyEffects(); game.getPlayers().resetPassed(); } else { SimulationNode parent = (SimulationNode) game.getCustomData(); if (parent.getDepth() == maxDepth) return true; logger.debug( indent(parent.getDepth()) + "simulating -- triggered ability - adding children:" + options.size()); for (Ability option : options) { addAbilityNode(parent, option, game); } } return true; }
protected int simulateBlockers( Game game, SimulationNode2 node, UUID defenderId, int depth, int alpha, int beta, boolean counter) { if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } Integer val = null; SimulationNode2 bestNode = null; // check if defender is being attacked if (game.getCombat().isAttacked(defenderId, game)) { SimulatedPlayer2 defender = (SimulatedPlayer2) game.getPlayer(defenderId); if (logger.isDebugEnabled()) { logger.debug( defender.getName() + "'s possible blockers: " + defender.getAvailableBlockers(game)); } List<Combat> combats = defender.addBlockers(game); for (Combat engagement : combats) { if (alpha >= beta) { logger.debug("Sim blockers -- pruning blockers"); break; } Game sim = game.copy(); for (CombatGroup group : engagement.getGroups()) { if (group.getAttackers().size() > 0) { UUID attackerId = group.getAttackers().get(0); for (UUID blockerId : group.getBlockers()) { sim.getPlayer(defenderId).declareBlocker(defenderId, blockerId, attackerId, sim); } } } sim.fireEvent( GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, defenderId); if (logger.isDebugEnabled()) { logger.debug("Sim block for player:" + game.getPlayer(defenderId).getName()); } sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); logger.debug("Sim blockers: resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent( GameEvent.getEvent( GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); finishCombat(sim); if (sim.gameOver(null)) { val = GameStateEvaluator2.evaluate(playerId, sim); } else if (!counter) { val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta); } else { val = GameStateEvaluator2.evaluate(playerId, sim); } if (!defenderId.equals(playerId)) { if (val < beta) { beta = val; bestNode = newNode; bestNode.setScore(val); bestNode.setCombat(simCombat); } } else { if (val > alpha) { alpha = val; bestNode = newNode; bestNode.setScore(val); bestNode.setCombat(simCombat); } } } } if (val == null) { val = GameStateEvaluator2.evaluate(playerId, game); } if (bestNode != null) { node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); } if (logger.isDebugEnabled()) { logger.debug( "Sim blockers: returning score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } return val; }
protected int simulateAttackers( Game game, SimulationNode2 node, UUID attackerId, int depth, int alpha, int beta, boolean counter) { if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } Integer val = null; SimulationNode2 bestNode = null; SimulatedPlayer2 attacker = (SimulatedPlayer2) game.getPlayer(attackerId); UUID defenderId = game.getOpponents(attackerId).iterator().next(); if (logger.isDebugEnabled()) { logger.debug( attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(defenderId, game)); } for (Combat engagement : attacker.addAttackers(game)) { if (logger.isDebugEnabled()) { logger.debug( "Sim Attackers: " + engagement.getAttackers() + ", blockers: " + engagement.getBlockers()); } if (alpha >= beta) { logger.debug("Sim Attackers -- pruning attackers"); break; } Game sim = game.copy(); for (CombatGroup group : engagement.getGroups()) { for (UUID attackId : group.getAttackers()) { sim.getPlayer(attackerId).declareAttacker(attackId, defenderId, sim, false); } } sim.fireEvent( GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, attackerId); if (logger.isDebugEnabled()) { logger.debug("Sim attack for player:" + game.getPlayer(attackerId).getName()); } sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); logger.debug("Sim attack: resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent( GameEvent.getEvent( GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); sim.getPhase().setStep(new DeclareBlockersStep()); val = simulateCombat(sim, newNode, depth - 1, alpha, beta, counter); if (!attackerId.equals(playerId)) { if (val < beta) { beta = val; bestNode = newNode; bestNode.setScore(val); if (newNode.getChildren().size() > 0) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } } } else { if (val > alpha) { alpha = val; bestNode = newNode; bestNode.setScore(val); if (newNode.getChildren().size() > 0) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } } } } if (val == null) { val = GameStateEvaluator2.evaluate(playerId, game); } if (bestNode != null) { node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); } if (logger.isDebugEnabled()) { logger.debug( "Sim attackers: returning score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } return val; }
public boolean replaceEvent(GameEvent event, Game game) { boolean caught = false; HashMap<UUID, HashSet<UUID>> consumed = new HashMap<>(); do { HashMap<ReplacementEffect, HashSet<Ability>> rEffects = getApplicableReplacementEffects(event, game); // Remove all consumed effects (ability dependant) for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) { ReplacementEffect entry = it1.next(); if (consumed.containsKey(entry.getId())) { HashSet<UUID> consumedAbilitiesIds = consumed.get(entry.getId()); if (rEffects.get(entry) == null || consumedAbilitiesIds.size() == rEffects.get(entry).size()) { it1.remove(); } else { Iterator it = rEffects.get(entry).iterator(); while (it.hasNext()) { Ability ability = (Ability) it.next(); if (consumedAbilitiesIds.contains(ability.getId())) { it.remove(); } } } } } // no effects left, quit if (rEffects.isEmpty()) { break; } int index; boolean onlyOne = false; if (rEffects.size() == 1) { ReplacementEffect effect = rEffects.keySet().iterator().next(); HashSet<Ability> abilities; if (effect.getEffectType().equals(EffectType.REPLACEMENT)) { abilities = replacementEffects.getAbility(effect.getId()); } else { abilities = preventionEffects.getAbility(effect.getId()); } if (abilities == null || abilities.size() == 1) { onlyOne = true; } } if (onlyOne) { index = 0; } else { // 20100716 - 616.1c Player player = game.getPlayer(event.getPlayerId()); index = player.chooseReplacementEffect(getReplacementEffectsTexts(rEffects, game), game); } // get the selected effect int checked = 0; ReplacementEffect rEffect = null; Ability rAbility = null; for (Map.Entry<ReplacementEffect, HashSet<Ability>> entry : rEffects.entrySet()) { if (entry.getValue() == null) { if (checked == index) { rEffect = entry.getKey(); break; } else { checked++; } } else { HashSet<Ability> abilities = entry.getValue(); int size = abilities.size(); if (index > (checked + size - 1)) { checked += size; } else { rEffect = entry.getKey(); Iterator it = abilities.iterator(); while (it.hasNext() && rAbility == null) { if (checked == index) { rAbility = (Ability) it.next(); } else { it.next(); checked++; } } break; } } } if (rEffect != null) { event.getAppliedEffects().add(rEffect.getId()); caught = rEffect.replaceEvent(event, rAbility, game); } if (caught) { // Event was completely replaced -> stop applying effects to it break; } // add the applied effect to the consumed effects if (rEffect != null) { if (consumed.containsKey(rEffect.getId())) { HashSet<UUID> set = consumed.get(rEffect.getId()); if (rAbility != null) { if (!set.contains(rAbility.getId())) { set.add(rAbility.getId()); } } } else { HashSet<UUID> set = new HashSet<>(); if (rAbility != null) { // in case of AuraReplacementEffect or PlaneswalkerReplacementEffect there // is no Ability set.add(rAbility.getId()); } consumed.put(rEffect.getId(), set); } } // Must be called here for some effects to be able to work correctly // TODO: add info which effects need that call game.applyEffects(); } while (true); return caught; }
@Override public boolean activate(Game game, boolean noMana) { Player controller = game.getPlayer(this.getControllerId()); if (controller == null) { return false; } game.applyEffects(); /* 20130201 - 601.2b * If the spell is modal the player announces the mode choice (see rule 700.2). */ if (!getModes().choose(game, this)) { return false; } if (controller.isTestMode()) { if (!controller.addTargets(this, game)) { return false; } } getSourceObject(game); /* 20130201 - 601.2b * If the player wishes to splice any cards onto the spell (see rule 702.45), he * or she reveals those cards in his or her hand. */ if (this.abilityType.equals(AbilityType.SPELL)) { game.getContinuousEffects().applySpliceEffects(this, game); } // if ability can be cast for no mana, clear the mana costs now, because additional mana costs // must be paid. // For Flashback ability can be set X before, so the X costs have to be restored for the // flashbacked ability if (noMana) { if (this.getManaCostsToPay().getVariableCosts().size() > 0) { int xValue = this.getManaCostsToPay().getX(); this.getManaCostsToPay().clear(); VariableManaCost xCosts = new VariableManaCost(); xCosts.setAmount(xValue); this.getManaCostsToPay().add(xCosts); } else { this.getManaCostsToPay().clear(); } } // 20130201 - 601.2b // If the spell has alternative or additional costs that will be paid as it's being cast such // as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his // or her intentions to pay any or all of those costs (see rule 601.2e). // A player can't apply two alternative methods of casting or two alternative costs to a single // spell. if (!activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game)) { if (getAbilityType().equals(AbilityType.SPELL) && ((SpellAbility) this) .getSpellAbilityType() .equals(SpellAbilityType.FACE_DOWN_CREATURE)) { return false; } } // 20121001 - 601.2b // If the spell has a variable cost that will be paid as it's being cast (such as an {X} in // its mana cost; see rule 107.3), the player announces the value of that variable. VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller); String announceString = handleOtherXCosts(game, controller); // For effects from cards like Void Winnower x costs have to be set if (this.getAbilityType().equals(AbilityType.SPELL) && game.replaceEvent( GameEvent.getEvent( GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) { return false; } for (Mode mode : this.getModes().getSelectedModes()) { this.getModes().setActiveMode(mode); // 20121001 - 601.2c // 601.2c The player announces his or her choice of an appropriate player, object, or zone for // each target the spell requires. A spell may require some targets only if an alternative or // additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for // it; // otherwise, the spell is cast as though it did not require those targets. If the spell has a // variable number of targets, the player announces how many targets he or she will choose // before // he or she announces those targets. The same target can't be chosen multiple times for any // one // instance of the word "target" on the spell. However, if the spell uses the word "target" in // multiple places, the same object, player, or zone can be chosen once for each instance of // the // word "target" (as long as it fits the targeting criteria). If any effects say that an // object // or player must be chosen as a target, the player chooses targets so that he or she obeys // the // maximum possible number of such effects without violating any rules or effects that say // that // an object or player can't be chosen as a target. The chosen players, objects, and/or zones // each become a target of that spell. (Any abilities that trigger when those players, // objects, // and/or zones become the target of a spell trigger at this point; they'll wait to be put on // the stack until the spell has finished being cast.) if (sourceObject != null && !this.getAbilityType() .equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in // playerImpl.triggerAbility sourceObject.adjustTargets(this, game); } if (mode.getTargets().size() > 0 && mode.getTargets() .chooseTargets( getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game) == false) { if ((variableManaCost != null || announceString != null) && !game.isSimulation()) { game.informPlayer( controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X"); } return false; // when activation of ability is canceled during target selection } } // end modes // TODO: Handle optionalCosts at the same time as already OptionalAdditionalSourceCosts are // handled. for (Cost cost : optionalCosts) { if (cost instanceof ManaCost) { cost.clearPaid(); if (controller.chooseUse( Outcome.Benefit, "Pay optional cost " + cost.getText() + "?", this, game)) { manaCostsToPay.add((ManaCost) cost); } } } // 20100716 - 601.2e if (sourceObject != null) { sourceObject.adjustCosts(this, game); if (sourceObject instanceof Card) { for (Ability ability : ((Card) sourceObject).getAbilities(game)) { if (ability instanceof AdjustingSourceCosts) { ((AdjustingSourceCosts) ability).adjustCosts(this, game); } } } else { for (Ability ability : sourceObject.getAbilities()) { if (ability instanceof AdjustingSourceCosts) { ((AdjustingSourceCosts) ability).adjustCosts(this, game); } } } } // this is a hack to prevent mana abilities with mana costs from causing endless loops - pay // other costs first if (this instanceof ManaAbility && !costs.pay(this, game, sourceId, controllerId, noMana, null)) { logger.debug("activate mana ability failed - non mana costs"); return false; } // 20101001 - 601.2e if (costModificationActive) { game.getContinuousEffects().costModification(this, game); } else { costModificationActive = true; } UUID activatorId = controllerId; if ((this instanceof ActivatedAbilityImpl) && ((ActivatedAbilityImpl) this).getActivatorId() != null) { activatorId = ((ActivatedAbilityImpl) this).getActivatorId(); } // 20100716 - 601.2f (noMana is not used here, because mana costs were cleared for this ability // before adding additional costs and applying cost modification effects) if (!manaCostsToPay.pay(this, game, sourceId, activatorId, false, null)) { return false; // cancel during mana payment } // 20100716 - 601.2g if (!costs.pay(this, game, sourceId, activatorId, noMana, null)) { logger.debug("activate failed - non mana costs"); return false; } if (!game.isSimulation()) { // inform about x costs now, so canceled announcements are not shown in the log if (announceString != null) { game.informPlayers(announceString); } if (variableManaCost != null) { int xValue = getManaCostsToPay().getX(); game.informPlayers( controller.getLogName() + " announces a value of " + xValue + " for " + variableManaCost.getText()); } } activated = true; // fire if tapped for mana (may only fire now because else costs of ability itself can be payed // with mana of abilities that trigger for that event if (this.getAbilityType().equals(AbilityType.MANA)) { for (Cost cost : costs) { if (cost instanceof TapSourceCost) { Mana mana = null; Effect effect = getEffects().get(0); if (effect instanceof DynamicManaEffect) { mana = ((DynamicManaEffect) effect).getMana(game, this); } else if (effect instanceof BasicManaEffect) { mana = ((BasicManaEffect) effect).getMana(game, this); } if (mana != null && mana.getAny() == 0) { // if mana == null or Any > 0 the event has to be fired in the mana effect // to know which mana was produced ManaEvent event = new ManaEvent( GameEvent.EventType.TAPPED_FOR_MANA, sourceId, sourceId, controllerId, mana); if (!game.replaceEvent(event)) { game.fireEvent(event); } } break; } } } return true; }
@Override public boolean apply(Game game, Ability source) { MageObject sourceObject = source.getSourceObject(game); if (sourceObject == null) { return false; } Map<UUID, Set<Card>> permanentsOwned = new HashMap<>(); Collection<Permanent> permanents = game.getBattlefield().getAllActivePermanents(); for (Permanent permanent : permanents) { Set<Card> set = permanentsOwned.get(permanent.getOwnerId()); if (set == null) { set = new LinkedHashSet<>(); } set.add(permanent); permanentsOwned.put(permanent.getOwnerId(), set); } // shuffle permanents into owner's library Map<UUID, Integer> permanentsCount = new HashMap<>(); for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { Set<Card> set = permanentsOwned.remove(playerId); Integer count = 0; if (set != null) { count = set.size(); player.moveCards(set, Zone.BATTLEFIELD, Zone.LIBRARY, source, game); } if (count > 0) { player.shuffleLibrary(game); } permanentsCount.put(playerId, count); } } game.applyEffects(); // so effects from creatures that were on the battlefield won't trigger // from draw or later put into play Map<UUID, CardsImpl> cardsRevealed = new HashMap<>(); // draw cards and reveal them for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { Integer count = Math.min(permanentsCount.get(player.getId()), player.getLibrary().size()); CardsImpl cards = new CardsImpl(); for (int i = 0; i < count; i++) { Card card = player.getLibrary().removeFromTop(game); if (card != null) { cards.add(card); } } player.revealCards(sourceObject.getIdName() + " (" + player.getName() + ")", cards, game); cardsRevealed.put(player.getId(), cards); } } // put artifacts, creaturs and lands onto the battlefield for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { CardsImpl cards = cardsRevealed.get(player.getId()); for (Card card : cards.getCards(game)) { if (card != null && (card.getCardType().contains(CardType.ARTIFACT) || card.getCardType().contains(CardType.CREATURE) || card.getCardType().contains(CardType.LAND))) { card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), player.getId()); cards.remove(card); } } } } // put enchantments onto the battlefield for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { CardsImpl cards = cardsRevealed.get(player.getId()); for (Card card : cards.getCards(game)) { if (card != null && card.getCardType().contains(CardType.ENCHANTMENT)) { card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), player.getId()); cards.remove(card); } } } } // put the rest of the cards on buttom of the library for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { CardsImpl cards = cardsRevealed.get(player.getId()); player.putCardsOnBottomOfLibrary(cards, game, source, false); } } return true; }