From f119965f461d9a11080b239263504bd4ce03325e Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 10 Apr 2017 14:53:43 -0500 Subject: [PATCH 1/5] - Fixed cost of Greel, Mind Raker. --- Mage.Sets/src/mage/cards/g/GreelMindRaker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Sets/src/mage/cards/g/GreelMindRaker.java b/Mage.Sets/src/mage/cards/g/GreelMindRaker.java index a27293dcbbc..7a5ac37e672 100644 --- a/Mage.Sets/src/mage/cards/g/GreelMindRaker.java +++ b/Mage.Sets/src/mage/cards/g/GreelMindRaker.java @@ -42,6 +42,9 @@ import mage.constants.Zone; import mage.target.TargetPlayer; import java.util.UUID; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.filter.FilterCard; +import mage.target.common.TargetCardInHand; /** * @@ -61,6 +64,7 @@ public class GreelMindRaker extends CardImpl { // {X}{B}, {tap}, Discard two cards: Target player discards X cards at random. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DiscardTargetEffect(new ManacostVariableValue(), true), new ManaCostsImpl("{X}{B}")); ability.addCost(new TapSourceCost()); + ability.addCost(new DiscardTargetCost(new TargetCardInHand(2, new FilterCard()))); ability.addTarget(new TargetPlayer()); this.addAbility(ability); } From 1ce32eb24ecc3c2470e1856848a9ad7da2c753e1 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Sun, 9 Apr 2017 23:39:52 -0600 Subject: [PATCH 2/5] Fix a couple of the perl scripts to work properly with the cards.f.FirstLetter style card class reorg --- Utils/gen-list-implemented-cards-for-set.pl | 2 +- Utils/gen-list-unimplemented-cards-for-set.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Utils/gen-list-implemented-cards-for-set.pl b/Utils/gen-list-implemented-cards-for-set.pl index 2e330ae0f46..664b6077e56 100755 --- a/Utils/gen-list-implemented-cards-for-set.pl +++ b/Utils/gen-list-implemented-cards-for-set.pl @@ -67,7 +67,7 @@ my $toPrint = ''; foreach my $card (sort cardSort @setCards) { my $className = toCamelCase(@{$card}[0]); - my $currentFileName = "../Mage.Sets/src/mage/sets/" . $knownSets{$setName} . "/" . $className . ".java"; + my $currentFileName = "../Mage.Sets/src/mage/cards/" . lc(substr($className, 0, 1)) . "/" . $className . ".java"; if (-e $currentFileName) { if ($toPrint) { $toPrint .= "\n"; diff --git a/Utils/gen-list-unimplemented-cards-for-set.pl b/Utils/gen-list-unimplemented-cards-for-set.pl index a01e5662e8b..15df889f215 100755 --- a/Utils/gen-list-unimplemented-cards-for-set.pl +++ b/Utils/gen-list-unimplemented-cards-for-set.pl @@ -94,7 +94,7 @@ foreach my $card (sort cardSort @setCards) { $cardNames {@{$card}[0]} = 1; - my $currentFileName = "../Mage.Sets/src/mage/sets/" . $knownSets{$setName} . "/" . $className . ".java"; + my $currentFileName = "../Mage.Sets/src/mage/cards/" . lc(substr($className, 0, 1)) . "/" . $className . ".java"; if(! -e $currentFileName) { $cardNames {@{$card}[0]} = 0; if ($toPrint) { From b33e03862a8b863fbabce3f7766b3b6480b5c0bc Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Sun, 9 Apr 2017 23:41:03 -0600 Subject: [PATCH 3/5] Work in progress changes to support As Foretold * Modifies how cards with no mana cost are handled. You can now begin to cast them if there is an AlternativeCost that would allow you to play them. --- Mage.Sets/src/mage/cards/a/AsForetold.java | 228 ++++++++++++++++++ Mage.Sets/src/mage/sets/Amonkhet.java | 1 + .../mage/abilities/effects/EffectImpl.java | 2 +- .../main/java/mage/players/PlayerImpl.java | 6 - 4 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/a/AsForetold.java diff --git a/Mage.Sets/src/mage/cards/a/AsForetold.java b/Mage.Sets/src/mage/cards/a/AsForetold.java new file mode 100644 index 00000000000..62b523ddd02 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AsForetold.java @@ -0,0 +1,228 @@ +/* + * 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.a; + +import java.util.UUID; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.Watcher; + +/** + * + * @author stravant + * + * Note, this card is pretty hacky in its implementation due to the fact that the alternative cost system doesn't + * really support "once each turn" alternative costs in an obvious way, but it should work in all scenarios as far + * as I can see. + */ +public class AsForetold extends CardImpl { + public AsForetold(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + + // At the beginning of your upkeep, put a time counter on As Foretold. + addAbility( + new BeginningOfUpkeepTriggeredAbility( + new AddCountersSourceEffect(CounterType.TIME.createInstance(), new StaticValue(1), true), + TargetController.YOU, + /* optional = */false)); + + // Once each turn, you may pay {0} rather than pay the mana cost for a spell you cast with converted mana cost X or less, where X is the number of time counters on As Foretold. + addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new AsForetoldAddAltCostEffect()), new AsForetoldAltCostUsedWatcher()); + } + + public AsForetold(final AsForetold card) { + super(card); + } + + @Override + public AsForetold copy() { + return new AsForetold(this); + } +} + +/** + * Used to determine what cast objects to apply the alternative cost to + */ +class SpellWithManaCostLessThanOrEqualToCondition implements Condition { + private int counters; + + public SpellWithManaCostLessThanOrEqualToCondition(int counters) { + this.counters = counters; + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject object = game.getObject(source.getSourceId()); + return object != null && !object.isLand() && object.getConvertedManaCost() <= counters; + } +} + + +/** + * Special AlternativeCostSourceAbility implementation. We wrap the call to askToActivateAlternativeCosts in order to + * tell when the alternative cost is used, and mark it as having been used this turn in the watcher + */ +class AsForetoldAlternativeCost extends AlternativeCostSourceAbility { + private UUID sourceAsForetold; + + AsForetoldAlternativeCost(UUID sourceAsForetold, int timeCounters) { + super(new ManaCostsImpl("{0}"), new SpellWithManaCostLessThanOrEqualToCondition(timeCounters)); + this.sourceAsForetold = sourceAsForetold; + } + + AsForetoldAlternativeCost(final AsForetoldAlternativeCost ability) { + super(ability); + this.sourceAsForetold = ability.sourceAsForetold; + } + + @Override + public AsForetoldAlternativeCost copy() { + return new AsForetoldAlternativeCost(this); + } + + @Override + public boolean askToActivateAlternativeCosts(Ability ability, Game game) { + boolean activated = super.askToActivateAlternativeCosts(ability, game); + if (activated) { + // Get the watcher + AsForetoldAltCostUsedWatcher asForetoldAltCostUsedWatcher = + (AsForetoldAltCostUsedWatcher)game.getState().getWatchers() + .get("asForetoldAltCostUsedWatcher", sourceAsForetold); + + // Mark as used + asForetoldAltCostUsedWatcher.markUsedThisTurn(); + } + return activated; + } +} + +/** + * The continuous effect that adds the option to pay the alternative cost if we haven't used it yet this turn + */ +class AsForetoldAddAltCostEffect extends ContinuousEffectImpl { + public AsForetoldAddAltCostEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Once each turn, you may pay {0} rather than pay the mana cost for a spelly ou cast with converted mana cost X or less, where X is the number of time counters on {this}."; + } + + public AsForetoldAddAltCostEffect(final AsForetoldAddAltCostEffect effect) { + super(effect); + } + + @Override + public AsForetoldAddAltCostEffect copy() { + return new AsForetoldAddAltCostEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent != null) { + // Get the watcher + AsForetoldAltCostUsedWatcher asForetoldAltCostUsedWatcher = + (AsForetoldAltCostUsedWatcher)game.getState().getWatchers() + .get("asForetoldAltCostUsedWatcher", sourcePermanent.getId()); + + // If we haven't used it yet this turn, give the option of using the zero alternative cost + if (!asForetoldAltCostUsedWatcher.hasBeenUsedThisTurn()) { + int timeCounters = sourcePermanent.getCounters(game).getCount("time"); + controller.getAlternativeSourceCosts().add(new AsForetoldAlternativeCost(sourcePermanent.getId(), timeCounters)); + } + + // Return true even if we didn't add the alt cost. We still applied the effect + return true; + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.RulesEffects; + } +} + +/** + * Watcher used as extra storage to record whether a given As Foretold has been used this turn. + * Technically speaking this watcher doesn't *watch* any GameEvents, but it does "watch" the + * alternative cost being used. That just isn't possible to watch through a game event. It's still + * helpfull to co-op the Watcher system for this since it automatically handles ZoneChangeCounter + * stuff and resetting the condition at the end of the turn. + */ +class AsForetoldAltCostUsedWatcher extends Watcher { + public AsForetoldAltCostUsedWatcher() { + super("asForetoldAltCostUsedWatcher", WatcherScope.CARD); + } + + public AsForetoldAltCostUsedWatcher(final AsForetoldAltCostUsedWatcher watcher) { + super(watcher); + } + + @Override + public void watch(GameEvent event, Game game) { + // Nothing to do, we explicitly mark used in the alternative cost + } + + public boolean hasBeenUsedThisTurn() { + return conditionMet(); + } + + public void markUsedThisTurn() { + condition = true; + } + + @Override + public AsForetoldAltCostUsedWatcher copy() { + return new AsForetoldAltCostUsedWatcher(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Amonkhet.java b/Mage.Sets/src/mage/sets/Amonkhet.java index 5b77ea5f656..215df454a3d 100644 --- a/Mage.Sets/src/mage/sets/Amonkhet.java +++ b/Mage.Sets/src/mage/sets/Amonkhet.java @@ -72,6 +72,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Angler Drake", 41, Rarity.UNCOMMON, mage.cards.a.AnglerDrake.class)); cards.add(new SetCardInfo("Anointer Priest", 3, Rarity.COMMON, mage.cards.a.AnointerPriest.class)); cards.add(new SetCardInfo("Archfiend of Ifnir", 78, Rarity.RARE, mage.cards.a.ArchfiendOfIfnir.class)); + cards.add(new SetCardInfo("As Foretold", 42, Rarity.MYTHIC, mage.cards.a.AsForetold.class)); cards.add(new SetCardInfo("Aven Mindcensor", 5, Rarity.RARE, mage.cards.a.AvenMindcensor.class)); cards.add(new SetCardInfo("Bontu's Monument", 225, Rarity.UNCOMMON, mage.cards.b.BontusMonument.class)); cards.add(new SetCardInfo("Canyon Slough", 239, Rarity.RARE, mage.cards.c.CanyonSlough.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java index 89db5169f8e..c0117cff6b4 100644 --- a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java @@ -61,8 +61,8 @@ public abstract class EffectImpl implements Effect { public EffectImpl(final EffectImpl effect) { this.id = effect.id; this.outcome = effect.outcome; - this.effectType = effect.effectType; this.staticText = effect.staticText; + this.effectType = effect.effectType; this.targetPointer = effect.targetPointer.copy(); if (effect.values != null) { values = new HashMap<>(); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index dae06986196..a346270c3a4 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1281,9 +1281,6 @@ public abstract class PlayerImpl implements Player, Serializable { if (Zone.GRAVEYARD == zone && canPlayCardsFromGraveyard()) { for (ActivatedAbility ability : candidateAbilites.getPlayableAbilities(Zone.HAND)) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility) { - continue; // You can't play spells from graveyard that have no costs - } if (ability.canActivate(playerId, game)) { output.put(ability.getId(), ability); } @@ -1293,9 +1290,6 @@ public abstract class PlayerImpl implements Player, Serializable { if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) { for (Ability ability : candidateAbilites) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility && !(Objects.equals(ability.getSourceId(), getCastSourceIdWithAlternateMana()))) { - continue; // You can't play spells that have no costs, unless you can play them without paying their mana costs - } ability.setControllerId(this.getId()); if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND) && ((ActivatedAbility) ability).canActivate(playerId, game)) { From c22a8f717ea7ff00f463db49bb97b0b499f1a5f5 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Mon, 10 Apr 2017 16:10:40 -0600 Subject: [PATCH 4/5] Fix for problem introduced in 321f5597b72a78ec6420bdc81eee944f569517a1 * Fixes a problem introduced in the JInternalFrame -> JLayeredPane change where the AbilityPicker wouldn't show up. --- Mage.Client/src/main/java/mage/client/game/GamePanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 1e239242160..2cb9a387652 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -2187,7 +2187,7 @@ public final class GamePanel extends javax.swing.JPanel { public void installComponents() { jLayeredPane.setOpaque(false); - jLayeredPane.add(abilityPicker); + jLayeredPane.add(abilityPicker, JLayeredPane.MODAL_LAYER); jLayeredPane.add(DialogManager.getManager(gameId), JLayeredPane.MODAL_LAYER, 0); abilityPicker.setVisible(false); } From 2b08d5a101216f04efc0d89cd3a878513e7ebca5 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Mon, 10 Apr 2017 17:00:27 -0600 Subject: [PATCH 5/5] As Foretold Implemented * Final change to card casting code to support as foretold. Removed all of the "Can't cast cards with no mana cost" code from the earlier parts of the casting process and simplified it to just the PlayerImpl::canPlay check and one final check in the main AbilityImpl::activate code after alternative costs have been chosen. --- Mage/src/main/java/mage/abilities/AbilityImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 1f3365e3c68..ca4f014e73a 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -286,6 +286,17 @@ public abstract class AbilityImpl implements Ability { } } + // 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost. An ability can + // also have an unpayable cost if its cost is based on the mana cost of an object with no mana cost. + // Attempting to cast a spell or activate an ability that has an unpayable cost is a legal action. + // However, attempting to pay an unpayable cost is an illegal action. + // + // We apply this now, *AFTER* the user has made the choice to pay an alternative cost for the + // spell. You can also still cast a spell with an unplayable cost by... not paying it's mana cost. + if (getAbilityType() == AbilityType.SPELL && getManaCostsToPay().isEmpty() && !noMana) { + 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.