diff --git a/Mage.Client/src/main/resources/image.url.properties b/Mage.Client/src/main/resources/image.url.properties index b541b78b01..7ea834fa98 100644 --- a/Mage.Client/src/main/resources/image.url.properties +++ b/Mage.Client/src/main/resources/image.url.properties @@ -74,6 +74,6 @@ dd3evg=ddaevg dd3gvl=ddagvl dd3jvc=ddajvc # Remove setname as soon as the images can be downloaded -ignore.urls=TOK,AER,PCA,DDS,ANB,AKH,HOU,MM3 +ignore.urls=TOK,PCA,DDS,ANB,AKH,HOU,MM3 # sets ordered by release time (newest goes first) token.lookup.order=ANB,HOU,MM3,DDS,AKH,DD3DVD,DD3EVG,DD3GVL,DD3JVC,H09,AER,PCA,C16,V16,MPS,KLD,DDR,CN2,EMN,EMA,SOI,DDQ,CP,CMA,ARENA,SUS,APAC,EURO,UGIN,C15,OGW,EXP,DDP,BFZ,DRB,V09,V10,V11,V12,V13,V14,V15,TPR,MPRP,DD3,DDO,ORI,MM2,PTC,DTK,FRF,KTK,M15,VMA,CNS,JOU,BNG,THS,DDL,M14,MMA,DGM,GTC,RTR,M13,AVR,DDI,DKA,ISD,M12,NPH,MBS,SOM,M11,ROE,DDE,WWK,ZEN,M10,GVL,ARB,DVD,CFX,JVC,ALA,EVE,SHM,EVG,MOR,LRW,10E,CLS,CHK,GRC \ No newline at end of file diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index f3a4fcae96..ea2af12d1a 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -85,6 +85,7 @@ import mage.game.draft.Draft; import mage.game.events.GameEvent; import mage.game.match.Match; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.game.tournament.Tournament; import mage.players.Player; import mage.players.PlayerImpl; @@ -811,14 +812,14 @@ public class HumanPlayer extends PlayerImpl { } @Override - public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { + public boolean playMana(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { payManaMode = true; - boolean result = playManaHandling(unpaid, promptText, game); + boolean result = playManaHandling(abilityToCast, unpaid, promptText, game); payManaMode = false; return result; } - protected boolean playManaHandling(ManaCost unpaid, String promptText, Game game) { + protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { updateGameStatePriority("playMana", game); Map options = new HashMap<>(); if (unpaid.getText().contains("P}")) { @@ -832,7 +833,7 @@ public class HumanPlayer extends PlayerImpl { if (response.getBoolean() != null) { return false; } else if (response.getUUID() != null) { - playManaAbilities(unpaid, game); + playManaAbilities(abilityToCast, unpaid, game); } else if (response.getString() != null && response.getString().equals("special")) { if (unpaid instanceof ManaCostsImpl) { specialManaAction(unpaid, game); @@ -897,12 +898,17 @@ public class HumanPlayer extends PlayerImpl { return xValue; } - protected void playManaAbilities(ManaCost unpaid, Game game) { + protected void playManaAbilities(Ability abilityToCast, ManaCost unpaid, Game game) { updateGameStatePriority("playManaAbilities", game); MageObject object = game.getObject(response.getUUID()); if (object == null) { return; } + Spell spell = game.getStack().getSpell(abilityToCast.getSourceId()); + if (spell != null && spell.isDoneActivatingManaAbilities()) { + game.informPlayer(this, "You can't no longer use activated mana abilities to pay for the current spell. Cancel and recast the spell and activate mana abilities first."); + return; + } Zone zone = game.getState().getZone(object.getId()); if (zone != null) { LinkedHashMap useableAbilities = getUseableManaAbilities(object, zone, game); diff --git a/Mage.Sets/src/mage/cards/b/BattleAtTheBridge.java b/Mage.Sets/src/mage/cards/b/BattleAtTheBridge.java new file mode 100644 index 0000000000..6fa4797ebc --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BattleAtTheBridge.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.cards.b; + +import java.util.UUID; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.ImproviseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class BattleAtTheBridge extends CardImpl { + + public BattleAtTheBridge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{B}"); + + // Improvise (Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}.) + addAbility(new ImproviseAbility()); + + // Target creature gets -X/-X until end of turn. You gain X life. + DynamicValue x = new SignInversionDynamicValue(new ManacostVariableValue()); + this.getSpellAbility().addEffect(new BoostTargetEffect(x, x, Duration.EndOfTurn, true)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new GainLifeEffect(new ManacostVariableValue())); + } + + public BattleAtTheBridge(final BattleAtTheBridge card) { + super(card); + } + + @Override + public BattleAtTheBridge copy() { + return new BattleAtTheBridge(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AetherRevolt.java b/Mage.Sets/src/mage/sets/AetherRevolt.java index 882968b4ae..78a4536f2a 100644 --- a/Mage.Sets/src/mage/sets/AetherRevolt.java +++ b/Mage.Sets/src/mage/sets/AetherRevolt.java @@ -66,6 +66,7 @@ public class AetherRevolt extends ExpansionSet { this.parentSet = Kaladesh.getInstance(); cards.add(new SetCardInfo("Ajani Unyielding", 127, Rarity.MYTHIC, mage.cards.a.AjaniUnyielding.class)); cards.add(new SetCardInfo("Ajani, Valiant Protector", 185, Rarity.MYTHIC, mage.cards.a.AjaniValiantProtector.class)); + cards.add(new SetCardInfo("Battle at the Bridge", 53, Rarity.RARE, mage.cards.b.BattleAtTheBridge.class)); cards.add(new SetCardInfo("Consulate Crackdown", 11, Rarity.RARE, mage.cards.c.ConsulateCrackdown.class)); cards.add(new SetCardInfo("Dark Intimations", 128, Rarity.RARE, mage.cards.d.DarkIntimations.class)); cards.add(new SetCardInfo("Disallow", 31, Rarity.RARE, mage.cards.d.Disallow.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java b/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java new file mode 100644 index 0000000000..12ff28c861 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java @@ -0,0 +1,149 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.keyword; + +import java.util.UUID; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpecialAction; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.AlternateManaPaymentAbility; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.constants.AbilityType; +import mage.constants.ManaType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.ManaPool; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author LevelX2 + */ +public class ImproviseAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility { + + private static final FilterArtifactPermanent filterUntapped = new FilterArtifactPermanent(); + + static { + filterUntapped.add(Predicates.not(new TappedPredicate())); + } + + public ImproviseAbility() { + super(Zone.STACK, null); + this.setRuleAtTheTop(true); + } + + public ImproviseAbility(final ImproviseAbility ability) { + super(ability); + } + + @Override + public ImproviseAbility copy() { + return new ImproviseAbility(this); + } + + @Override + public void addSpecialAction(Ability source, Game game, ManaCost unpaid) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && game.getBattlefield().contains(filterUntapped, controller.getId(), 1, game)) { + if (source.getAbilityType().equals(AbilityType.SPELL) && unpaid.getMana().getGeneric() > 0) { + SpecialAction specialAction = new ImproviseSpecialAction(unpaid); + specialAction.setControllerId(source.getControllerId()); + specialAction.setSourceId(source.getSourceId()); + // create filter for possible artifacts to tap + FilterControlledArtifactPermanent filter = new FilterControlledArtifactPermanent(); + filter.add(Predicates.not(new TappedPredicate())); + Target target = new TargetControlledPermanent(1, unpaid.getMana().getGeneric(), filter, true); + target.setTargetName("artifact to Improvise"); + specialAction.addTarget(target); + if (specialAction.canActivate(source.getControllerId(), game)) { + game.getState().getSpecialActions().add(specialAction); + } + } + } + } + + @Override + public String getRule() { + return "Improvise (Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}.)"; + } +} + +class ImproviseSpecialAction extends SpecialAction { + + public ImproviseSpecialAction(ManaCost unpaid) { + super(Zone.ALL, true); + setRuleVisible(false); + this.addEffect(new ImproviseEffect(unpaid)); + } + + public ImproviseSpecialAction(final ImproviseSpecialAction ability) { + super(ability); + } + + @Override + public ImproviseSpecialAction copy() { + return new ImproviseSpecialAction(this); + } +} + +class ImproviseEffect extends OneShotEffect { + + private final ManaCost unpaid; + + public ImproviseEffect(ManaCost unpaid) { + super(Outcome.Benefit); + this.unpaid = unpaid; + this.staticText = "Improvise (Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}.)"; + } + + public ImproviseEffect(final ImproviseEffect effect) { + super(effect); + this.unpaid = effect.unpaid; + } + + @Override + public ImproviseEffect copy() { + return new ImproviseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (controller != null && spell != null) { + for (UUID artifactId : this.getTargetPointer().getTargets(game, source)) { + Permanent perm = game.getPermanent(artifactId); + if (perm == null) { + continue; + } + if (!perm.isTapped() && perm.tap(game)) { + ManaPool manaPool = controller.getManaPool(); + manaPool.addMana(Mana.ColorlessMana(1), game, source); + manaPool.unlockManaType(ManaType.COLORLESS); + if (!game.isSimulation()) { + game.informPlayers("Improvise: " + controller.getLogName() + " taps " + perm.getLogName() + " to pay {1}"); + } + spell.setDoneActivatingManaAbilities(true); + } + + } + return true; + } + return false; + } + +} diff --git a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java index 29d74f3b8f..4a146d9c81 100644 --- a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java @@ -70,8 +70,10 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl if (!controlsAbility(playerId, game)) { return false; } + // check if player is in the process of playing spell costs and he is no longer allowed to use activated mana abilities (e.g. becaus he started to use improvise) //20091005 - 605.3a return costs.canPay(this, sourceId, controllerId, game); + } /** diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index ada5635bef..3fd17d3a5e 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -88,6 +88,8 @@ public class Spell extends StackObjImpl implements Card { private boolean faceDown; private boolean countered; + private boolean doneActivatingManaAbilities; // if this is true, the player is no longer allowed to pay the spell costs with activating of mana abilies + public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) { this.card = card; this.color = card.getColor(null).copy(); @@ -108,6 +110,7 @@ public class Spell extends StackObjImpl implements Card { this.controllerId = controllerId; this.fromZone = fromZone; this.countered = false; + this.doneActivatingManaAbilities = false; } public Spell(final Spell spell) { @@ -135,6 +138,7 @@ public class Spell extends StackObjImpl implements Card { this.color = spell.color.copy(); this.frameColor = spell.color.copy(); this.frameStyle = spell.frameStyle; + this.doneActivatingManaAbilities = spell.doneActivatingManaAbilities; } public boolean activate(Game game, boolean noMana) { @@ -383,6 +387,14 @@ public class Spell extends StackObjImpl implements Card { } } + public boolean isDoneActivatingManaAbilities() { + return doneActivatingManaAbilities; + } + + public void setDoneActivatingManaAbilities(boolean doneActivatingManaAbilities) { + this.doneActivatingManaAbilities = doneActivatingManaAbilities; + } + @Override public UUID getSourceId() { return card.getId();