diff --git a/Mage/src/mage/abilities/costs/mana/BuybackManaCost.java b/Mage/src/mage/abilities/costs/mana/BuybackManaCost.java new file mode 100644 index 00000000000..cee80655628 --- /dev/null +++ b/Mage/src/mage/abilities/costs/mana/BuybackManaCost.java @@ -0,0 +1,57 @@ +/* + * 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.mana; + +import mage.abilities.costs.OptionalAdditionalCostImpl; + +/** +* This cost defines a Buyback mana cost +* +* @author LevelX2 +*/ +public class BuybackManaCost extends OptionalAdditionalCostImpl { + + public BuybackManaCost(String manaString) { + super("Buyback","(You may pay an additional {cost} as you cast this spell. If you do, put this card into your hand as it resolves.)",new ManaCostsImpl(manaString)); + } + + public BuybackManaCost(final BuybackManaCost cost) { + super(cost); + } + + @Override + public BuybackManaCost copy() { + return new BuybackManaCost(this); + } + + @Override + public String getCastSuffixMessage(int position) { + return " with " + name; + } +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/keyword/BuybackAbility.java b/Mage/src/mage/abilities/keyword/BuybackAbility.java new file mode 100644 index 00000000000..e5ec6bef6c6 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/BuybackAbility.java @@ -0,0 +1,223 @@ +/* + * 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.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.OptionalAdditionalCost; +import mage.abilities.costs.OptionalAdditionalCostImpl; +import mage.abilities.costs.OptionalAdditionalSourceCosts; +import mage.abilities.costs.mana.BuybackManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ + +public class BuybackAbility extends StaticAbility implements OptionalAdditionalSourceCosts { + + protected List buybackCosts = new LinkedList(); + + public BuybackAbility(String manaString) { + super(Zone.STACK, new BuybackEffect()); + this.buybackCosts.add(new BuybackManaCost(manaString)); + setRuleAtTheTop(true); + } + + public BuybackAbility(Cost cost) { + super(Zone.STACK, new BuybackEffect()); + OptionalAdditionalCostImpl buybackCost = new OptionalAdditionalCostImpl("Buyback", "(You may {cost} in addition to any other costs as you cast this spell. If you do, put this card into your hand as it resolves.)", cost); + this.buybackCosts.add(buybackCost); + setRuleAtTheTop(true); + } + + public BuybackAbility(final BuybackAbility ability) { + super(ability); + this.buybackCosts = ability.buybackCosts; + } + + @Override + public BuybackAbility copy() { + return new BuybackAbility(this); + } + + public boolean isActivated() { + for (OptionalAdditionalCost cost: buybackCosts) { + if(cost.isActivated()) { + return true; + } + } + return false; + } + + public void resetBuyback() { + for (OptionalAdditionalCost cost: buybackCosts) { + cost.reset(); + } + } + + public List getBuybackCosts () { + return buybackCosts; + } + + public void addKickerManaCost(OptionalAdditionalCost kickerCost) { + buybackCosts.add(kickerCost); + } + + @Override + public void addOptionalAdditionalCosts(Ability ability, Game game) { + if (ability instanceof SpellAbility) { + Player player = game.getPlayer(controllerId); + if (player != null) { + this.resetBuyback(); + for (OptionalAdditionalCost buybackCost: buybackCosts) { + boolean again = true; + while (again) { + String times = ""; + if (buybackCost.isRepeatable()) { + int activated = buybackCost.getActivateCount(); + times = Integer.toString(activated + 1) + (activated == 0 ? " time ":" times "); + } + if (player.chooseUse(Constants.Outcome.Benefit, "Pay " + times + buybackCost.getText(false) + " ?", game)) { + buybackCost.activate(); + for (Iterator it = ((Costs) buybackCost).iterator(); it.hasNext();) { + Cost cost = (Cost) it.next(); + if (cost instanceof ManaCostsImpl) { + ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); + } else { + ability.getCosts().add(cost.copy()); + } + } + + again = buybackCost.isRepeatable(); + } else { + again = false; + } + } + } + } + } + } + + + @Override + public String getRule() { + StringBuilder sb = new StringBuilder(); + int numberBuyback = 0; + String remarkText = ""; + for (OptionalAdditionalCost buybackCost: buybackCosts) { + if (numberBuyback == 0) { + sb.append(buybackCost.getText(false)); + remarkText = buybackCost.getReminderText(); + } else { + sb.append(" and/or ").append(buybackCost.getText(true)); + } + ++numberBuyback; + } + if (numberBuyback == 1) { + sb.append(" ").append(remarkText); + } + + return sb.toString(); + } + + @Override + public String getCastMessageSuffix() { + StringBuilder sb = new StringBuilder(); + int position = 0; + for (OptionalAdditionalCost cost : buybackCosts) { + if (cost.isActivated()) { + sb.append(cost.getCastSuffixMessage(position)); + ++position; + } + } + return sb.toString(); + } +} + +class BuybackEffect extends ReplacementEffectImpl { + + public BuybackEffect() { + super(Constants.Duration.WhileOnBattlefield, Constants.Outcome.Exile); + staticText = "When {this} resolves and you payed buyback costs, put it back to hand instead"; + } + + public BuybackEffect(final BuybackEffect effect) { + super(effect); + } + + @Override + public BuybackEffect copy() { + return new BuybackEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { + ZoneChangeEvent zEvent = (ZoneChangeEvent)event; + if (zEvent.getFromZone() == Zone.STACK && + (event.getAppliedEffects() == null || !event.getAppliedEffects().contains(this.getId()))) { + event.getAppliedEffects().add(this.getId()); + return true; + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Card card = game.getCard(source.getSourceId()); + if (card != null && source instanceof BuybackAbility) { + if (((BuybackAbility) source).isActivated()) { + return card.moveToZone(Zone.HAND, source.getId(), game, true, event.getAppliedEffects()); + } + } + return false; + } + +} \ No newline at end of file diff --git a/Mage/src/mage/cards/Card.java b/Mage/src/mage/cards/Card.java index cc661c49f14..e497daeeda3 100644 --- a/Mage/src/mage/cards/Card.java +++ b/Mage/src/mage/cards/Card.java @@ -28,6 +28,7 @@ package mage.cards; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import mage.Constants.Rarity; @@ -85,6 +86,8 @@ public interface Card extends MageObject { */ public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag); + public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects); + /** * Moves the card to an exile zone * @param exileId set to null for generic exile zone @@ -94,6 +97,10 @@ public interface Card extends MageObject { * @return true if card was moved to zone */ public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game); + + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects); + + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId); public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId); public List getMana(); diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index e92e892b763..f1b2f7c8b7e 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -246,8 +246,13 @@ public abstract class CardImpl> extends MageObjectImpl @Override public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { + return this.moveToZone(toZone, sourceId, game, flag, null); + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); if (!game.replaceEvent(event)) { if (event.getFromZone() != null) { switch (event.getFromZone()) { @@ -355,11 +360,15 @@ public abstract class CardImpl> extends MageObjectImpl } return false; } - @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { + return moveToExile(exileId, name, sourceId, game, null); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); if (!game.replaceEvent(event)) { if (fromZone != null) { switch (fromZone) { diff --git a/Mage/src/mage/game/events/GameEvent.java b/Mage/src/mage/game/events/GameEvent.java index 5d8e9bc42ea..4bc4db2d377 100644 --- a/Mage/src/mage/game/events/GameEvent.java +++ b/Mage/src/mage/game/events/GameEvent.java @@ -46,7 +46,7 @@ public class GameEvent { protected boolean flag; protected String data; protected Zone zone; - protected ArrayList appliedEffects = new ArrayList();; + protected ArrayList appliedEffects = new ArrayList(); public enum EventType { diff --git a/Mage/src/mage/game/events/ZoneChangeEvent.java b/Mage/src/mage/game/events/ZoneChangeEvent.java index d8132355294..495fd30fcb8 100644 --- a/Mage/src/mage/game/events/ZoneChangeEvent.java +++ b/Mage/src/mage/game/events/ZoneChangeEvent.java @@ -28,6 +28,7 @@ package mage.game.events; +import java.util.ArrayList; import java.util.UUID; import mage.Constants.Zone; import mage.game.permanent.Permanent; @@ -49,12 +50,31 @@ public class ZoneChangeEvent extends GameEvent { this.target = target; } + public ZoneChangeEvent(Permanent target, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone, ArrayList appliedEffects) { + super(EventType.ZONE_CHANGE, target.getId(), sourceId, playerId); + this.fromZone = fromZone; + this.toZone = toZone; + this.target = target; + if (appliedEffects != null) { + this.appliedEffects = appliedEffects; + }; + } + public ZoneChangeEvent(UUID targetId, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { super(EventType.ZONE_CHANGE, targetId, sourceId, playerId); this.fromZone = fromZone; this.toZone = toZone; } + public ZoneChangeEvent(UUID targetId, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone, ArrayList appliedEffects) { + super(EventType.ZONE_CHANGE, targetId, sourceId, playerId); + this.fromZone = fromZone; + this.toZone = toZone; + if (appliedEffects != null) { + this.appliedEffects = appliedEffects; + } + } + public ZoneChangeEvent(Permanent target, UUID playerId, Zone fromZone, Zone toZone) { this(target, null, playerId, fromZone, toZone); } diff --git a/Mage/src/mage/game/permanent/PermanentCard.java b/Mage/src/mage/game/permanent/PermanentCard.java index 3c47351a703..3cec874f345 100644 --- a/Mage/src/mage/game/permanent/PermanentCard.java +++ b/Mage/src/mage/game/permanent/PermanentCard.java @@ -28,6 +28,7 @@ package mage.game.permanent; +import java.util.ArrayList; import mage.Constants.Zone; import mage.cards.Card; import mage.cards.LevelerCard; @@ -165,13 +166,17 @@ public class PermanentCard extends PermanentImpl { public Card getCard() { return card; } - @Override public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { + return moveToZone(toZone, sourceId, game, flag, null); + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); Player controller = game.getPlayer(controllerId); if (controller != null && controller.removeFromBattlefield(this, game)) { - ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone); + ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone, appliedEffects); if (!game.replaceEvent(event)) { Player owner = game.getPlayer(ownerId); game.rememberLKI(objectId, Zone.BATTLEFIELD, this); @@ -208,13 +213,17 @@ public class PermanentCard extends PermanentImpl { return false; } - @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { + return moveToExile(exileId, name, sourceId, game, null); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); Player controller = game.getPlayer(controllerId); if (controller != null && controller.removeFromBattlefield(this, game)) { - ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED); + ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); if (!game.replaceEvent(event)) { game.rememberLKI(objectId, Zone.BATTLEFIELD, this); if (exileId == null) { diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 5eb74003eeb..f9e07173762 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -28,6 +28,7 @@ package mage.game.stack; +import java.util.ArrayList; import mage.Constants.CardType; import mage.Constants.Rarity; import mage.Constants.Zone; @@ -366,14 +367,25 @@ public class Spell> implements StackObject, Card { @Override public void adjustTargets(Ability ability, Game game) {} + @Override public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag) { + return moveToZone(zone, sourceId, game, flag, null); + } + + @Override + public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { throw new UnsupportedOperationException("Unsupported operation"); } @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.STACK, Zone.EXILED); + return moveToExile(exileId, name, sourceId, game, null); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { + ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.STACK, Zone.EXILED, appliedEffects); if (!game.replaceEvent(event)) { game.getStack().remove(this); game.rememberLKI(this.getId(), event.getFromZone(), this);