From c33e7ad59b9c8e6e0c86c137b2bc234164e2d9d1 Mon Sep 17 00:00:00 2001 From: LoneFox <lonefox@kapsi.fi> Date: Tue, 10 Nov 2015 20:35:48 +0200 Subject: [PATCH] Add support for selecting the counter type during resolution to RemoveCounterTargetEffect. Use it for existing cards. Fix some tooltip text issues with the effect. Implement cards: Ferropede and Spinal Parasite --- .../src/mage/sets/fifthdawn/Ferropede.java | 70 ++++++++++++++ .../mage/sets/fifthdawn/SpinalParasite.java | 74 +++++++++++++++ .../mage/sets/gatecrash/ThrullParasite.java | 91 +------------------ .../mage/sets/shadowmoor/MedicineRunner.java | 86 +----------------- .../mage/abilities/TriggeredAbilityImpl.java | 3 +- .../counter/RemoveCounterTargetEffect.java | 82 +++++++++++++---- 6 files changed, 216 insertions(+), 190 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/fifthdawn/Ferropede.java create mode 100644 Mage.Sets/src/mage/sets/fifthdawn/SpinalParasite.java diff --git a/Mage.Sets/src/mage/sets/fifthdawn/Ferropede.java b/Mage.Sets/src/mage/sets/fifthdawn/Ferropede.java new file mode 100644 index 0000000000..f680e10eea --- /dev/null +++ b/Mage.Sets/src/mage/sets/fifthdawn/Ferropede.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.sets.fifthdawn; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.counter.RemoveCounterTargetEffect; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.TargetPermanent; + +/** + * + * @author LoneFox + */ +public class Ferropede extends CardImpl { + + public Ferropede(UUID ownerId) { + super(ownerId, 122, "Ferropede", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + this.expansionSetCode = "5DN"; + this.subtype.add("Insect"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Ferropede is unblockable. + this.addAbility(new CantBeBlockedSourceAbility()); + // Whenever Ferropede deals combat damage to a player, you may remove a counter from target permanent. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new RemoveCounterTargetEffect(), true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public Ferropede(final Ferropede card) { + super(card); + } + + @Override + public Ferropede copy() { + return new Ferropede(this); + } +} diff --git a/Mage.Sets/src/mage/sets/fifthdawn/SpinalParasite.java b/Mage.Sets/src/mage/sets/fifthdawn/SpinalParasite.java new file mode 100644 index 0000000000..c80e694636 --- /dev/null +++ b/Mage.Sets/src/mage/sets/fifthdawn/SpinalParasite.java @@ -0,0 +1,74 @@ +/* + * 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.sets.fifthdawn; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.effects.common.counter.RemoveCounterTargetEffect; +import mage.abilities.keyword.SunburstAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.target.TargetPermanent; + +/** + * + * @author LoneFox + */ +public class SpinalParasite extends CardImpl { + + public SpinalParasite(UUID ownerId) { + super(ownerId, 155, "Spinal Parasite", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}"); + this.expansionSetCode = "5DN"; + this.subtype.add("Insect"); + this.power = new MageInt(-1); + this.toughness = new MageInt(-1); + + // Sunburst + this.addAbility(new SunburstAbility(this)); + // Remove two +1/+1 counters from Spinal Parasite: Remove a counter from target permanent. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RemoveCounterTargetEffect(), + new RemoveCountersSourceCost(CounterType.P1P1.createInstance(2))); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public SpinalParasite(final SpinalParasite card) { + super(card); + } + + @Override + public SpinalParasite copy() { + return new SpinalParasite(this); + } +} diff --git a/Mage.Sets/src/mage/sets/gatecrash/ThrullParasite.java b/Mage.Sets/src/mage/sets/gatecrash/ThrullParasite.java index e989bcca14..d53ebeca49 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/ThrullParasite.java +++ b/Mage.Sets/src/mage/sets/gatecrash/ThrullParasite.java @@ -27,29 +27,18 @@ */ package mage.sets.gatecrash; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.RemoveCounterTargetEffect; import mage.abilities.keyword.ExtortAbility; import mage.cards.CardImpl; -import mage.choices.Choice; -import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.Zone; -import mage.counters.Counter; -import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetNonlandPermanent; /** @@ -69,7 +58,7 @@ public class ThrullParasite extends CardImpl { // Extort this.addAbility(new ExtortAbility()); // {tap}, Pay 2 life: Remove a counter from target nonland permanent. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RemoveCounterTargetEffect(),new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RemoveCounterTargetEffect(), new TapSourceCost()); ability.addTarget(new TargetNonlandPermanent()); ability.addCost(new PayLifeCost(2)); this.addAbility(ability); @@ -84,75 +73,3 @@ public class ThrullParasite extends CardImpl { return new ThrullParasite(this); } } - -class RemoveCounterTargetEffect extends OneShotEffect { - - private CounterType counterTypeToRemove; - - public RemoveCounterTargetEffect() { - super(Outcome.Detriment); - this.staticText = "Remove a counter from target nonland permanent"; - } - - public RemoveCounterTargetEffect(CounterType counterTypeToRemove) { - super(Outcome.Detriment); - this.staticText = "Remove a " + counterTypeToRemove.getName() + " counter from target nonland permanent"; - this.counterTypeToRemove = counterTypeToRemove; - } - - public RemoveCounterTargetEffect(final RemoveCounterTargetEffect effect) { - super(effect); - this.counterTypeToRemove = effect.counterTypeToRemove; - } - - @Override - public RemoveCounterTargetEffect copy() { - return new RemoveCounterTargetEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - boolean result = false; - Player controller = game.getPlayer(source.getControllerId()); - for (UUID targetId: getTargetPointer().getTargets(game, source)) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null) { - if (permanent.getCounters().size() > 0 && (counterTypeToRemove == null || permanent.getCounters().containsKey(counterTypeToRemove))) { - String counterName = null; - if (counterTypeToRemove != null) { - counterName = counterTypeToRemove.getName(); - } else { - if (permanent.getCounters().size() > 1 && counterTypeToRemove == null) { - Choice choice = new ChoiceImpl(true); - Set<String> choices = new HashSet<String>(); - for (Counter counter : permanent.getCounters().values()) { - if (permanent.getCounters().getCount(counter.getName()) > 0) { - choices.add(counter.getName()); - } - } - choice.setChoices(choices); - choice.setMessage("Choose a counter type to remove from " + permanent.getName()); - controller.choose(Outcome.Detriment, choice, game); - counterName = choice.getChoice(); - } else { - for (Counter counter : permanent.getCounters().values()) { - if (counter.getCount() > 0) { - counterName = counter.getName(); - } - } - } - } - if (counterName != null) { - permanent.removeCounters(counterName, 1, game); - if (permanent.getCounters().getCount(counterName) == 0 ){ - permanent.getCounters().removeCounter(counterName); - } - result |= true; - game.informPlayers(new StringBuilder(controller.getLogName()).append(" removes a ").append(counterName).append(" counter from ").append(permanent.getName()).toString()); - } - } - } - } - return result; - } -} diff --git a/Mage.Sets/src/mage/sets/shadowmoor/MedicineRunner.java b/Mage.Sets/src/mage/sets/shadowmoor/MedicineRunner.java index 002c393ef7..5c122ca7ae 100644 --- a/Mage.Sets/src/mage/sets/shadowmoor/MedicineRunner.java +++ b/Mage.Sets/src/mage/sets/shadowmoor/MedicineRunner.java @@ -27,29 +27,19 @@ */ package mage.sets.shadowmoor; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.RemoveCounterTargetEffect; import mage.cards.CardImpl; -import mage.choices.Choice; -import mage.choices.ChoiceImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; -import mage.counters.Counter; -import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; /** * - * @author jeffwadsworth using code from LevelX + * @author jeffwadsworth */ public class MedicineRunner extends CardImpl { @@ -77,75 +67,3 @@ public class MedicineRunner extends CardImpl { return new MedicineRunner(this); } } - -class RemoveCounterTargetEffect extends OneShotEffect { - - private CounterType counterTypeToRemove; - - public RemoveCounterTargetEffect() { - super(Outcome.Detriment); - this.staticText = "remove a counter from target permanent"; - } - - public RemoveCounterTargetEffect(CounterType counterTypeToRemove) { - super(Outcome.Detriment); - this.staticText = "remove a " + counterTypeToRemove.getName() + " counter from target permanent"; - this.counterTypeToRemove = counterTypeToRemove; - } - - public RemoveCounterTargetEffect(final RemoveCounterTargetEffect effect) { - super(effect); - this.counterTypeToRemove = effect.counterTypeToRemove; - } - - @Override - public RemoveCounterTargetEffect copy() { - return new RemoveCounterTargetEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - boolean result = false; - Player controller = game.getPlayer(source.getControllerId()); - for (UUID targetId : getTargetPointer().getTargets(game, source)) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null) { - if (permanent.getCounters().size() > 0 && (counterTypeToRemove == null || permanent.getCounters().containsKey(counterTypeToRemove))) { - String counterName = null; - if (counterTypeToRemove != null) { - counterName = counterTypeToRemove.getName(); - } else { - if (permanent.getCounters().size() > 1 && counterTypeToRemove == null) { - Choice choice = new ChoiceImpl(true); - Set<String> choices = new HashSet<>(); - for (Counter counter : permanent.getCounters().values()) { - if (permanent.getCounters().getCount(counter.getName()) > 0) { - choices.add(counter.getName()); - } - } - choice.setChoices(choices); - choice.setMessage("Choose a counter type to remove from " + permanent.getName()); - controller.choose(Outcome.Detriment, choice, game); - counterName = choice.getChoice(); - } else { - for (Counter counter : permanent.getCounters().values()) { - if (counter.getCount() > 0) { - counterName = counter.getName(); - } - } - } - } - if (counterName != null) { - permanent.removeCounters(counterName, 1, game); - if (permanent.getCounters().getCount(counterName) == 0) { - permanent.getCounters().removeCounter(counterName); - } - result |= true; - game.informPlayers(new StringBuilder(controller.getLogName()).append(" removes a ").append(counterName).append(" counter from ").append(permanent.getName()).toString()); - } - } - } - } - return result; - } -} diff --git a/Mage/src/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/mage/abilities/TriggeredAbilityImpl.java index aabda88d3c..2db3f564bd 100644 --- a/Mage/src/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/mage/abilities/TriggeredAbilityImpl.java @@ -133,7 +133,8 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge || ruleLow.startsWith("return") || ruleLow.startsWith("tap") || ruleLow.startsWith("untap") - || ruleLow.startsWith("put")) { + || ruleLow.startsWith("put") + || ruleLow.startsWith("remove")) { sb.append("you may "); } else { if (!ruleLow.startsWith("its controller may")) { diff --git a/Mage/src/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java b/Mage/src/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java index 5b651f6662..9f636d65b5 100644 --- a/Mage/src/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/counter/RemoveCounterTargetEffect.java @@ -28,14 +28,19 @@ package mage.abilities.effects.common.counter; +import java.util.HashSet; +import java.util.Set; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; import mage.constants.Outcome; import mage.counters.Counter; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.util.CardUtil; /** @@ -46,6 +51,11 @@ import mage.util.CardUtil; public class RemoveCounterTargetEffect extends OneShotEffect { private final Counter counter; + public RemoveCounterTargetEffect() { + super(Outcome.UnboostCreature); + counter = null; + } + public RemoveCounterTargetEffect(Counter counter) { super(Outcome.UnboostCreature); this.counter = counter; @@ -53,21 +63,24 @@ public class RemoveCounterTargetEffect extends OneShotEffect { public RemoveCounterTargetEffect(RemoveCounterTargetEffect effect) { super(effect); - this.counter = effect.counter.copy(); + this.counter = effect.counter == null ? null : effect.counter.copy(); } @Override public boolean apply(Game game, Ability source) { Permanent p = game.getPermanent(targetPointer.getFirst(game, source)); - if (p != null && p.getCounters().getCount(counter.getName()) >= counter.getCount()) { - p.removeCounters(counter.getName(), counter.getCount(), game); - if (!game.isSimulation()) - game.informPlayers(new StringBuilder("Removed ").append(counter.getCount()).append(" ").append(counter.getName()) - .append(" counter from ").append(p.getName()).toString()); - return true; + if(p != null) { + Counter toRemove = (counter == null ? selectCounterType(game, source, p) : counter); + if(toRemove != null && p.getCounters().getCount(toRemove.getName()) >= toRemove.getCount()) { + p.removeCounters(toRemove.getName(), toRemove.getCount(), game); + if(!game.isSimulation()) + game.informPlayers("Removed " + toRemove.getCount() + " " + toRemove.getName() + + " counter from " + p.getName()); + return true; + } } Card c = game.getCard(targetPointer.getFirst(game, source)); - if (c != null && c.getCounters(game).getCount(counter.getName()) >= counter.getCount()) { + if (c != null && counter != null && c.getCounters(game).getCount(counter.getName()) >= counter.getCount()) { c.removeCounters(counter.getName(), counter.getCount(), game); if (!game.isSimulation()) game.informPlayers(new StringBuilder("Removed ").append(counter.getCount()).append(" ").append(counter.getName()) @@ -78,21 +91,54 @@ public class RemoveCounterTargetEffect extends OneShotEffect { return false; } + private Counter selectCounterType(Game game, Ability source, Permanent permanent) { + Player controller = game.getPlayer(source.getControllerId()); + if(controller != null && permanent.getCounters().size() > 0) { + String counterName = null; + if(permanent.getCounters().size() > 1) { + Choice choice = new ChoiceImpl(true); + Set<String> choices = new HashSet<>(); + for(Counter counter : permanent.getCounters().values()) { + if (permanent.getCounters().getCount(counter.getName()) > 0) { + choices.add(counter.getName()); + } + } + choice.setChoices(choices); + choice.setMessage("Choose a counter type to remove from " + permanent.getName()); + controller.choose(Outcome.Detriment, choice, game); + counterName = choice.getChoice(); + } else { + for(Counter counter : permanent.getCounters().values()) { + if(counter.getCount() > 0) { + counterName = counter.getName(); + } + } + } + return new Counter(counterName); + } + return null; + } + @Override public RemoveCounterTargetEffect copy() { return new RemoveCounterTargetEffect(this); } @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } - StringBuilder sb = new StringBuilder("remove "); - sb.append(CardUtil.numberToText(counter.getCount(), "a")); - sb.append(" ").append(counter.getName()); - sb.append(counter.getCount() > 1 ?" counters from ":" counter from "); - sb.append(mode.getTargets().get(0).getTargetName()); - return sb.toString(); + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + + String text = "remove "; + if(counter == null) { + text += "a counter"; + } + else { + text += CardUtil.numberToText(counter.getCount(), "a") + " " + counter.getName(); + text += counter.getCount() > 1 ? " counters" : " counter"; + } + text += " from target " + mode.getTargets().get(0).getTargetName(); + return text; } }