diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 947ef9de667..55cae31eb5b 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -182,6 +182,12 @@ public abstract class AbilityImpl> implements Ability { // A player can't apply two alternative methods of casting or two alternative costs to a single spell. if (card != null) { for (Ability ability : card.getAbilities()) { + if (ability instanceof AlternativeSourceCosts) { + if (((AlternativeSourceCosts)ability).askToActivateAlternativeCosts(this, game)) { + // only one alternative costs may be activated + break; + } + } if (ability instanceof OptionalAdditionalSourceCosts) { ((OptionalAdditionalSourceCosts)ability).addOptionalAdditionalCosts(this, game); } diff --git a/Mage/src/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/mage/abilities/ActivatedAbilityImpl.java index bf56b30d6d1..137dbc05f23 100644 --- a/Mage/src/mage/abilities/ActivatedAbilityImpl.java +++ b/Mage/src/mage/abilities/ActivatedAbilityImpl.java @@ -34,7 +34,7 @@ import mage.Constants.AbilityType; import mage.Constants.TimingRule; import mage.Constants.Zone; import mage.MageObject; -import mage.abilities.common.SpellCastTriggeredAbility; +import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; import mage.abilities.costs.OptionalAdditionalSourceCosts; @@ -276,6 +276,9 @@ public abstract class ActivatedAbilityImpl> ex if (ability instanceof OptionalAdditionalSourceCosts) { sb.append(((OptionalAdditionalSourceCosts) ability).getCastMessageSuffix()); } + if (ability instanceof AlternativeSourceCosts) { + sb.append(((AlternativeSourceCosts) ability).getCastMessageSuffix()); + } } return sb.toString(); } diff --git a/Mage/src/mage/abilities/condition/common/EvokedCondition.java b/Mage/src/mage/abilities/condition/common/EvokedCondition.java new file mode 100644 index 00000000000..df92cd75b40 --- /dev/null +++ b/Mage/src/mage/abilities/condition/common/EvokedCondition.java @@ -0,0 +1,70 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source 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.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.keyword.EvokeAbility; +import mage.cards.Card; +import mage.game.Game; + +/** + * Checks if a the spell was cast with the alternate evoke costs + * + * @author LevelX2 + */ + +public class EvokedCondition implements Condition { + + private static EvokedCondition fInstance = null; + + private EvokedCondition() {} + + public static Condition getInstance() { + if (fInstance == null) { + fInstance = new EvokedCondition(); + } + return fInstance; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(source.getSourceId()); + if (card != null) { + for (Ability ability: card.getAbilities()) { + if (ability instanceof EvokeAbility) { + if(((EvokeAbility) ability).isActivated()) { + return true; + } + } + } + } + return false; + } +} diff --git a/Mage/src/mage/abilities/costs/AlternativeCost2.java b/Mage/src/mage/abilities/costs/AlternativeCost2.java new file mode 100644 index 00000000000..abae796f551 --- /dev/null +++ b/Mage/src/mage/abilities/costs/AlternativeCost2.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source 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.abilities.costs; + +/** + * + * @author LevelX2 + */ + + +public interface AlternativeCost2 extends Cost { + + String getName(); + + /** + * Returns the complete text for the alternate cost or if onlyCost is true + * only the pure text fore the included native cost + * + * @param onlyCost + * @return + */ + String getText(boolean onlyCost); + + /** + * Returns a reminder text, if the cost has one + * + * @return + */ + String getReminderText(); + + /** + * Returns a text suffix for the game log, that can be added to + * the cast message. + * + * @param position - if there are multiple costs, it's the postion the cost is set (starting with 0) + * @return + */ + String getCastSuffixMessage(int position); + + + /** + * If the player intends to pay the alternate cost, the cost will be activated + * + * @param activated + */ + void activate(); + + /** + * Reset the activate + * + */ + void reset(); + + /** + * Returns if the cost was activated + * + * @return + */ + boolean isActivated(); + +} diff --git a/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java b/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java new file mode 100644 index 00000000000..dccd67ba18e --- /dev/null +++ b/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source 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.abilities.costs; + +/** + * + * @author Ludwig + */ + +public class AlternativeCost2Impl > extends CostsImpl implements AlternativeCost2 { + + protected String name; + protected String reminderText; + protected String delimiter; + + protected boolean activated; + + public AlternativeCost2Impl(String name, String reminderText, Cost cost) { + this(name, " ", reminderText, cost); + } + + public AlternativeCost2Impl(String name, String delimiter, String reminderText, Cost cost) { + this.activated = false; + this.name = name; + this.delimiter = delimiter; + this.reminderText = new StringBuilder("").append(reminderText).append("").toString(); + this.add((Cost) cost); + } + + public AlternativeCost2Impl(final AlternativeCost2Impl cost) { + super(cost); + this.name = cost.name; + this.reminderText = cost.reminderText; + this.activated = cost.activated; + this.delimiter = cost.delimiter; + } + + @Override + public String getName() { + return this.name; + } + /** + * Returns the complete text for the addional cost or if onlyCost is true + * only the pure text fore the included native cost + * + * @param onlyCost + * @return + */ + @Override + public String getText(boolean onlyCost) { + if (onlyCost) { + return getText(); + } else { + return new StringBuffer(name).append(delimiter).append(getText()).toString(); + } + } + + /** + * Returns a reminder text, if the cost has one + * + * @return + */ + @Override + public String getReminderText() { + String replace = ""; + if (reminderText != null && !reminderText.isEmpty()) { + replace = reminderText.replace("{cost}", this.getText(true)); + } + return replace; + } + + /** + * Returns a text suffix for the game log, that can be added to + * the cast message. + * + * @param position - if there are multiple costs, it's the postion the cost is set (starting with 0) + * @return + */ + @Override + public String getCastSuffixMessage(int position) { + StringBuilder sb = new StringBuilder(position > 0 ? " and ":"").append(" with "); + sb.append(name); + return sb.toString(); + } + + + /** + * If the player intends to pay the cost, the cost will be activated + * + * @param activated + */ + @Override + public void activate() { + activated = true; + }; + + /** + * Reset the activate and count information + * + */ + @Override + public void reset() { + activated = false; + } + + /** + * Returns if the cost was activated + * + * @return + */ + @Override + public boolean isActivated(){ + return activated; + }; + + @Override + public AlternativeCost2Impl copy() { + return new AlternativeCost2Impl(this); + } +} diff --git a/Mage/src/mage/abilities/costs/AlternativeSourceCosts.java b/Mage/src/mage/abilities/costs/AlternativeSourceCosts.java new file mode 100644 index 00000000000..f724a0c02e5 --- /dev/null +++ b/Mage/src/mage/abilities/costs/AlternativeSourceCosts.java @@ -0,0 +1,61 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source 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.abilities.costs; + +import mage.abilities.Ability; +import mage.game.Game; + +/** + * Interface for abilities that add alternative costs to the source. + * + * Example of such additional source costs: {@link mage.abilities.keyword.KickerAbility} + * + * @author LevelX2 + */ +public interface AlternativeSourceCosts { + + /** + * Ask the player if he wants to use the alternative costs + * + * @param ability ability the alternative cost is activated for + * @param game + */ + boolean askToActivateAlternativeCosts(Ability ability, Game game); + + /** + * Was the alternative cost activated + * @return + */ + boolean isActivated(); + + /** + * Suffix string to use for game log + * @return + */ + String getCastMessageSuffix(); +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/keyword/EvokeAbility.java b/Mage/src/mage/abilities/keyword/EvokeAbility.java new file mode 100644 index 00000000000..dbe7564f211 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/EvokeAbility.java @@ -0,0 +1,166 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source 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.abilities.keyword; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import mage.Constants; +import mage.Constants.Zone; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.StaticAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.EvokedCondition; +import mage.abilities.costs.AlternativeCost2; +import mage.abilities.costs.AlternativeCost2Impl; +import mage.abilities.costs.AlternativeSourceCosts; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.cards.Card; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ + +public class EvokeAbility extends StaticAbility implements AlternativeSourceCosts { + + protected static final String EVOKE_KEYWORD = "Evoke"; + protected static final String REMINDER_TEXT = "(You may cast this spell for its evoke cost. If you do, it's sacrificed when it enters the battlefield.)"; + + protected List evokeCosts = new LinkedList(); + + public EvokeAbility(Card card, String manaString) { + super(Zone.ALL, null); + name = EVOKE_KEYWORD; + this.addEvokeCost(manaString); + Ability ability = new ConditionalTriggeredAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()), EvokedCondition.getInstance(), "Sacrifice {this} when it enters the battlefield and was evoked."); + ability.setRuleVisible(false); + ability.setRuleVisible(false); + card.addAbility(ability); + + } + + public EvokeAbility(final EvokeAbility ability) { + super(ability); + } + + @Override + public EvokeAbility copy() { + return new EvokeAbility(this); + } + + public final AlternativeCost2 addEvokeCost(String manaString) { + AlternativeCost2 evokeCost = new AlternativeCost2Impl(EVOKE_KEYWORD, REMINDER_TEXT, new ManaCostsImpl(manaString)); + evokeCosts.add(evokeCost); + return evokeCost; + } + + public void resetEvoke() { + for (AlternativeCost2 cost: evokeCosts) { + cost.reset(); + } + } + @Override + public boolean isActivated() { + for (AlternativeCost2 cost: evokeCosts) { + if(cost.isActivated()) { + return true; + } + } + return false; + } + + @Override + public boolean askToActivateAlternativeCosts(Ability ability, Game game) { + if (ability instanceof SpellAbility) { + Player player = game.getPlayer(controllerId); + if (player != null) { + this.resetEvoke(); + for (AlternativeCost2 evokeCost: evokeCosts) { + if (evokeCost.canPay(sourceId, controllerId, game) && + player.chooseUse(Constants.Outcome.Benefit, new StringBuilder(EVOKE_KEYWORD).append(" the creature for ").append(evokeCost.getText(true)).append(" ?").toString(), game)) { + evokeCost.activate(); + ability.getManaCostsToPay().clear(); + ability.getCosts().clear(); + for (Iterator it = ((Costs) evokeCost).iterator(); it.hasNext();) { + Cost cost = (Cost) it.next(); + if (cost instanceof ManaCostsImpl) { + ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); + } else { + ability.getCosts().add(cost.copy()); + } + } + } + } + } + } + return isActivated(); + } + + @Override + public String getRule() { + StringBuilder sb = new StringBuilder(); + int numberCosts = 0; + String remarkText = ""; + for (AlternativeCost2 evokeCost: evokeCosts) { + if (numberCosts == 0) { + sb.append(evokeCost.getText(false)); + remarkText = evokeCost.getReminderText(); + } else { + sb.append(" and/or ").append(evokeCost.getText(true)); + } + ++numberCosts; + } + if (numberCosts == 1) { + sb.append(" ").append(remarkText); + } + + return sb.toString(); + } + + @Override + public String getCastMessageSuffix() { + StringBuilder sb = new StringBuilder(); + int position = 0; + for (AlternativeCost2 cost : evokeCosts) { + if (cost.isActivated()) { + sb.append(cost.getCastSuffixMessage(position)); + ++position; + } + } + return sb.toString(); + } +} diff --git a/Utils/keywords.txt b/Utils/keywords.txt index f9b9fcee2d2..7703191a843 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -2,6 +2,7 @@ Annihilator|number| Bloodthirst|number| Bushido|number| Dredge|number| +Evoke|card, manaString| Soulshift|number| Basic landcycling|cost| Forestcycling|cost|