/** * Handles X mana costs and sets manaCostsToPay. * * @param game * @param noMana * @param controller * @return variableManaCost for posting to log later */ protected VariableManaCost handleManaXCosts(Game game, boolean noMana, Player controller) { // 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. // TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost VariableManaCost variableManaCost = null; for (ManaCost cost : manaCostsToPay) { if (cost instanceof VariableManaCost) { variableManaCost = (VariableManaCost) cost; break; // only one VariableManCost per spell (or is it possible to have more?) } } if (variableManaCost != null) { int xValue; if (!variableManaCost.isPaid()) { // should only happen for human players if (!noMana) { xValue = controller.announceXMana( variableManaCost.getMinX(), variableManaCost.getMaxX(), "Announce the value for " + variableManaCost.getText(), game, this); int amountMana = xValue * variableManaCost.getMultiplier(); StringBuilder manaString = threadLocalBuilder.get(); if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) { manaString.append("{").append(amountMana).append("}"); } else { String manaSymbol = null; if (variableManaCost.getFilter().isBlack()) { manaSymbol = "B"; } else if (variableManaCost.getFilter().isRed()) { manaSymbol = "R"; } else if (variableManaCost.getFilter().isBlue()) { manaSymbol = "U"; } else if (variableManaCost.getFilter().isGreen()) { manaSymbol = "G"; } else if (variableManaCost.getFilter().isWhite()) { manaSymbol = "W"; } if (manaSymbol == null) { throw new UnsupportedOperationException( "ManaFilter is not supported: " + this.toString()); } for (int i = 0; i < amountMana; i++) { manaString.append("{").append(manaSymbol).append("}"); } } manaCostsToPay.add(new ManaCostsImpl(manaString.toString())); manaCostsToPay.setX(amountMana); } variableManaCost.setPaid(); } } return variableManaCost; }
@Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); ManaCosts cost = new ManaCostsImpl("{X}"); if (you != null && you.chooseUse(Outcome.Neutral, "Do you want to to pay {X}?", game)) { int costX = you.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source.getId(), source.getControllerId(), false)) { Token token = new GoblinSoldierToken(); return token.putOntoBattlefield( costX, game, source.getSourceId(), source.getControllerId()); } } return false; }
@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; }