From 17bb0a6c53c01a7845b76d0a6571ee0ae262f1d7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:40:13 +0100 Subject: [PATCH 01/65] Implemented Heroism --- Mage.Sets/src/mage/cards/h/Heroism.java | 139 ++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/Heroism.java diff --git a/Mage.Sets/src/mage/cards/h/Heroism.java b/Mage.Sets/src/mage/cards/h/Heroism.java new file mode 100644 index 0000000000..34ac472c95 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Heroism.java @@ -0,0 +1,139 @@ +/* + * 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.h; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PreventDamageByTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class Heroism extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a white creature"); + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + + public Heroism(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}"); + + // Sacrifice a white creature: For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays {2}{R}. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new HeroismEffect(), new SacrificeTargetCost(new TargetControlledCreaturePermanent(1,1, filter, true)))); + } + + public Heroism(final Heroism card) { + super(card); + } + + @Override + public Heroism copy() { + return new Heroism(this); + } +} + +class HeroismEffect extends OneShotEffect { + + private static final FilterAttackingCreature filter = new FilterAttackingCreature("attacking red creature"); + static { + filter.add(new ColorPredicate(ObjectColor.RED)); + } + + public HeroismEffect() { + super(Outcome.Benefit); + this.staticText = "For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays {2}{R}"; + } + + public HeroismEffect(final HeroismEffect effect) { + super(effect); + } + + @Override + public HeroismEffect copy() { + return new HeroismEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + game.getPlayerList(); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Player player = game.getPlayer(game.getActivePlayerId()); + Cost cost = new ManaCostsImpl("{2}{R}"); + List permanentsToPrevent = new ArrayList<>(); + for (Permanent permanent : game.getState().getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { + String message = "Pay " + cost.getText() + "? If you don't, " + permanent.getLogName() + "'s combat damage will be prevented this turn."; + if (player != null && player.chooseUse(Outcome.Neutral, message, source, game)) { + if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) { + game.informPlayers(player.getLogName() + " paid " + cost.getText() + " for " + permanent.getLogName()); + continue; + } else { + game.informPlayers(player.getLogName() + " didn't pay " + cost.getText() + " for " + permanent.getLogName()); + permanentsToPrevent.add(permanent); + } + } else { + game.informPlayers(player.getLogName() + " didn't pay " + cost.getText() + " for " + permanent.getLogName()); + permanentsToPrevent.add(permanent); + } + } + + for (Permanent permanent : permanentsToPrevent) { + ContinuousEffect effect = new PreventDamageByTargetEffect(Duration.EndOfTurn, Integer.MAX_VALUE, true); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} From c4221c21d7d2ab814756e381053dd91023efbf6d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:40:57 +0100 Subject: [PATCH 02/65] Implemented Thelon's Curse --- Mage.Sets/src/mage/cards/t/ThelonsCurse.java | 134 +++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/ThelonsCurse.java diff --git a/Mage.Sets/src/mage/cards/t/ThelonsCurse.java b/Mage.Sets/src/mage/cards/t/ThelonsCurse.java new file mode 100644 index 0000000000..220d0bce60 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThelonsCurse.java @@ -0,0 +1,134 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author spjspj & L_J + */ +public class ThelonsCurse extends CardImpl { + + private static final FilterCreaturePermanent filterCreature = new FilterCreaturePermanent("blue creatures"); + + static { + filterCreature.add(new ColorPredicate(ObjectColor.BLUE)); + } + + public ThelonsCurse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{G}"); + + // Blue creatures don't untap during their controllers' untap steps. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapInControllersUntapStepAllEffect(Duration.WhileOnBattlefield, TargetController.ANY, filterCreature))); + + // At the beginning of each player's upkeep, that player may choose any number of tapped blue creatures he or she controls and pay {U} for each creature chosen this way. If the player does, untap those creatures. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new ThelonsCurseEffect(), TargetController.ANY, false)); + } + + public ThelonsCurse(final ThelonsCurse card) { + super(card); + } + + @Override + public ThelonsCurse copy() { + return new ThelonsCurse(this); + } +} + +class ThelonsCurseEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("tapped blue creature"); + + static { + filter.add(new TappedPredicate()); + filter.add(new ColorPredicate(ObjectColor.BLUE)); + } + + ThelonsCurseEffect() { + super(Outcome.Benefit); + staticText = "that player may choose any number of tapped blue creatures he or she controls and pay {U} for each creature chosen this way. If the player does, untap those creatures."; + } + + ThelonsCurseEffect(ThelonsCurseEffect effect) { + super(effect); + } + + @Override + public ThelonsCurseEffect copy() { + return new ThelonsCurseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (player != null && sourcePermanent != null) { + int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); + while (player.canRespond() && countBattlefield > 0 && player.chooseUse(Outcome.AIDontUseIt, "Pay {U} and untap a tapped blue creature under your control?", source, game)) { + Target tappedCreatureTarget = new TargetControlledCreaturePermanent(1, 1, filter, true); + if (player.choose(Outcome.Detriment, tappedCreatureTarget, source.getSourceId(), game)) { + Cost cost = new ManaCostsImpl("U"); + Permanent tappedCreature = game.getPermanent(tappedCreatureTarget.getFirstTarget()); + + if (cost.pay(source, game, source.getSourceId(), player.getId(), false)) { + tappedCreature.untap(game); + } + } + countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); + } + return true; + } + return false; + } +} From 3c1b0d039625812f26059c9dc189b7bb56013254 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:43:14 +0100 Subject: [PATCH 03/65] Implemented Heroism and Thelon's Curse --- Mage.Sets/src/mage/sets/FallenEmpires.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/sets/FallenEmpires.java b/Mage.Sets/src/mage/sets/FallenEmpires.java index bbfed14790..8aed048f3c 100644 --- a/Mage.Sets/src/mage/sets/FallenEmpires.java +++ b/Mage.Sets/src/mage/sets/FallenEmpires.java @@ -152,6 +152,7 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Goblin Warrens", 122, Rarity.RARE, mage.cards.g.GoblinWarrens.class)); cards.add(new SetCardInfo("Hand of Justice", 142, Rarity.RARE, mage.cards.h.HandOfJustice.class)); cards.add(new SetCardInfo("Havenwood Battleground", 181, Rarity.UNCOMMON, mage.cards.h.HavenwoodBattleground.class)); + cards.add(new SetCardInfo("Heroism", 143, Rarity.UNCOMMON, mage.cards.h.Heroism.class)); cards.add(new SetCardInfo("High Tide", 35, Rarity.COMMON, HighTide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("High Tide", 36, Rarity.COMMON, HighTide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("High Tide", 37, Rarity.COMMON, HighTide.class, NON_FULL_USE_VARIOUS)); @@ -232,6 +233,7 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Thallid Devourer", 91, Rarity.UNCOMMON, mage.cards.t.ThallidDevourer.class)); cards.add(new SetCardInfo("Thelonite Druid", 92, Rarity.UNCOMMON, mage.cards.t.TheloniteDruid.class)); cards.add(new SetCardInfo("Thelonite Monk", 93, Rarity.RARE, mage.cards.t.TheloniteMonk.class)); + cards.add(new SetCardInfo("Thelon's Curse", 95, Rarity.RARE, mage.cards.t.ThelonsCurse.class)); cards.add(new SetCardInfo("Thorn Thallid", 96, Rarity.COMMON, ThornThallid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thorn Thallid", 97, Rarity.COMMON, ThornThallid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thorn Thallid", 98, Rarity.COMMON, ThornThallid.class, NON_FULL_USE_VARIOUS)); From 8755a914445bcb52624129e0ddef9f125d53e23c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:45:43 +0100 Subject: [PATCH 04/65] Implemented Dream Coat --- Mage.Sets/src/mage/cards/DreamCoat.java | 146 ++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/DreamCoat.java diff --git a/Mage.Sets/src/mage/cards/DreamCoat.java b/Mage.Sets/src/mage/cards/DreamCoat.java new file mode 100644 index 0000000000..da541eeb5e --- /dev/null +++ b/Mage.Sets/src/mage/cards/DreamCoat.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.cards.d; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BecomesColorTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class DreamCoat extends CardImpl { + + public DreamCoat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Neutral)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // {0}: Enchanted creature becomes the color or colors of your choice. Activate this ability only once each turn. + this.addAbility(new LimitedTimesPerTurnActivatedAbility(Zone.BATTLEFIELD, new BecomesColorOrColorsEnchantedEffect(), new GenericManaCost(0), 1)); + } + + public DreamCoat(final DreamCoat card) { + super(card); + } + + @Override + public DreamCoat copy() { + return new DreamCoat(this); + } +} + +class BecomesColorOrColorsEnchantedEffect extends OneShotEffect { + + public BecomesColorOrColorsEnchantedEffect() { + super(Outcome.Neutral); + this.staticText = "Enchanted creature becomes the color or colors of your choice"; + } + + public BecomesColorOrColorsEnchantedEffect(final BecomesColorOrColorsEnchantedEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent enchantment = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Permanent permanent = game.getPermanent(enchantment.getAttachedTo()); + StringBuilder sb = new StringBuilder(); + + if (controller != null && enchantment != null && permanent != null) { + for (int i = 0; i < 5; i++) { + if (i > 0) { + if (!controller.chooseUse(Outcome.Neutral, "Do you wish to choose another color?", source, game)) { + break; + } + } + ChoiceColor choiceColor = new ChoiceColor(); + controller.choose(Outcome.Benefit, choiceColor, game); + if (!controller.canRespond()) { + return false; + } + if (!game.isSimulation()) { + game.informPlayers(permanent.getName() + ": " + controller.getLogName() + " has chosen " + choiceColor.getChoice()); + } + if (choiceColor.getColor().isBlack()) { + sb.append('B'); + } else if (choiceColor.getColor().isBlue()) { + sb.append('U'); + } else if (choiceColor.getColor().isRed()) { + sb.append('R'); + } else if (choiceColor.getColor().isGreen()) { + sb.append('G'); + } else if (choiceColor.getColor().isWhite()) { + sb.append('W'); + } + } + String colors = new String(sb); + ObjectColor chosenColors = new ObjectColor(colors); + ContinuousEffect effect = new BecomesColorTargetEffect(chosenColors, Duration.Custom); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + + return true; + } + return false; + } + + @Override + public BecomesColorOrColorsEnchantedEffect copy() { + return new BecomesColorOrColorsEnchantedEffect(this); + } +} From 79e623a4d0ddc447b4b1f7371776f1ff0bec7b3c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:46:07 +0100 Subject: [PATCH 05/65] Moved Dream Coat --- Mage.Sets/src/mage/cards/{ => d}/DreamCoat.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Mage.Sets/src/mage/cards/{ => d}/DreamCoat.java (100%) diff --git a/Mage.Sets/src/mage/cards/DreamCoat.java b/Mage.Sets/src/mage/cards/d/DreamCoat.java similarity index 100% rename from Mage.Sets/src/mage/cards/DreamCoat.java rename to Mage.Sets/src/mage/cards/d/DreamCoat.java From caec2f8c8678158aec9253015e9f4529d2729f7b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:46:57 +0100 Subject: [PATCH 06/65] Implemented Infernal Medusa --- .../src/mage/cards/i/InfernalMedusa.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/InfernalMedusa.java diff --git a/Mage.Sets/src/mage/cards/i/InfernalMedusa.java b/Mage.Sets/src/mage/cards/i/InfernalMedusa.java new file mode 100644 index 0000000000..492d40838b --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InfernalMedusa.java @@ -0,0 +1,80 @@ +/* + * 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.i; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.BecomesBlockedByCreatureTriggeredAbility; +import mage.abilities.common.BlocksTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; + +/** + * + * @author L_J + */ +public class InfernalMedusa extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Wall creature"); + + static { + filter.add(Predicates.not(new SubtypePredicate(SubType.WALL))); + } + + public InfernalMedusa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + this.subtype.add(SubType.GORGON); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever Infernal Medusa blocks a creature, destroy that creature at end of combat. + Effect effect = new CreateDelayedTriggeredAbilityEffect(new AtTheEndOfCombatDelayedTriggeredAbility(new DestroyTargetEffect()), true); + effect.setText("destroy that creature at end of combat"); + this.addAbility(new BlocksTriggeredAbility(effect, false, true)); + // Whenever Infernal Medusa becomes blocked by a non-Wall creature, destroy that creature at end of combat. + this.addAbility(new BecomesBlockedByCreatureTriggeredAbility(effect, filter, false)); + } + + public InfernalMedusa(final InfernalMedusa card) { + super(card); + } + + @Override + public InfernalMedusa copy() { + return new InfernalMedusa(this); + } +} From 2c1e85dc372e2a2f7d06a909d457fbd4ff6d8754 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:47:55 +0100 Subject: [PATCH 07/65] Implemented Jovial Evil --- Mage.Sets/src/mage/cards/j/JovialEvil.java | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/j/JovialEvil.java diff --git a/Mage.Sets/src/mage/cards/j/JovialEvil.java b/Mage.Sets/src/mage/cards/j/JovialEvil.java new file mode 100644 index 0000000000..86f90219e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JovialEvil.java @@ -0,0 +1,106 @@ +/* + * 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.j; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +/** + * + * @author Backfir3 + */ +public class JovialEvil extends CardImpl { + + public JovialEvil(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}"); + + // Jovial Evil deals X damage to target opponent, where X is twice the number of white creatures that player controls. + this.getSpellAbility().addEffect(new JovialEvilEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + public JovialEvil(final JovialEvil card) { + super(card); + } + + @Override + public JovialEvil copy() { + return new JovialEvil(this); + } +} + +class JovialEvilEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + static{ + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + + JovialEvilEffect() { + super(Outcome.Damage); + staticText = "{this} deals X damage to target opponent, where X is twice the number of white creatures that player controls"; + } + + JovialEvilEffect(final JovialEvilEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(source.getFirstTarget()); + if (opponent != null) { + int amount = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, opponent.getId(), game)) { + amount++; + } + if (amount > 0) { + opponent.damage(amount * 2, source.getSourceId(), game, false, true); + } + return true; + } + return false; + } + + @Override + public JovialEvilEffect copy() { + return new JovialEvilEffect(this); + } +} From 3d44301cab399c81532ae579f91c6bc78a306ce7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:49:22 +0100 Subject: [PATCH 08/65] Implemented Elkin Bottle --- Mage.Sets/src/mage/cards/ElkinBottle.java | 153 ++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/ElkinBottle.java diff --git a/Mage.Sets/src/mage/cards/ElkinBottle.java b/Mage.Sets/src/mage/cards/ElkinBottle.java new file mode 100644 index 0000000000..7fa8d73a16 --- /dev/null +++ b/Mage.Sets/src/mage/cards/ElkinBottle.java @@ -0,0 +1,153 @@ +/* + * 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.e; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.game.turn.Step; +import mage.players.Library; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class ElkinBottle extends CardImpl { + + public ElkinBottle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + + // {3}, {tap}, Exile the top card of your library. Until the beginning of your next upkeep, you may play that card. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ElkinBottleExileEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + public ElkinBottle(final ElkinBottle card) { + super(card); + } + + @Override + public ElkinBottle copy() { + return new ElkinBottle(this); + } +} + +class ElkinBottleExileEffect extends OneShotEffect { + + public ElkinBottleExileEffect() { + super(Outcome.Detriment); + this.staticText = "Exile the top card of your library. Until the beginning of your next upkeep, you may play that card"; + } + + public ElkinBottleExileEffect(final ElkinBottleExileEffect effect) { + super(effect); + } + + @Override + public ElkinBottleExileEffect copy() { + return new ElkinBottleExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (sourcePermanent != null && controller != null && controller.getLibrary().hasCards()) { + Library library = controller.getLibrary(); + Card card = library.removeFromTop(game); + if (card != null) { + String exileName = new StringBuilder(sourcePermanent.getIdName()).append(" ").toString(); + controller.moveCardToExileWithInfo(card, source.getSourceId(), exileName, source.getSourceId(), game, Zone.LIBRARY, true); + ContinuousEffect effect = new ElkinBottleCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId())); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} + +class ElkinBottleCastFromExileEffect extends AsThoughEffectImpl { + + private boolean sameStep = true; + + public ElkinBottleCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.staticText = "Until the beginning of your next upkeep, you may play that card."; + } + + public ElkinBottleCastFromExileEffect(final ElkinBottleCastFromExileEffect effect) { + super(effect); + } + + @Override + public ElkinBottleCastFromExileEffect copy() { + return new ElkinBottleCastFromExileEffect(this); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (game.getPhase().getStep().getType() == PhaseStep.UPKEEP) { + if (!sameStep && game.getActivePlayerId().equals(source.getControllerId()) || game.getPlayer(source.getControllerId()).hasReachedNextTurnAfterLeaving()) { + return true; + } + } else { + sameStep = false; + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + return source.getControllerId().equals(affectedControllerId) + && sourceId.equals(getTargetPointer().getFirst(game, source)); + } + +} From 4c0114ca170ee0e7948818d98844c4e6c9216340 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:49:48 +0100 Subject: [PATCH 09/65] Moved Elkin Bottle --- Mage.Sets/src/mage/cards/{ => e}/ElkinBottle.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Mage.Sets/src/mage/cards/{ => e}/ElkinBottle.java (100%) diff --git a/Mage.Sets/src/mage/cards/ElkinBottle.java b/Mage.Sets/src/mage/cards/e/ElkinBottle.java similarity index 100% rename from Mage.Sets/src/mage/cards/ElkinBottle.java rename to Mage.Sets/src/mage/cards/e/ElkinBottle.java From 4e6814feed314add70beb40f75e33533dbc92cee Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:51:14 +0100 Subject: [PATCH 10/65] Implemented Primordial Ooze --- .../src/mage/cards/p/PrimordialOoze.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PrimordialOoze.java diff --git a/Mage.Sets/src/mage/cards/p/PrimordialOoze.java b/Mage.Sets/src/mage/cards/p/PrimordialOoze.java new file mode 100644 index 0000000000..bea95cb56c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrimordialOoze.java @@ -0,0 +1,113 @@ +/* + * 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.p; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksEachCombatStaticAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class PrimordialOoze extends CardImpl { + + public PrimordialOoze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{R}"); + this.subtype.add(SubType.OOZE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Primordial Ooze attacks each combat if able. + this.addAbility(new AttacksEachCombatStaticAbility()); + // At the beginning of your upkeep, put a +1/+1 counter on Primordial Ooze. Then you may pay {X}, where X is the number of +1/+1 counters on it. If you don't, tap Primordial Ooze and it deals X damage to you. + Ability ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), TargetController.YOU, false); + ability.addEffect(new PrimordialOozeEffect()); + this.addAbility(ability); + } + + public PrimordialOoze(final PrimordialOoze card) { + super(card); + } + + @Override + public PrimordialOoze copy() { + return new PrimordialOoze(this); + } +} + +class PrimordialOozeEffect extends OneShotEffect { + + public PrimordialOozeEffect() { + super(Outcome.Detriment); + this.staticText = "Then you may pay {X}, where X is the number of +1/+1 counters on it. If you don't, tap {this} and it deals X damage to you"; + } + + public PrimordialOozeEffect(final PrimordialOozeEffect effect) { + super(effect); + } + + @Override + public PrimordialOozeEffect copy() { + return new PrimordialOozeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourceObject = (Permanent) source.getSourceObjectIfItStillExists(game); + if (controller != null && sourceObject != null) { + int counter = sourceObject.getCounters(game).getCount(CounterType.P1P1); + Cost cost = new ManaCostsImpl<>("{" + counter + '}'); + if (!(controller.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + " to prevent taking " + counter + " damage from " + sourceObject.getLogName() + "?", source, game) + && cost.pay(source, game, source.getSourceId(), controller.getId(), false, null))) { + sourceObject.tap(game); + controller.damage(counter, source.getSourceId(), game, false, true); + } + return true; + } + return false; + } +} From 31bf5450b48c249cfc90f3a06bb79d86a9081690 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:52:56 +0100 Subject: [PATCH 11/65] Implemented Typhoon --- Mage.Sets/src/mage/cards/t/Typhoon.java | 106 ++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/Typhoon.java diff --git a/Mage.Sets/src/mage/cards/t/Typhoon.java b/Mage.Sets/src/mage/cards/t/Typhoon.java new file mode 100644 index 0000000000..2d17c5fc5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Typhoon.java @@ -0,0 +1,106 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class Typhoon extends CardImpl { + + public Typhoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}"); + + // Typhoon deals damage to each opponent equal to the number of Islands that player controls. + this.getSpellAbility().addEffect(new TyphoonEffect()); + } + + public Typhoon(final Typhoon card) { + super(card); + } + + @Override + public Typhoon copy() { + return new Typhoon(this); + } +} + +class TyphoonEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(); + static{ + filter.add(new SubtypePredicate(SubType.ISLAND)); + } + + TyphoonEffect() { + super(Outcome.Damage); + staticText = "{this} deals damage to each opponent equal to the number of Islands that player controls"; + } + + TyphoonEffect(final TyphoonEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + if (playerId != source.getControllerId()) { + Player player = game.getPlayer(playerId); + if (player != null) { + int amount = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) { + amount++; + } + if (amount > 0) { + player.damage(amount, source.getSourceId(), game, false, true); + } + } + } + } + return true; + } + + @Override + public TyphoonEffect copy() { + return new TyphoonEffect(this); + } +} From a2facbdb446c1f2aa42354e088dacc6cd946ac96 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:54:17 +0100 Subject: [PATCH 12/65] Implemented Al-Abara's Carpet --- .../src/mage/cards/a/AlAbarasCarpet.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AlAbarasCarpet.java diff --git a/Mage.Sets/src/mage/cards/a/AlAbarasCarpet.java b/Mage.Sets/src/mage/cards/a/AlAbarasCarpet.java new file mode 100644 index 0000000000..998ae08db8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlAbarasCarpet.java @@ -0,0 +1,110 @@ +/* + * 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.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.DamagePlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author L_J + */ +public class AlAbarasCarpet extends CardImpl { + + public AlAbarasCarpet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + // {5}, {T}: Prevent all damage that would be dealt to you this turn by attacking creatures without flying. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AlAbarasCarpetEffect(), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + } + + public AlAbarasCarpet(final AlAbarasCarpet card) { + super(card); + } + + @Override + public AlAbarasCarpet copy() { + return new AlAbarasCarpet(this); + } +} + +class AlAbarasCarpetEffect extends PreventionEffectImpl { + + private static final FilterAttackingCreature filter = new FilterAttackingCreature(); + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + AlAbarasCarpetEffect() { + super(Duration.EndOfTurn, Integer.MAX_VALUE, false); + staticText = "Prevent all damage that would be dealt to you this turn by attacking creatures without flying"; + } + + AlAbarasCarpetEffect(final AlAbarasCarpetEffect effect) { + super(effect); + } + + @Override + public AlAbarasCarpetEffect copy() { + return new AlAbarasCarpetEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (super.applies(event, source, game) && event instanceof DamagePlayerEvent && event.getAmount() > 0) { + DamagePlayerEvent damageEvent = (DamagePlayerEvent) event; + if (event.getTargetId().equals(source.getControllerId())) { + Permanent permanent = game.getPermanentOrLKIBattlefield(damageEvent.getSourceId()); + if (permanent != null && filter.match(permanent, game)) { + return true; + } + } + } + return false; + } +} From 8c5039d4f65d8074f647ace3eb8ececc6d8d6db8 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:55:39 +0100 Subject: [PATCH 13/65] Fixed duration of Grinning Totem effect --- Mage.Sets/src/mage/cards/g/GrinningTotem.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GrinningTotem.java b/Mage.Sets/src/mage/cards/g/GrinningTotem.java index 73d68a2a91..6516baa043 100644 --- a/Mage.Sets/src/mage/cards/g/GrinningTotem.java +++ b/Mage.Sets/src/mage/cards/g/GrinningTotem.java @@ -46,6 +46,7 @@ import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; +import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; @@ -129,6 +130,8 @@ class GrinningTotemSearchAndExileEffect extends OneShotEffect { } class GrinningTotemMayPlayEffect extends AsThoughEffectImpl { + + private boolean sameStep = true; public GrinningTotemMayPlayEffect() { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); @@ -147,9 +150,11 @@ class GrinningTotemMayPlayEffect extends AsThoughEffectImpl { @Override public boolean isInactive(Ability source, Game game) { if (game.getPhase().getStep().getType() == PhaseStep.UPKEEP) { - if (game.getActivePlayerId().equals(source.getControllerId())) { + if (!sameStep && game.getActivePlayerId().equals(source.getControllerId()) || game.getPlayer(source.getControllerId()).hasReachedNextTurnAfterLeaving()) { return true; } + } else { + sameStep = false; } return false; } From 7dd5445766ffcec43858be2a0abdc15f939dbc7c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:57:25 +0100 Subject: [PATCH 14/65] Implemented Telekinesis --- Mage.Sets/src/mage/cards/t/Telekinesis.java | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/Telekinesis.java diff --git a/Mage.Sets/src/mage/cards/t/Telekinesis.java b/Mage.Sets/src/mage/cards/t/Telekinesis.java new file mode 100644 index 0000000000..38d75164ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Telekinesis.java @@ -0,0 +1,64 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.PreventCombatDamageBySourceEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author L_J + */ +public class Telekinesis extends CardImpl { + + public Telekinesis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}{U}"); + + // Tap target creature. Prevent all combat damage that would be dealt by that creature this turn. It doesn't untap during its controller's next two untap steps. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellAbility().addEffect(new PreventCombatDamageBySourceEffect(Duration.EndOfTurn).setText("Prevent all combat damage that would be dealt by that creature this turn")); + this.getSpellAbility().addEffect(new DontUntapInControllersNextUntapStepTargetEffect("It", true, null)); + } + + public Telekinesis(final Telekinesis card) { + super(card); + } + + @Override + public Telekinesis copy() { + return new Telekinesis(this); + } +} From de21a86913310c4d03ce66225a7b0a4d29966d45 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 12:59:46 +0100 Subject: [PATCH 15/65] Implemented Telekinesis --- ...nControllersNextUntapStepTargetEffect.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java index f0de6583bd..8825e0617a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java @@ -49,6 +49,8 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR private UUID onlyIfControlledByPlayer; private String targetName; + // used for Telekinesis - skips next two untap steps if true + private boolean twoSteps; // holds the info what target was already handled in Untap of its controller private final Map handledTargetsDuringTurn = new HashMap<>(); @@ -63,7 +65,7 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR } public DontUntapInControllersNextUntapStepTargetEffect(String targetName) { - this(targetName, null); + this(targetName, false, null); } /** @@ -73,14 +75,20 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR * controlled by that controller, null = it works for all players */ public DontUntapInControllersNextUntapStepTargetEffect(String targetName, UUID onlyIfControlledByPlayer) { + this(targetName, false, onlyIfControlledByPlayer); + } + + public DontUntapInControllersNextUntapStepTargetEffect(String targetName, boolean twoSteps, UUID onlyIfControlledByPlayer) { super(Duration.Custom, Outcome.Detriment, false, true); this.targetName = targetName; + this.twoSteps = twoSteps; this.onlyIfControlledByPlayer = onlyIfControlledByPlayer; } public DontUntapInControllersNextUntapStepTargetEffect(final DontUntapInControllersNextUntapStepTargetEffect effect) { super(effect); this.targetName = effect.targetName; + this.twoSteps = effect.twoSteps; this.handledTargetsDuringTurn.putAll(effect.handledTargetsDuringTurn); this.onlyIfControlledByPlayer = effect.onlyIfControlledByPlayer; } @@ -112,7 +120,7 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR @Override public boolean applies(GameEvent event, Ability source, Game game) { - // the check if a permanent untap pahse is already handled is needed if multiple effects are added to prevent untap in next untap step of controller + // the check if a permanent untap phase is already handled is needed if multiple effects are added to prevent untap in next untap step of controller // if we don't check it for every untap step of a turn only one effect would be consumed instead of all be valid for the next untap step if (event.getType() == EventType.UNTAP_STEP) { boolean allHandled = true; @@ -127,13 +135,14 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR allHandled = false; } else if (!handledTargetsDuringTurn.get(targetId)) { // if it was already ready to be handled on an previous Untap step set it to done if not already so - handledTargetsDuringTurn.put(targetId, true); + handledTargetsDuringTurn.put(targetId, !twoSteps); } } else { allHandled = false; } } } + if (allHandled) { discard(); } @@ -146,7 +155,7 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent != null && game.getActivePlayerId().equals(permanent.getControllerId())) { if ((onlyIfControlledByPlayer == null) || game.getActivePlayerId().equals(onlyIfControlledByPlayer)) { // If onlyIfControlledByPlayer is set, then don't apply unless we're currently controlled by the specified player. - handledTargetsDuringTurn.put(event.getTargetId(), true); + handledTargetsDuringTurn.put(event.getTargetId(), !twoSteps); return true; } } @@ -162,11 +171,11 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR } if (targetName != null && !targetName.isEmpty()) { if (targetName.equals("Those creatures") || targetName.equals("They")) { - return targetName + " don't untap during their controller's next untap step"; + return targetName + " don't untap during their controller's next " + (twoSteps ? "two " : "") + "untap step" + (twoSteps ? "s" : ""); } else - return targetName + " doesn't untap during its controller's next untap step"; + return targetName + " doesn't untap during its controller's next " + (twoSteps ? "two " : "") + "untap step" + (twoSteps ? "s" : ""); } else { - return "target " + (mode == null ? "creature" : mode.getTargets().get(0).getTargetName()) + " doesn't untap during its controller's next untap step"; + return "target " + (mode == null ? "creature" : mode.getTargets().get(0).getTargetName()) + " doesn't untap during its controller's next " + (twoSteps ? "two " : "") + "untap step" + (twoSteps ? "s" : ""); } } From 29bdff3f10dd914d446317a5c7237ea5e1203707 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:11:00 +0100 Subject: [PATCH 16/65] Implemented Psychic Purge --- Mage.Sets/src/mage/p/PsychicPurge.java | 114 +++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Mage.Sets/src/mage/p/PsychicPurge.java diff --git a/Mage.Sets/src/mage/p/PsychicPurge.java b/Mage.Sets/src/mage/p/PsychicPurge.java new file mode 100644 index 0000000000..871b266546 --- /dev/null +++ b/Mage.Sets/src/mage/p/PsychicPurge.java @@ -0,0 +1,114 @@ +/* + * 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.p; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.stack.StackObject; +import mage.target.common.TargetCreatureOrPlayer; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class PsychicPurge extends CardImpl { + + public PsychicPurge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Psychic Purge deals 1 damage to target creature or player. + this.getSpellAbility().addEffect(new DamageTargetEffect(1)); + this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); + + // When a spell or ability an opponent controls causes you to discard Psychic Purge, that player loses 5 life. + Ability ability = new PsychicPurgeTriggeredAbility(); + this.addAbility(ability); + } + + public PsychicPurge(final PsychicPurge card) { + super(card); + } + + @Override + public PsychicPurge copy() { + return new PsychicPurge(this); + } +} + +class PsychicPurgeTriggeredAbility extends TriggeredAbilityImpl { + + public PsychicPurgeTriggeredAbility() { + super(Zone.GRAVEYARD, new LoseLifeTargetEffect(5), false); + } + + public PsychicPurgeTriggeredAbility(final PsychicPurgeTriggeredAbility ability) { + super(ability); + } + + @Override + public PsychicPurgeTriggeredAbility copy() { + return new PsychicPurgeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DISCARDED_CARD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (getSourceId().equals(event.getTargetId())) { + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + if (stackObject != null) { + if (game.getOpponents(this.getControllerId()).contains(stackObject.getControllerId())) { + Effect effect = this.getEffects().get(0); + effect.setTargetPointer(new FixedTarget(stackObject.getControllerId())); + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "When a spell or ability an opponent controls causes you to discard {this}, that player loses 5 life."; + } +} From e3e519d19ead560defc6dc094df81033f8ef75c5 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:12:14 +0100 Subject: [PATCH 17/65] Moved Psychic Purge --- Mage.Sets/src/mage/{ => cards}/p/PsychicPurge.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Mage.Sets/src/mage/{ => cards}/p/PsychicPurge.java (100%) diff --git a/Mage.Sets/src/mage/p/PsychicPurge.java b/Mage.Sets/src/mage/cards/p/PsychicPurge.java similarity index 100% rename from Mage.Sets/src/mage/p/PsychicPurge.java rename to Mage.Sets/src/mage/cards/p/PsychicPurge.java From 6ee03fa2d3bdc7339ce638bd9b8f29f256ccb5d6 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:21:31 +0100 Subject: [PATCH 18/65] Implemented Saprazzan Bailiff --- .../src/mage/cards/s/SaprazzanBailiff.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SaprazzanBailiff.java diff --git a/Mage.Sets/src/mage/cards/s/SaprazzanBailiff.java b/Mage.Sets/src/mage/cards/s/SaprazzanBailiff.java new file mode 100644 index 0000000000..9bcdece7c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaprazzanBailiff.java @@ -0,0 +1,119 @@ +/* + * 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.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandFromGraveyardAllEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class SaprazzanBailiff extends CardImpl { + + private static final FilterCard filter = new FilterCard("artifact and enchantment cards"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.ENCHANTMENT))); + } + + public SaprazzanBailiff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}{U}"); + this.subtype.add(SubType.MERFOLK); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Saprazzan Bailiff enters the battlefield, exile all artifact and enchantment cards from all graveyards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SaprazzanBailiffEffect())); + + // When Saprazzan Bailiff leaves the battlefield, return all artifact and enchantment cards from all graveyards to their owners' hands. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnToHandFromGraveyardAllEffect(filter).setText("return all artifact and enchantment cards from all graveyards to their owners' hands"), false)); + } + + public SaprazzanBailiff(final SaprazzanBailiff card) { + super(card); + } + + @Override + public SaprazzanBailiff copy() { + return new SaprazzanBailiff(this); + } +} + +class SaprazzanBailiffEffect extends OneShotEffect { + + public SaprazzanBailiffEffect() { + super(Outcome.Detriment); + staticText = "exile all artifact and enchantment cards from all graveyards"; + } + + @Override + public SaprazzanBailiffEffect copy() { + return new SaprazzanBailiffEffect(); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + for (UUID cid : player.getGraveyard().copy()) { + Card card = game.getCard(cid); + if (card != null && (card.isArtifact() || card.isEnchantment())) { + controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true); + } + } + } + } + return true; + } +} From 3d81c89fda379095fcd75107fe41c73a379a5d71 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:23:24 +0100 Subject: [PATCH 19/65] Implemented Saprazzan Bailiff --- Mage.Sets/src/mage/sets/MercadianMasques.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index d20afbfc27..64662face5 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -278,6 +278,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Sailmonger", 95, Rarity.UNCOMMON, mage.cards.s.Sailmonger.class)); cards.add(new SetCardInfo("Sand Squid", 96, Rarity.RARE, mage.cards.s.SandSquid.class)); cards.add(new SetCardInfo("Sandstone Needle", 326, Rarity.COMMON, mage.cards.s.SandstoneNeedle.class)); + cards.add(new SetCardInfo("Saprazzan Bailiff", 97, Rarity.RARE, mage.cards.s.SaprazzanBailiff.class)); cards.add(new SetCardInfo("Saprazzan Cove", 327, Rarity.UNCOMMON, mage.cards.s.SaprazzanCove.class)); cards.add(new SetCardInfo("Saprazzan Heir", 99, Rarity.RARE, mage.cards.s.SaprazzanHeir.class)); cards.add(new SetCardInfo("Saprazzan Legate", 100, Rarity.UNCOMMON, mage.cards.s.SaprazzanLegate.class)); From 9c94faf9d9b3e06f5d4d19958d41cc3a02c89edb Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:25:10 +0100 Subject: [PATCH 20/65] Implemented Elkin Bottle --- Mage.Sets/src/mage/sets/IceAge.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index f7ca4d9134..aae48d7561 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -114,6 +114,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Earthlink", 363, Rarity.RARE, mage.cards.e.Earthlink.class)); cards.add(new SetCardInfo("Elder Druid", 120, Rarity.RARE, mage.cards.e.ElderDruid.class)); cards.add(new SetCardInfo("Elemental Augury", 364, Rarity.RARE, mage.cards.e.ElementalAugury.class)); + cards.add(new SetCardInfo("Elkin Bottle", 292, Rarity.RARE, mage.cards.e.ElkinBottle.class)); cards.add(new SetCardInfo("Enduring Renewal", 247, Rarity.RARE, mage.cards.e.EnduringRenewal.class)); cards.add(new SetCardInfo("Energy Storm", 248, Rarity.RARE, mage.cards.e.EnergyStorm.class)); cards.add(new SetCardInfo("Enervate", 67, Rarity.COMMON, mage.cards.e.Enervate.class)); From 11fabfab975e4dc0d70a30aa8593440cd2221641 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:33:02 +0100 Subject: [PATCH 21/65] Implemented cards --- Mage.Sets/src/mage/sets/Legends.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Legends.java b/Mage.Sets/src/mage/sets/Legends.java index 5b74674e2a..971fc3b3c2 100644 --- a/Mage.Sets/src/mage/sets/Legends.java +++ b/Mage.Sets/src/mage/sets/Legends.java @@ -59,6 +59,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Aerathi Berserker", 131, Rarity.UNCOMMON, mage.cards.a.AerathiBerserker.class)); cards.add(new SetCardInfo("Aisling Leprechaun", 87, Rarity.COMMON, mage.cards.a.AislingLeprechaun.class)); cards.add(new SetCardInfo("Akron Legionnaire", 170, Rarity.RARE, mage.cards.a.AkronLegionnaire.class)); + cards.add(new SetCardInfo("Al-abara's Carpet", 213, Rarity.RARE, mage.cards.a.AlAbarasCarpet.class)); cards.add(new SetCardInfo("Alabaster Potion", 171, Rarity.COMMON, mage.cards.a.AlabasterPotion.class)); cards.add(new SetCardInfo("Alchor's Tomb", 214, Rarity.RARE, mage.cards.a.AlchorsTomb.class)); cards.add(new SetCardInfo("All Hallow's Eve", 2, Rarity.RARE, mage.cards.a.AllHallowsEve.class)); @@ -105,6 +106,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Divine Intervention", 177, Rarity.RARE, mage.cards.d.DivineIntervention.class)); cards.add(new SetCardInfo("Divine Offering", 178, Rarity.COMMON, mage.cards.d.DivineOffering.class)); cards.add(new SetCardInfo("Divine Transformation", 179, Rarity.RARE, mage.cards.d.DivineTransformation.class)); + cards.add(new SetCardInfo("Dream Coat", 51, Rarity.UNCOMMON, mage.cards.d.DreamCoat.class)); cards.add(new SetCardInfo("Durkwood Boars", 96, Rarity.COMMON, mage.cards.d.DurkwoodBoars.class)); cards.add(new SetCardInfo("Dwarven Song", 141, Rarity.UNCOMMON, mage.cards.d.DwarvenSong.class)); cards.add(new SetCardInfo("Elder Land Wurm", 180, Rarity.RARE, mage.cards.e.ElderLandWurm.class)); @@ -148,6 +150,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Immolation", 151, Rarity.COMMON, mage.cards.i.Immolation.class)); cards.add(new SetCardInfo("In the Eye of Chaos", 61, Rarity.RARE, mage.cards.i.InTheEyeOfChaos.class)); cards.add(new SetCardInfo("Indestructible Aura", 190, Rarity.COMMON, mage.cards.i.IndestructibleAura.class)); + cards.add(new SetCardInfo("Infernal Medusa", 22, Rarity.UNCOMMON, mage.cards.i.InfernalMedusa.class)); cards.add(new SetCardInfo("Invoke Prejudice", 62, Rarity.RARE, mage.cards.i.InvokePrejudice.class)); cards.add(new SetCardInfo("Ivory Guardians", 192, Rarity.UNCOMMON, mage.cards.i.IvoryGuardians.class)); cards.add(new SetCardInfo("Jacques le Vert", 272, Rarity.RARE, mage.cards.j.JacquesLeVert.class)); @@ -155,6 +158,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Jedit Ojanen", 274, Rarity.UNCOMMON, mage.cards.j.JeditOjanen.class)); cards.add(new SetCardInfo("Jerrard of the Closed Fist", 275, Rarity.UNCOMMON, mage.cards.j.JerrardOfTheClosedFist.class)); cards.add(new SetCardInfo("Johan", 276, Rarity.RARE, mage.cards.j.Johan.class)); + cards.add(new SetCardInfo("Jovial Evil", 23, Rarity.RARE, mage.cards.j.JovialEvil.class)); cards.add(new SetCardInfo("Juxtapose", 63, Rarity.RARE, mage.cards.j.Juxtapose.class)); cards.add(new SetCardInfo("Karakas", 248, Rarity.UNCOMMON, mage.cards.k.Karakas.class)); cards.add(new SetCardInfo("Kasimir the Lone Wolf", 277, Rarity.UNCOMMON, mage.cards.k.KasimirTheLoneWolf.class)); @@ -199,8 +203,10 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Planar Gate", 235, Rarity.RARE, mage.cards.p.PlanarGate.class)); cards.add(new SetCardInfo("Pradesh Gypsies", 111, Rarity.UNCOMMON, mage.cards.p.PradeshGypsies.class)); cards.add(new SetCardInfo("Presence of the Master", 200, Rarity.UNCOMMON, mage.cards.p.PresenceOfTheMaster.class)); + cards.add(new SetCardInfo("Primordial Ooze", 157, Rarity.UNCOMMON, mage.cards.p.PrimordialOoze.class)); cards.add(new SetCardInfo("Princess Lucrezia", 289, Rarity.UNCOMMON, mage.cards.p.PrincessLucrezia.class)); cards.add(new SetCardInfo("Psionic Entity", 67, Rarity.RARE, mage.cards.p.PsionicEntity.class)); + cards.add(new SetCardInfo("Psychic Purge", 68, Rarity.COMMON, mage.cards.p.PsychicPurge.class)); cards.add(new SetCardInfo("Pyrotechnics", 158, Rarity.COMMON, mage.cards.p.Pyrotechnics.class)); cards.add(new SetCardInfo("Rabid Wombat", 112, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); cards.add(new SetCardInfo("Radjan Spirit", 113, Rarity.UNCOMMON, mage.cards.r.RadjanSpirit.class)); @@ -240,6 +246,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Sylvan Library", 121, Rarity.UNCOMMON, mage.cards.s.SylvanLibrary.class)); cards.add(new SetCardInfo("Sylvan Paradise", 122, Rarity.UNCOMMON, mage.cards.s.SylvanParadise.class)); cards.add(new SetCardInfo("Syphon Soul", 32, Rarity.COMMON, mage.cards.s.SyphonSoul.class)); + cards.add(new SetCardInfo("Telekinesis", 79, Rarity.RARE, mage.cards.t.Telekinesis.class)); cards.add(new SetCardInfo("Teleport", 80, Rarity.RARE, mage.cards.t.Teleport.class)); cards.add(new SetCardInfo("Tetsuo Umezawa", 302, Rarity.RARE, mage.cards.t.TetsuoUmezawa.class)); cards.add(new SetCardInfo("The Abyss", 34, Rarity.RARE, mage.cards.t.TheAbyss.class)); @@ -257,6 +264,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Triassic Egg", 242, Rarity.RARE, mage.cards.t.TriassicEgg.class)); cards.add(new SetCardInfo("Tuknir Deathlock", 307, Rarity.RARE, mage.cards.t.TuknirDeathlock.class)); cards.add(new SetCardInfo("Tundra Wolves", 209, Rarity.COMMON, mage.cards.t.TundraWolves.class)); + cards.add(new SetCardInfo("Typhoon", 123, Rarity.RARE, mage.cards.t.Typhoon.class)); cards.add(new SetCardInfo("Underworld Dreams", 38, Rarity.UNCOMMON, mage.cards.u.UnderworldDreams.class)); cards.add(new SetCardInfo("Untamed Wilds", 124, Rarity.UNCOMMON, mage.cards.u.UntamedWilds.class)); cards.add(new SetCardInfo("Urborg", 255, Rarity.UNCOMMON, mage.cards.u.Urborg.class)); From 1f9749b087fc135c2f1a000bbd7924f74254643d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:35:03 +0100 Subject: [PATCH 22/65] Implemented Al-abara's Carpet --- Mage.Sets/src/mage/sets/MastersEditionIV.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/MastersEditionIV.java b/Mage.Sets/src/mage/sets/MastersEditionIV.java index 56844d6e8e..f2a8758846 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIV.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIV.java @@ -57,6 +57,7 @@ public class MastersEditionIV extends ExpansionSet { cards.add(new SetCardInfo("Acid Rain", 36, Rarity.RARE, mage.cards.a.AcidRain.class)); cards.add(new SetCardInfo("Aesthir Glider", 176, Rarity.COMMON, AesthirGlider.class)); cards.add(new SetCardInfo("Air Elemental", 37, Rarity.UNCOMMON, mage.cards.a.AirElemental.class)); + cards.add(new SetCardInfo("Al-abara's Carpet", 177, Rarity.RARE, mage.cards.a.AlAbarasCarpet.class)); cards.add(new SetCardInfo("Alaborn Musketeer", 1, Rarity.COMMON, mage.cards.a.AlabornMusketeer.class)); cards.add(new SetCardInfo("Alaborn Trooper", 2, Rarity.COMMON, mage.cards.a.AlabornTrooper.class)); cards.add(new SetCardInfo("Aladdin", 106, Rarity.RARE, mage.cards.a.Aladdin.class)); @@ -95,7 +96,7 @@ public class MastersEditionIV extends ExpansionSet { cards.add(new SetCardInfo("Clay Statue", 189, Rarity.UNCOMMON, mage.cards.c.ClayStatue.class)); cards.add(new SetCardInfo("Clockwork Avian", 190, Rarity.UNCOMMON, mage.cards.c.ClockworkAvian.class)); cards.add(new SetCardInfo("Clockwork Gnomes", 191, Rarity.UNCOMMON, mage.cards.c.ClockworkGnomes.class)); - cards.add(new SetCardInfo("Clockwork Swarm", 192, Rarity.COMMON, mage.cards.c.ClockworkSwarm.class)); + cards.add(new SetCardInfo("Clockwork Swarm", 192, Rarity.COMMON, mage.cards.c.ClockworkSwarm.class)); cards.add(new SetCardInfo("Cloud Dragon", 41, Rarity.RARE, mage.cards.c.CloudDragon.class)); cards.add(new SetCardInfo("Cloud Spirit", 42, Rarity.COMMON, mage.cards.c.CloudSpirit.class)); cards.add(new SetCardInfo("Colossus of Sardia", 193, Rarity.RARE, mage.cards.c.ColossusOfSardia.class)); @@ -186,7 +187,7 @@ public class MastersEditionIV extends ExpansionSet { cards.add(new SetCardInfo("Mahamoti Djinn", 52, Rarity.RARE, mage.cards.m.MahamotiDjinn.class)); cards.add(new SetCardInfo("Mana Matrix", 213, Rarity.RARE, mage.cards.m.ManaMatrix.class)); cards.add(new SetCardInfo("Mana Vault", 214, Rarity.RARE, mage.cards.m.ManaVault.class)); - cards.add(new SetCardInfo("Martyr's Cry", 19, Rarity.RARE, mage.cards.m.MartyrsCry.class)); + cards.add(new SetCardInfo("Martyr's Cry", 19, Rarity.RARE, mage.cards.m.MartyrsCry.class)); cards.add(new SetCardInfo("Martyrs of Korlis", 20, Rarity.UNCOMMON, mage.cards.m.MartyrsOfKorlis.class)); cards.add(new SetCardInfo("Maze of Ith", 246, Rarity.RARE, mage.cards.m.MazeOfIth.class)); cards.add(new SetCardInfo("Mightstone", 215, Rarity.COMMON, mage.cards.m.Mightstone.class)); @@ -243,10 +244,10 @@ public class MastersEditionIV extends ExpansionSet { cards.add(new SetCardInfo("Sinkhole", 97, Rarity.RARE, mage.cards.s.Sinkhole.class)); cards.add(new SetCardInfo("Sleight of Hand", 62, Rarity.COMMON, mage.cards.s.SleightOfHand.class)); cards.add(new SetCardInfo("Smoke", 137, Rarity.RARE, mage.cards.s.Smoke.class)); - cards.add(new SetCardInfo("Soldevi Golem", 228, Rarity.UNCOMMON, mage.cards.s.SoldeviGolem.class)); + cards.add(new SetCardInfo("Soldevi Golem", 228, Rarity.UNCOMMON, mage.cards.s.SoldeviGolem.class)); cards.add(new SetCardInfo("Soldevi Machinist", 63, Rarity.UNCOMMON, mage.cards.s.SoldeviMachinist.class)); cards.add(new SetCardInfo("Sol Ring", 227, Rarity.RARE, mage.cards.s.SolRing.class)); - cards.add(new SetCardInfo("Soldevi Golem", 228, Rarity.UNCOMMON, mage.cards.s.SoldeviGolem.class)); + cards.add(new SetCardInfo("Soldevi Golem", 228, Rarity.UNCOMMON, mage.cards.s.SoldeviGolem.class)); cards.add(new SetCardInfo("Soul Shred", 98, Rarity.COMMON, mage.cards.s.SoulShred.class)); cards.add(new SetCardInfo("Southern Elephant", 167, Rarity.COMMON, mage.cards.s.SouthernElephant.class)); cards.add(new SetCardInfo("Spotted Griffin", 28, Rarity.COMMON, mage.cards.s.SpottedGriffin.class)); From 636d283aecfc1781a7ea4c039d7574a3aafd48a7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:36:45 +0100 Subject: [PATCH 23/65] Implemented cards --- Mage.Sets/src/mage/sets/MastersEdition.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/sets/MastersEdition.java b/Mage.Sets/src/mage/sets/MastersEdition.java index a6029ef856..eb9d2ee293 100644 --- a/Mage.Sets/src/mage/sets/MastersEdition.java +++ b/Mage.Sets/src/mage/sets/MastersEdition.java @@ -195,6 +195,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Pox", 82, Rarity.RARE, mage.cards.p.Pox.class)); cards.add(new SetCardInfo("Preacher", 24, Rarity.RARE, mage.cards.p.Preacher.class)); cards.add(new SetCardInfo("Primal Order", 125, Rarity.RARE, mage.cards.p.PrimalOrder.class)); + cards.add(new SetCardInfo("Psychic Purge", 45, Rarity.UNCOMMON, mage.cards.p.PsychicPurge.class)); cards.add(new SetCardInfo("Psychic Venom", 46, Rarity.COMMON, mage.cards.p.PsychicVenom.class)); cards.add(new SetCardInfo("Pyroblast", 107, Rarity.COMMON, mage.cards.p.Pyroblast.class)); cards.add(new SetCardInfo("Rabid Wombat", 126, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); @@ -224,6 +225,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 189, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sylvan Library", 133, Rarity.RARE, mage.cards.s.SylvanLibrary.class)); cards.add(new SetCardInfo("Tawnos's Coffin", 169, Rarity.RARE, mage.cards.t.TawnossCoffin.class)); + cards.add(new SetCardInfo("Telekinesis", 52, Rarity.COMMON, mage.cards.t.Telekinesis.class)); cards.add(new SetCardInfo("Thawing Glaciers", 180, Rarity.RARE, mage.cards.t.ThawingGlaciers.class)); cards.add(new SetCardInfo("Thicket Basilisk", 134, Rarity.UNCOMMON, mage.cards.t.ThicketBasilisk.class)); cards.add(new SetCardInfo("Thorn Thallid", 135, Rarity.COMMON, ThornThallid.class)); From 8ca2e956a6e1061096c3557ac7165521439dca46 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:56:50 +0100 Subject: [PATCH 24/65] Implemented cards --- Mage.Sets/src/mage/sets/FifthEdition.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/FifthEdition.java b/Mage.Sets/src/mage/sets/FifthEdition.java index 0ba5e924b3..23672aa5d0 100644 --- a/Mage.Sets/src/mage/sets/FifthEdition.java +++ b/Mage.Sets/src/mage/sets/FifthEdition.java @@ -144,6 +144,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Earthquake", 223, Rarity.RARE, mage.cards.e.Earthquake.class)); cards.add(new SetCardInfo("Ebon Stronghold", 416, Rarity.UNCOMMON, mage.cards.e.EbonStronghold.class)); cards.add(new SetCardInfo("Elder Druid", 152, Rarity.RARE, mage.cards.e.ElderDruid.class)); + cards.add(new SetCardInfo("Elkin Bottle", 367, Rarity.RARE, mage.cards.e.ElkinBottle.class)); cards.add(new SetCardInfo("Elven Riders", 153, Rarity.UNCOMMON, mage.cards.e.ElvenRiders.class)); cards.add(new SetCardInfo("Elvish Archers", 154, Rarity.RARE, mage.cards.e.ElvishArchers.class)); cards.add(new SetCardInfo("Energy Flux", 83, Rarity.UNCOMMON, mage.cards.e.EnergyFlux.class)); @@ -328,7 +329,8 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Pradesh Gypsies", 179, Rarity.COMMON, mage.cards.p.PradeshGypsies.class)); cards.add(new SetCardInfo("Primal Clay", 395, Rarity.RARE, mage.cards.p.PrimalClay.class)); cards.add(new SetCardInfo("Primal Order", 180, Rarity.RARE, mage.cards.p.PrimalOrder.class)); - cards.add(new SetCardInfo("Prismatic Ward", 329, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); + cards.add(new SetCardInfo("Primordial Ooze", 261, Rarity.UNCOMMON, mage.cards.p.PrimordialOoze.class)); + cards.add(new SetCardInfo("Prismatic Ward", 329, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 112, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); cards.add(new SetCardInfo("Psychic Venom", 113, Rarity.COMMON, mage.cards.p.PsychicVenom.class)); cards.add(new SetCardInfo("Pyroblast", 262, Rarity.UNCOMMON, mage.cards.p.Pyroblast.class)); From a6b8882beca0f0d3b4cd48c64bcb41422c8f0435 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:57:37 +0100 Subject: [PATCH 25/65] Implemented Primordial Ooze --- Mage.Sets/src/mage/sets/Chronicles.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Chronicles.java b/Mage.Sets/src/mage/sets/Chronicles.java index fac22fcc4f..3bc1ca3905 100644 --- a/Mage.Sets/src/mage/sets/Chronicles.java +++ b/Mage.Sets/src/mage/sets/Chronicles.java @@ -114,6 +114,7 @@ public class Chronicles extends ExpansionSet { cards.add(new SetCardInfo("Obelisk of Undoing", 84, Rarity.RARE, mage.cards.o.ObeliskOfUndoing.class)); cards.add(new SetCardInfo("Palladia-Mors", 117, Rarity.RARE, mage.cards.p.PalladiaMors.class)); cards.add(new SetCardInfo("Petra Sphinx", 66, Rarity.RARE, mage.cards.p.PetraSphinx.class)); + cards.add(new SetCardInfo("Primordial Ooze", 54, Rarity.UNCOMMON, mage.cards.p.PrimordialOoze.class)); cards.add(new SetCardInfo("Rabid Wombat", 39, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); cards.add(new SetCardInfo("Rakalite", 85, Rarity.RARE, mage.cards.r.Rakalite.class)); cards.add(new SetCardInfo("Recall", 24, Rarity.UNCOMMON, mage.cards.r.Recall.class)); From 687d94056f258667a60a3577c0a9a6189c536f0b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 13:58:17 +0100 Subject: [PATCH 26/65] Implemented Elkin Bottle --- Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index f7fbf2feaa..cafbc870ab 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -114,6 +114,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Ebon Praetor", 89, Rarity.RARE, mage.cards.e.EbonPraetor.class)); cards.add(new SetCardInfo("Ebon Stronghold", 228, Rarity.UNCOMMON, mage.cards.e.EbonStronghold.class)); cards.add(new SetCardInfo("Elemental Augury", 193, Rarity.RARE, mage.cards.e.ElementalAugury.class)); + cards.add(new SetCardInfo("Elkin Bottle", 207, Rarity.RARE, mage.cards.e.ElkinBottle.class)); cards.add(new SetCardInfo("Elven Lyre", 208, Rarity.COMMON, mage.cards.e.ElvenLyre.class)); cards.add(new SetCardInfo("Elvish Farmer", 156, Rarity.RARE, mage.cards.e.ElvishFarmer.class)); cards.add(new SetCardInfo("Elvish Hunter", 157, Rarity.COMMON, ElvishHunter.class)); From 6679a680fdbe7cffc42fa337ad55584f97121261 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 11 Nov 2017 22:41:43 +0100 Subject: [PATCH 27/65] Changed type to Sorcery --- Mage.Sets/src/mage/cards/p/PsychicPurge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/p/PsychicPurge.java b/Mage.Sets/src/mage/cards/p/PsychicPurge.java index 871b266546..3e6c3dc46d 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicPurge.java +++ b/Mage.Sets/src/mage/cards/p/PsychicPurge.java @@ -51,7 +51,7 @@ import mage.target.targetpointer.FixedTarget; public class PsychicPurge extends CardImpl { public PsychicPurge(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}"); // Psychic Purge deals 1 damage to target creature or player. this.getSpellAbility().addEffect(new DamageTargetEffect(1)); From d2bc10d214deed0ef4e5a66df84736972476b2a0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 09:51:12 +0100 Subject: [PATCH 28/65] Implemented Spawnbroker --- Mage.Sets/src/mage/cards/s/Spawnbroker.java | 174 ++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/Spawnbroker.java diff --git a/Mage.Sets/src/mage/cards/s/Spawnbroker.java b/Mage.Sets/src/mage/cards/s/Spawnbroker.java new file mode 100644 index 0000000000..fcd1688cf4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Spawnbroker.java @@ -0,0 +1,174 @@ +/* + * 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.s; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author jeffwadsworth & L_J + */ +public class Spawnbroker extends CardImpl { + + private static final String rule = "you may exchange control of target creature you control and target creature with power less than or equal to that creature's power an opponent controls"; + + public Spawnbroker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Spawnbroker enters the battlefield, you may exchange control of target creature you control and target creature with power less than or equal to that creature's power an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExchangeControlTargetEffect(Duration.Custom, rule, false, true), true); + ability.addTarget(new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent()); + ability.addTarget(new SpawnbrokerSecondTarget()); + this.addAbility(ability); + + } + + public Spawnbroker(final Spawnbroker card) { + super(card); + } + + @Override + public Spawnbroker copy() { + return new Spawnbroker(this); + } +} + +class TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent extends TargetControlledPermanent { + + public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent() { + super(); + filter.add(new CardTypePredicate(CardType.CREATURE)); + setTargetName("creature you control"); + } + + public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(final TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent target) { + super(target); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + return super.canTarget(controllerId, id, source, game); + } + + @Override + public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { + Set possibleTargets = new HashSet<>(); + MageObject targetSource = game.getObject(sourceId); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { + if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { + possibleTargets.add(permanent.getId()); + } + } + return possibleTargets; + } + + @Override + public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent copy() { + return new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(this); + } +} + +class SpawnbrokerSecondTarget extends TargetPermanent { + + private Permanent firstTarget = null; + + public SpawnbrokerSecondTarget() { + super(); + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + filter.add(new CardTypePredicate(CardType.CREATURE)); + setTargetName("creature with power less than or equal to that creature's power an opponent controls"); + } + + public SpawnbrokerSecondTarget(final SpawnbrokerSecondTarget target) { + super(target); + this.firstTarget = target.firstTarget; + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + if (super.canTarget(id, source, game)) { + Permanent target1 = game.getPermanent(source.getFirstTarget()); + Permanent opponentPermanent = game.getPermanent(id); + if (target1 != null && opponentPermanent != null) { + return target1.getPower().getValue() >= opponentPermanent.getPower().getValue(); + } + } + return false; + } + + @Override + public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { + Set possibleTargets = new HashSet<>(); + if (firstTarget != null) { + MageObject targetSource = game.getObject(sourceId); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { + if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { + if (firstTarget.getPower().getValue() >= permanent.getPower().getValue()) { + possibleTargets.add(permanent.getId()); + } + } + } + } + return possibleTargets; + } + + @Override + public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { + firstTarget = game.getPermanent(source.getFirstTarget()); + return super.chooseTarget(Outcome.GainControl, playerId, source, game); + } + + @Override + public SpawnbrokerSecondTarget copy() { + return new SpawnbrokerSecondTarget(this); + } +} From 4beded2a9de5eee55e262f9552ae3d93dae6df08 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 09:52:02 +0100 Subject: [PATCH 29/65] Implemented Spawnbroker --- Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java b/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java index f2884e0aa5..02b7de3644 100644 --- a/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java +++ b/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java @@ -296,6 +296,7 @@ public class RavnicaCityOfGuilds extends ExpansionSet { cards.add(new SetCardInfo("Smash", 143, Rarity.COMMON, mage.cards.s.Smash.class)); cards.add(new SetCardInfo("Snapping Drake", 64, Rarity.COMMON, mage.cards.s.SnappingDrake.class)); cards.add(new SetCardInfo("Sparkmage Apprentice", 144, Rarity.COMMON, mage.cards.s.SparkmageApprentice.class)); + cards.add(new SetCardInfo("Spawnbroker", 65, Rarity.RARE, mage.cards.s.Spawnbroker.class)); cards.add(new SetCardInfo("Spectral Searchlight", 271, Rarity.UNCOMMON, mage.cards.s.SpectralSearchlight.class)); cards.add(new SetCardInfo("Stasis Cell", 66, Rarity.COMMON, mage.cards.s.StasisCell.class)); cards.add(new SetCardInfo("Stinkweed Imp", 107, Rarity.COMMON, mage.cards.s.StinkweedImp.class)); From d5dc2986ce028e47d66cdd4b886f7ce3330f141c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 16:41:22 +0100 Subject: [PATCH 30/65] Implemented Lost Hours --- Mage.Sets/src/mage/cards/l/LostHours.java | 133 ++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LostHours.java diff --git a/Mage.Sets/src/mage/cards/l/LostHours.java b/Mage.Sets/src/mage/cards/l/LostHours.java new file mode 100644 index 0000000000..3a61f20295 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostHours.java @@ -0,0 +1,133 @@ +/* + * 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.l; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPlayer; + +/** + * + * @author L_J + */ +public class LostHours extends CardImpl { + + public LostHours(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}"); + + // Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. + this.getSpellAbility().addEffect(new LostHoursEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public LostHours(final LostHours card) { + super(card); + } + + @Override + public LostHours copy() { + return new LostHours(this); + } +} + +class LostHoursEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("nonland card"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public LostHoursEffect() { + super(Outcome.Discard); + this.staticText = "Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top."; + } + + public LostHoursEffect(final LostHoursEffect effect) { + super(effect); + } + + @Override + public LostHoursEffect copy() { + return new LostHoursEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (player != null && controller != null) { + player.revealCards("Lost Hours", player.getHand(), game); + + if (player.getHand().size() > 0) { + TargetCard target = new TargetCard(Zone.HAND, new FilterCard(filter)); + if (controller.choose(Outcome.Discard, player.getHand(), target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + // Move card to third position + CardsImpl cards = new CardsImpl(); + cards.add(card); + Card cardTop = null; + Card cardSecond = null; + if (player.getLibrary().hasCards()) { + cardTop = player.getLibrary().removeFromTop(game); + } + if (player.getLibrary().hasCards()) { + cardSecond = player.getLibrary().removeFromTop(game); + } + player.putCardsOnTopOfLibrary(cards, game, source, true); + if (cardSecond != null) { + player.getLibrary().putOnTop(cardSecond, game); + } + if (cardTop != null) { + player.getLibrary().putOnTop(cardTop, game); + } + game.informPlayers(card.getLogName() + " is put into " + player.getLogName() +"'s library " + (cardTop != null ? (cardSecond != null ? "third" : "second") : "first") + " from the top"); + } + } + } + return true; + } + return false; + } + +} From 5c88fdacb6fef2b3ec8ef88d4def602d7a8f767d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 16:42:17 +0100 Subject: [PATCH 31/65] Implemented Samite Censer-Bearer --- .../src/mage/cards/s/SamiteCenserBearer.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SamiteCenserBearer.java diff --git a/Mage.Sets/src/mage/cards/s/SamiteCenserBearer.java b/Mage.Sets/src/mage/cards/s/SamiteCenserBearer.java new file mode 100644 index 0000000000..4e9b37440d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SamiteCenserBearer.java @@ -0,0 +1,108 @@ +/* + * 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.s; + +import java.util.List; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PreventDamageToTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class SamiteCenserBearer extends CardImpl { + + public SamiteCenserBearer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {W}, Sacrifice Samite Censer-Bearer: Prevent the next 1 damage that would be dealt to each creature you control this turn. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SamiteCenserBearerEffect(), new ManaCostsImpl("{W}")); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public SamiteCenserBearer(final SamiteCenserBearer card) { + super(card); + } + + @Override + public SamiteCenserBearer copy() { + return new SamiteCenserBearer(this); + } +} + +class SamiteCenserBearerEffect extends OneShotEffect { + + public SamiteCenserBearerEffect() { + super(Outcome.PreventDamage); + this.staticText = "Prevent the next 1 damage that would be dealt to each creature you control this turn"; + } + + public SamiteCenserBearerEffect(final SamiteCenserBearerEffect effect) { + super(effect); + } + + @Override + public SamiteCenserBearerEffect copy() { + return new SamiteCenserBearerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + PreventDamageToTargetEffect effect = new PreventDamageToTargetEffect(Duration.EndOfTurn, 1); + List permanents = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, player.getId(), game); + for (Permanent permanent : permanents) { + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} From e8c868ccacc1669c5d3653898dbfacbae88367a5 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 16:43:01 +0100 Subject: [PATCH 32/65] Implemented Spellwild Ouphe --- .../src/mage/cards/s/SpellwildOuphe.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SpellwildOuphe.java diff --git a/Mage.Sets/src/mage/cards/s/SpellwildOuphe.java b/Mage.Sets/src/mage/cards/s/SpellwildOuphe.java new file mode 100644 index 0000000000..8f7acd0f37 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpellwildOuphe.java @@ -0,0 +1,113 @@ +/* + * 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.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.target.Target; +import mage.util.CardUtil; + +/** + * + * @author LevelX2 & L_J + */ +public class SpellwildOuphe extends CardImpl { + + public SpellwildOuphe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + this.subtype.add(SubType.OUPHE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Spells that target Elderwood Scion cost {2} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SpellwildOupheCostReductionEffect())); + } + + public SpellwildOuphe(final SpellwildOuphe card) { + super(card); + } + + @Override + public SpellwildOuphe copy() { + return new SpellwildOuphe(this); + } +} + +class SpellwildOupheCostReductionEffect extends CostModificationEffectImpl { + + private static final String effectText = "Spells that target {this} cost {2} less to cast"; + + SpellwildOupheCostReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = effectText; + } + + SpellwildOupheCostReductionEffect(SpellwildOupheCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + SpellAbility spellAbility = (SpellAbility) abilityToModify; + CardUtil.adjustCost(spellAbility, 2); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify instanceof SpellAbility || abilityToModify instanceof FlashbackAbility) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); + for (Target target : mode.getTargets()) { + for (UUID targetUUID : target.getTargets()) { + if (targetUUID.equals(source.getSourceId())) { + return true; + } + } + } + } + } + return false; + } + + @Override + public SpellwildOupheCostReductionEffect copy() { + return new SpellwildOupheCostReductionEffect(this); + } + +} From 81cc30418550debc941b7885e20fb9a6967abcb2 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 16:43:46 +0100 Subject: [PATCH 33/65] Implemented Venser's Diffusion --- .../src/mage/cards/v/VensersDiffusion.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/v/VensersDiffusion.java diff --git a/Mage.Sets/src/mage/cards/v/VensersDiffusion.java b/Mage.Sets/src/mage/cards/v/VensersDiffusion.java new file mode 100644 index 0000000000..6abb644917 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VensersDiffusion.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.v; + +import java.util.UUID; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterPermanentOrSuspendedCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.common.TargetPermanentOrSuspendedCard; + +/** + * + * @author L_J + */ +public class VensersDiffusion extends CardImpl { + + private static final FilterPermanentOrSuspendedCard filter = new FilterPermanentOrSuspendedCard("nonland permanent or suspended card"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public VensersDiffusion (UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{U}"); + + // Return target nonland permanent or suspended card to its owner's hand. + this.getSpellAbility().addTarget(new TargetPermanentOrSuspendedCard(filter, false)); + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + } + + public VensersDiffusion (final VensersDiffusion card) { + super(card); + } + + @Override + public VensersDiffusion copy() { + return new VensersDiffusion(this); + } + +} From 6404a0b070e82e355e0ee5df2b2e9d8b427e9e0f Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 12 Nov 2017 16:44:05 +0100 Subject: [PATCH 34/65] Implemented cards --- Mage.Sets/src/mage/sets/FutureSight.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Sets/src/mage/sets/FutureSight.java b/Mage.Sets/src/mage/sets/FutureSight.java index 1fd069f0cc..f3bc906701 100644 --- a/Mage.Sets/src/mage/sets/FutureSight.java +++ b/Mage.Sets/src/mage/sets/FutureSight.java @@ -137,6 +137,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Llanowar Reborn", 171, Rarity.UNCOMMON, mage.cards.l.LlanowarReborn.class)); cards.add(new SetCardInfo("Logic Knot", 52, Rarity.COMMON, mage.cards.l.LogicKnot.class)); cards.add(new SetCardInfo("Lost Auramancers", 11, Rarity.UNCOMMON, mage.cards.l.LostAuramancers.class)); + cards.add(new SetCardInfo("Lost Hours", 69, Rarity.COMMON, mage.cards.l.LostHours.class)); cards.add(new SetCardInfo("Lucent Liminid", 24, Rarity.COMMON, mage.cards.l.LucentLiminid.class)); cards.add(new SetCardInfo("Lumithread Field", 25, Rarity.COMMON, mage.cards.l.LumithreadField.class)); cards.add(new SetCardInfo("Lymph Sliver", 26, Rarity.COMMON, mage.cards.l.LymphSliver.class)); @@ -181,6 +182,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Rites of Flourishing", 137, Rarity.RARE, mage.cards.r.RitesOfFlourishing.class)); cards.add(new SetCardInfo("River of Tears", 179, Rarity.RARE, mage.cards.r.RiverOfTears.class)); cards.add(new SetCardInfo("Saltskitter", 14, Rarity.COMMON, mage.cards.s.Saltskitter.class)); + cards.add(new SetCardInfo("Samite Censer-Bearer", 15, Rarity.COMMON, mage.cards.s.SamiteCenserBearer.class)); cards.add(new SetCardInfo("Sarcomite Myr", 56, Rarity.COMMON, mage.cards.s.SarcomiteMyr.class)); cards.add(new SetCardInfo("Scourge of Kher Ridges", 107, Rarity.RARE, mage.cards.s.ScourgeOfKherRidges.class)); cards.add(new SetCardInfo("Scout's Warning", 16, Rarity.RARE, mage.cards.s.ScoutsWarning.class)); @@ -197,6 +199,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Snake Cult Initiation", 89, Rarity.UNCOMMON, mage.cards.s.SnakeCultInitiation.class)); cards.add(new SetCardInfo("Soultether Golem", 164, Rarity.UNCOMMON, mage.cards.s.SoultetherGolem.class)); cards.add(new SetCardInfo("Sparkspitter", 109, Rarity.UNCOMMON, mage.cards.s.Sparkspitter.class)); + cards.add(new SetCardInfo("Spellwild Ouphe", 151, Rarity.UNCOMMON, mage.cards.s.SpellwildOuphe.class)); cards.add(new SetCardInfo("Spin into Myth", 60, Rarity.UNCOMMON, mage.cards.s.SpinIntoMyth.class)); cards.add(new SetCardInfo("Spirit en-Dal", 17, Rarity.UNCOMMON, mage.cards.s.SpiritEnDal.class)); cards.add(new SetCardInfo("Sporoloth Ancient", 152, Rarity.COMMON, mage.cards.s.SporolothAncient.class)); @@ -217,6 +220,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Vedalken Aethermage", 61, Rarity.COMMON, mage.cards.v.VedalkenAethermage.class)); cards.add(new SetCardInfo("Veilstone Amulet", 166, Rarity.RARE, mage.cards.v.VeilstoneAmulet.class)); cards.add(new SetCardInfo("Venser, Shaper Savant", 46, Rarity.RARE, mage.cards.v.VenserShaperSavant.class)); + cards.add(new SetCardInfo("Venser's Diffusion", 47, Rarity.COMMON, mage.cards.v.VensersDiffusion.class)); cards.add(new SetCardInfo("Virulent Sliver", 155, Rarity.COMMON, mage.cards.v.VirulentSliver.class)); cards.add(new SetCardInfo("Whetwheel", 168, Rarity.RARE, mage.cards.w.Whetwheel.class)); cards.add(new SetCardInfo("Whip-Spine Drake", 62, Rarity.COMMON, mage.cards.w.WhipSpineDrake.class)); From 18e26737ed36a00d2466e5d315ebf19c471759f0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 00:45:19 +0100 Subject: [PATCH 35/65] Implemented Honor the Fallen --- .../src/mage/cards/h/HonorTheFallen.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/HonorTheFallen.java diff --git a/Mage.Sets/src/mage/cards/h/HonorTheFallen.java b/Mage.Sets/src/mage/cards/h/HonorTheFallen.java new file mode 100644 index 0000000000..69c8f4a0a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HonorTheFallen.java @@ -0,0 +1,109 @@ +/* + * 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.h; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author L_J + */ + +public class HonorTheFallen extends CardImpl { + + public HonorTheFallen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + + // Exile all creature cards from all graveyards. You gain 1 life for each card exiled this way. + this.getSpellAbility().addEffect(new HonorTheFallenEffect()); + } + + public HonorTheFallen(final HonorTheFallen card) { + super(card); + } + + @Override + public HonorTheFallen copy() { + return new HonorTheFallen(this); + } + +} + +class HonorTheFallenEffect extends OneShotEffect { + + private static final FilterCreatureCard filter = new FilterCreatureCard(); + + public HonorTheFallenEffect() { + super(Outcome.Detriment); + staticText = "Exile all creature cards from all graveyards. You gain 1 life for each card exiled this way"; + } + + public HonorTheFallenEffect(final HonorTheFallenEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int exiledCards = 0; + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + for (Card card: player.getGraveyard().getCards(game)) { + if (filter.match(card, game)) { + if (card.moveToExile(null, "", source.getSourceId(), game)) { + exiledCards++; + } + } + } + } + } + controller.gainLife(exiledCards, game); + return true; + } + return false; + } + + @Override + public HonorTheFallenEffect copy() { + return new HonorTheFallenEffect(this); + } + +} From 0c6d3661ff8f8a121115a4165a5ebb6c0dfe0332 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 00:45:54 +0100 Subject: [PATCH 36/65] Implemented Honor the Fallen --- Mage.Sets/src/mage/sets/MercadianMasques.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index 64662face5..ad0e9aa438 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -174,6 +174,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("High Market", 320, Rarity.RARE, mage.cards.h.HighMarket.class)); cards.add(new SetCardInfo("High Seas", 83, Rarity.UNCOMMON, mage.cards.h.HighSeas.class)); cards.add(new SetCardInfo("Highway Robber", 139, Rarity.COMMON, mage.cards.h.HighwayRobber.class)); + cards.add(new SetCardInfo("Honor the Fallen", 21, Rarity.RARE, mage.cards.h.HonorTheFallen.class)); cards.add(new SetCardInfo("Hoodwink", 84, Rarity.COMMON, mage.cards.h.Hoodwink.class)); cards.add(new SetCardInfo("Horned Troll", 251, Rarity.COMMON, mage.cards.h.HornedTroll.class)); cards.add(new SetCardInfo("Horn of Plenty", 298, Rarity.RARE, mage.cards.h.HornOfPlenty.class)); From 5d1ef487bf89d40562614ed6878486436b1e0a37 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:03:37 +0100 Subject: [PATCH 37/65] Text fix --- .../effects/common/SacrificeSourceUnlessPaysEffect.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java index 8d80092432..f407374713 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java @@ -72,6 +72,7 @@ public class SacrificeSourceUnlessPaysEffect extends OneShotEffect { if (costText.toLowerCase().startsWith("discard") || costText.toLowerCase().startsWith("remove") || costText.toLowerCase().startsWith("return") + || costText.toLowerCase().startsWith("put") || costText.toLowerCase().startsWith("exile") || costText.toLowerCase().startsWith("sacrifice")) { sb.append(costText.substring(0, 1).toLowerCase()); From c02f1b78745bdb9161f8dde8390f723a19a78651 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:04:56 +0100 Subject: [PATCH 38/65] Implemented Anurid Scavenger --- .../src/mage/cards/a/AnuridScavenger.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AnuridScavenger.java diff --git a/Mage.Sets/src/mage/cards/a/AnuridScavenger.java b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java new file mode 100644 index 0000000000..c09c75d12d --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java @@ -0,0 +1,121 @@ +/* + * 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.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author emerald000 & L_J + */ +public class AnuridScavenger extends CardImpl { + + public AnuridScavenger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + this.subtype.add(SubType.FROG); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Protection from black + this.addAbility(ProtectionAbility.from(ObjectColor.BLACK)); + + // At the beginning of your upkeep, sacrifice Anurid Scavenger unless you put a card from your graveyard on the bottom of your library. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new AnuridScavengerCost()), TargetController.YOU, false)); + } + + public AnuridScavenger(final AnuridScavenger card) { + super(card); + } + + @Override + public AnuridScavenger copy() { + return new AnuridScavenger(this); + } +} + +class AnuridScavengerCost extends CostImpl { + + AnuridScavengerCost() { + this.addTarget(new TargetCardInYourGraveyard(1, 1, new FilterCard())); + this.text = "put a card from your graveyard on the bottom of your library"; + } + + + AnuridScavengerCost(final AnuridScavengerCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Player controller = game.getPlayer(controllerId); + if (controller != null) { + if (targets.choose(Outcome.Removal, controllerId, sourceId, game)) { + for (UUID targetId: targets.get(0).getTargets()) { + Card card = game.getCard(targetId); + if (card == null || game.getState().getZone(targetId) != Zone.GRAVEYARD) { + return false; + } + paid |= controller.moveCardToLibraryWithInfo(card, sourceId, game, Zone.GRAVEYARD, false, true); + } + } + + } + return paid; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + return targets.canChoose(controllerId, game); + } + + @Override + public AnuridScavengerCost copy() { + return new AnuridScavengerCost(this); + } +} From 2882272234f7e0fed587f1a4dd1fe6ae37a6434b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:05:48 +0100 Subject: [PATCH 39/65] Implemented False Memories --- Mage.Sets/src/mage/cards/f/FalseMemories.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FalseMemories.java diff --git a/Mage.Sets/src/mage/cards/f/FalseMemories.java b/Mage.Sets/src/mage/cards/f/FalseMemories.java new file mode 100644 index 0000000000..08f4cf2f03 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FalseMemories.java @@ -0,0 +1,62 @@ +/* + * 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.f; + +import java.util.UUID; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileCardFromOwnGraveyardControllerEffect; +import mage.abilities.effects.common.PutTopCardOfLibraryIntoGraveControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author L_J + */ +public class FalseMemories extends CardImpl { + + public FalseMemories(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}"); + + // Put the top seven cards of your library into your graveyard. + this.getSpellAbility().addEffect(new PutTopCardOfLibraryIntoGraveControllerEffect(7)); + // At the beginning of the next end step, exile seven cards from your graveyard. + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ExileCardFromOwnGraveyardControllerEffect(7)))); + } + + public FalseMemories(final FalseMemories card) { + super(card); + } + + @Override + public FalseMemories copy() { + return new FalseMemories(this); + } +} From 50170b9f758a89c51eef3e92fcc1ed9624ca2e54 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:06:29 +0100 Subject: [PATCH 40/65] Implemented Gurzigost --- Mage.Sets/src/mage/cards/g/Gurzigost.java | 129 ++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/Gurzigost.java diff --git a/Mage.Sets/src/mage/cards/g/Gurzigost.java b/Mage.Sets/src/mage/cards/g/Gurzigost.java new file mode 100644 index 0000000000..d708e41a4c --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Gurzigost.java @@ -0,0 +1,129 @@ +/* + * 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.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.DamageAsThoughNotBlockedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author emerald000 & L_J + */ +public class Gurzigost extends CardImpl { + + public Gurzigost(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(6); + this.toughness = new MageInt(8); + + // At the beginning of your upkeep, sacrifice Gurzigost unless you put two cards from your graveyard on the bottom of your library. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new GurzigostCost()), TargetController.YOU, false)); + + // {G}{G}, Discard a card: You may have Gurzigost assign its combat damage this turn as though it weren't blocked. + Effect effect = new GainAbilitySourceEffect(DamageAsThoughNotBlockedAbility.getInstance(), Duration.EndOfTurn); + effect.setText("You may have Gurzigost assign its combat damage this turn as though it weren't blocked"); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{G}{G}")); + ability.addCost(new DiscardCardCost()); + this.addAbility(ability); + } + + public Gurzigost(final Gurzigost card) { + super(card); + } + + @Override + public Gurzigost copy() { + return new Gurzigost(this); + } +} + +class GurzigostCost extends CostImpl { + + GurzigostCost() { + this.addTarget(new TargetCardInYourGraveyard(2, 2, new FilterCard())); + this.text = "put two cards from your graveyard on the bottom of your library"; + } + + + GurzigostCost(final GurzigostCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Player controller = game.getPlayer(controllerId); + if (controller != null) { + if (targets.choose(Outcome.Removal, controllerId, sourceId, game)) { + for (UUID targetId: targets.get(0).getTargets()) { + Card card = game.getCard(targetId); + if (card == null || game.getState().getZone(targetId) != Zone.GRAVEYARD) { + return false; + } + paid |= controller.moveCardToLibraryWithInfo(card, sourceId, game, Zone.GRAVEYARD, false, true); + } + } + + } + return paid; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + return targets.canChoose(controllerId, game); + } + + @Override + public GurzigostCost copy() { + return new GurzigostCost(this); + } +} From afe678897344d69df334b0edf0c33b9666630cfe Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:07:39 +0100 Subject: [PATCH 41/65] Implemented Pitchstone Wall --- Mage.Sets/src/mage/cards/PitchstoneWall.java | 110 +++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/PitchstoneWall.java diff --git a/Mage.Sets/src/mage/cards/PitchstoneWall.java b/Mage.Sets/src/mage/cards/PitchstoneWall.java new file mode 100644 index 0000000000..4b3a7fcb21 --- /dev/null +++ b/Mage.Sets/src/mage/cards/PitchstoneWall.java @@ -0,0 +1,110 @@ +/* + * 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.p; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class PitchstoneWall extends CardImpl { + + public PitchstoneWall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + this.subtype.add(SubType.WALL); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Defender (This creature can't attack.) + this.addAbility(DefenderAbility.getInstance()); + + // Whenever you discard a card, you may sacrifice Pitchstone Wall. If you do, return the discarded card from your graveyard to your hand. + this.addAbility(new PitchstoneWallTriggeredAbility()); + } + + public PitchstoneWall(final PitchstoneWall card) { + super(card); + } + + @Override + public PitchstoneWall copy() { + return new PitchstoneWall(this); + } +} + +class PitchstoneWallTriggeredAbility extends TriggeredAbilityImpl { + + public PitchstoneWallTriggeredAbility() { + super(Zone.BATTLEFIELD, new DoIfCostPaid(new ReturnToHandTargetEffect().setText("return the discarded card from your graveyard to your hand"), new SacrificeSourceCost()), false); + } + + public PitchstoneWallTriggeredAbility(final PitchstoneWallTriggeredAbility ability) { + super(ability); + } + + @Override + public PitchstoneWallTriggeredAbility copy() { + return new PitchstoneWallTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DISCARDED_CARD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(controllerId)) { + Effect effect = this.getEffects().get(0); + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + return true; + } + return false; + } + + @Override + public String getRule() { + return "Whenever you discard a card, " + super.getRule(); + } +} From 9436e30e46c8821a1785116651527b103240a266 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:08:02 +0100 Subject: [PATCH 42/65] Moved Pitchstone Wall --- Mage.Sets/src/mage/cards/{ => p}/PitchstoneWall.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Mage.Sets/src/mage/cards/{ => p}/PitchstoneWall.java (100%) diff --git a/Mage.Sets/src/mage/cards/PitchstoneWall.java b/Mage.Sets/src/mage/cards/p/PitchstoneWall.java similarity index 100% rename from Mage.Sets/src/mage/cards/PitchstoneWall.java rename to Mage.Sets/src/mage/cards/p/PitchstoneWall.java From 7de1107e2737428cc484e004890d60185729685c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 13 Nov 2017 22:09:26 +0100 Subject: [PATCH 43/65] Implemented cards --- Mage.Sets/src/mage/sets/Torment.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Torment.java b/Mage.Sets/src/mage/sets/Torment.java index a0d03e5286..14dee3caf4 100644 --- a/Mage.Sets/src/mage/sets/Torment.java +++ b/Mage.Sets/src/mage/sets/Torment.java @@ -58,6 +58,7 @@ public class Torment extends ExpansionSet { cards.add(new SetCardInfo("Acorn Harvest", 118, Rarity.COMMON, mage.cards.a.AcornHarvest.class)); cards.add(new SetCardInfo("Ambassador Laquatus", 23, Rarity.RARE, mage.cards.a.AmbassadorLaquatus.class)); cards.add(new SetCardInfo("Angel of Retribution", 1, Rarity.RARE, mage.cards.a.AngelOfRetribution.class)); + cards.add(new SetCardInfo("Anurid Scavenger", 119, Rarity.COMMON, mage.cards.a.AnuridScavenger.class)); cards.add(new SetCardInfo("Aquamoeba", 24, Rarity.COMMON, mage.cards.a.Aquamoeba.class)); cards.add(new SetCardInfo("Arrogant Wurm", 120, Rarity.UNCOMMON, mage.cards.a.ArrogantWurm.class)); cards.add(new SetCardInfo("Aven Trooper", 2, Rarity.COMMON, mage.cards.a.AvenTrooper.class)); @@ -93,6 +94,7 @@ public class Torment extends ExpansionSet { cards.add(new SetCardInfo("Dwell on the Past", 124, Rarity.UNCOMMON, mage.cards.d.DwellOnThePast.class)); cards.add(new SetCardInfo("Enslaved Dwarf", 96, Rarity.COMMON, mage.cards.e.EnslavedDwarf.class)); cards.add(new SetCardInfo("Faceless Butcher", 60, Rarity.COMMON, mage.cards.f.FacelessButcher.class)); + cards.add(new SetCardInfo("False Memories", 37, Rarity.RARE, mage.cards.f.FalseMemories.class)); cards.add(new SetCardInfo("Far Wanderings", 125, Rarity.COMMON, mage.cards.f.FarWanderings.class)); cards.add(new SetCardInfo("Fiery Temper", 97, Rarity.COMMON, mage.cards.f.FieryTemper.class)); cards.add(new SetCardInfo("Flash of Defiance", 99, Rarity.COMMON, mage.cards.f.FlashOfDefiance.class)); @@ -102,6 +104,7 @@ public class Torment extends ExpansionSet { cards.add(new SetCardInfo("Gravegouger", 62, Rarity.COMMON, mage.cards.g.Gravegouger.class)); cards.add(new SetCardInfo("Grim Lavamancer", 100, Rarity.RARE, mage.cards.g.GrimLavamancer.class)); cards.add(new SetCardInfo("Grotesque Hybrid", 63, Rarity.UNCOMMON, mage.cards.g.GrotesqueHybrid.class)); + cards.add(new SetCardInfo("Gurzigost", 126, Rarity.RARE, mage.cards.g.Gurzigost.class)); cards.add(new SetCardInfo("Hell-Bent Raider", 101, Rarity.RARE, mage.cards.h.HellBentRaider.class)); cards.add(new SetCardInfo("Hydromorph Guardian", 39, Rarity.COMMON, mage.cards.h.HydromorphGuardian.class)); cards.add(new SetCardInfo("Hydromorph Gull", 40, Rarity.UNCOMMON, mage.cards.h.HydromorphGull.class)); @@ -144,6 +147,7 @@ public class Torment extends ExpansionSet { cards.add(new SetCardInfo("Pay No Heed", 12, Rarity.COMMON, mage.cards.p.PayNoHeed.class)); cards.add(new SetCardInfo("Petradon", 108, Rarity.RARE, mage.cards.p.Petradon.class)); cards.add(new SetCardInfo("Petravark", 109, Rarity.COMMON, mage.cards.p.Petravark.class)); + cards.add(new SetCardInfo("Pitchstone Wall", 110, Rarity.UNCOMMON, mage.cards.p.PitchstoneWall.class)); cards.add(new SetCardInfo("Plagiarize", 44, Rarity.RARE, mage.cards.p.Plagiarize.class)); cards.add(new SetCardInfo("Possessed Aven", 45, Rarity.RARE, mage.cards.p.PossessedAven.class)); cards.add(new SetCardInfo("Possessed Barbarian", 111, Rarity.RARE, mage.cards.p.PossessedBarbarian.class)); From 4433d3f1e7ace78e7d8d0020c398d18c955f75dc Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 14 Nov 2017 23:50:46 +0100 Subject: [PATCH 44/65] Implemented Tombstone Stairwell --- .../src/mage/cards/t/TombstoneStairwell.java | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TombstoneStairwell.java diff --git a/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java b/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java new file mode 100644 index 0000000000..6ee6eb340c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java @@ -0,0 +1,232 @@ +/* + * 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.t; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.CumulativeUpkeepAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TombspawnZombieToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author L_J + */ +public class TombstoneStairwell extends CardImpl { + + public TombstoneStairwell(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{B}"); + addSuperType(SuperType.WORLD); + + // Cumulative upkeep-Pay {1}{B}. + this.addAbility(new CumulativeUpkeepAbility(new ManaCostsImpl("{1}{B}"))); + + // At the beginning of each upkeep, if Tombstone Stairwell is on the battlefield, each player creates a 2/2 black Zombie creature token with haste named Tombspawn for each creature card in his or her graveyard. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TombstoneStairwellCreateTokenEffect(), TargetController.ANY, false)); + + // At the beginning of each end step or when Tombstone Stairwell leaves the battlefield, destroy all tokens created with Tombstone Stairwell. They can't be regenerated. + this.addAbility(new TombstoneStairwellTriggeredAbility()); + } + + public TombstoneStairwell(final TombstoneStairwell card) { + super(card); + } + + @Override + public TombstoneStairwell copy() { + return new TombstoneStairwell(this); + } +} + +class TombstoneStairwellCreateTokenEffect extends OneShotEffect { + + TombstoneStairwellCreateTokenEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "if {this} is on the battlefield, each player creates a 2/2 black Zombie creature token with haste named Tombspawn for each creature card in his or her graveyard"; + } + + TombstoneStairwellCreateTokenEffect(final TombstoneStairwellCreateTokenEffect effect) { + super(effect); + } + + @Override + public TombstoneStairwellCreateTokenEffect copy() { + return new TombstoneStairwellCreateTokenEffect(this); + } + + @Override + @SuppressWarnings("unchecked") + public boolean apply(Game game, Ability source) { + Token token = new TombspawnZombieToken(); + Player activePlayer = game.getPlayer(game.getActivePlayerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (game.getPlayer(source.getControllerId()) != null && activePlayer != null && permanent != null) { + Object object = game.getState().getValue(CardUtil.getCardZoneString("_tokensCreated", source.getSourceId(), game)); + Set tokensCreated; + if (object != null) { + tokensCreated = (Set) object; + } else { + tokensCreated = new HashSet<>(); + } + + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + int creatureCardsInGraveyard = player.getGraveyard().count(new FilterCreatureCard(), source.getControllerId(), source.getSourceId(), game); + token.putOntoBattlefield(creatureCardsInGraveyard, game, source.getSourceId(), playerId); + for (UUID tokenId : token.getLastAddedTokenIds()) { + tokensCreated.add(tokenId); + } + } + game.getState().setValue(CardUtil.getCardZoneString("_tokensCreated", source.getSourceId(), game), tokensCreated); + return true; + } + return false; + } +} + +class TombstoneStairwellTriggeredAbility extends TriggeredAbilityImpl { + + TombstoneStairwellTriggeredAbility() { + super(Zone.BATTLEFIELD, new TombstoneStairwellDestroyEffect(), false); + } + + TombstoneStairwellTriggeredAbility(TombstoneStairwellTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.END_TURN_STEP_PRE + || event.getType() == EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == EventType.END_TURN_STEP_PRE) { + Permanent permanent = game.getPermanentOrLKIBattlefield(sourceId); + if (permanent != null) { + for (Effect effect : this.getEffects()) { + if (effect instanceof TombstoneStairwellDestroyEffect) { + ((TombstoneStairwellDestroyEffect) effect).setCardZoneString(CardUtil.getCardZoneString("_tokensCreated", this.getSourceId(), game, false)); + } + if (getTargets().isEmpty()) { + effect.setTargetPointer(new FixedTarget(event.getPlayerId())); + } + } + return true; + } + } + else if (event.getType() == EventType.ZONE_CHANGE) { + if (event.getTargetId().equals(this.getSourceId())) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD) { + for (Effect effect : this.getEffects()) { + if (effect instanceof TombstoneStairwellDestroyEffect) { + ((TombstoneStairwellDestroyEffect) effect).setCardZoneString(CardUtil.getCardZoneString("_tokensCreated", this.getSourceId(), game, true)); + } + } + return true; + } + } + } + return false; + } + + @Override + public TombstoneStairwellTriggeredAbility copy() { + return new TombstoneStairwellTriggeredAbility(this); + } + + @Override + public String getRule() { + return "At the beginning of each end step or when {this} leaves the battlefield, " + modes.getText(); + } +} + +class TombstoneStairwellDestroyEffect extends OneShotEffect { + + private String cardZoneString; + + TombstoneStairwellDestroyEffect() { + super(Outcome.Benefit); + this.staticText = "destroy all tokens created with {this}. They can't be regenerated"; + } + + TombstoneStairwellDestroyEffect(final TombstoneStairwellDestroyEffect effect) { + super(effect); + this.cardZoneString = effect.cardZoneString; + } + + @Override + public TombstoneStairwellDestroyEffect copy() { + return new TombstoneStairwellDestroyEffect(this); + } + + @Override + @SuppressWarnings("unchecked") + public boolean apply(Game game, Ability source) { + Object object = game.getState().getValue(cardZoneString); + if (object != null) { + Set tokensCreated = (Set) object; + for (UUID tokenId : tokensCreated) { + Permanent token = game.getPermanent(tokenId); + if (token != null) { + token.destroy(source.getSourceId(), game, true); + } + } + } + return true; + } + + public void setCardZoneString(String cardZoneString) { + this.cardZoneString = cardZoneString; + } +} From ca2a5a830b713fde83f4439c64049a977f8e3638 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 14 Nov 2017 23:52:56 +0100 Subject: [PATCH 45/65] Implemented Tombstone Stairwell --- Mage.Sets/src/mage/sets/Mirage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index e16c401dca..ecfb5546ea 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -304,6 +304,7 @@ public class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Telim'Tor's Edict", 198, Rarity.RARE, mage.cards.t.TelimTorsEdict.class)); cards.add(new SetCardInfo("Thirst", 99, Rarity.COMMON, mage.cards.t.Thirst.class)); cards.add(new SetCardInfo("Tidal Wave", 100, Rarity.UNCOMMON, mage.cards.t.TidalWave.class)); + cards.add(new SetCardInfo("Tombstone Stairwell", 47, Rarity.RARE, mage.cards.t.TombstoneStairwell.class)); cards.add(new SetCardInfo("Tranquil Domain", 143, Rarity.COMMON, mage.cards.t.TranquilDomain.class)); cards.add(new SetCardInfo("Tropical Storm", 144, Rarity.UNCOMMON, mage.cards.t.TropicalStorm.class)); cards.add(new SetCardInfo("Uktabi Faerie", 145, Rarity.COMMON, mage.cards.u.UktabiFaerie.class)); From ad9b95553fe80f8f93e0e8566f9d9f30f5128eed Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 15 Nov 2017 00:05:29 +0100 Subject: [PATCH 46/65] Implemented Tombstone Stairwell --- .../permanent/token/TombspawnZombieToken.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Mage/src/main/java/mage/game/permanent/token/TombspawnZombieToken.java diff --git a/Mage/src/main/java/mage/game/permanent/token/TombspawnZombieToken.java b/Mage/src/main/java/mage/game/permanent/token/TombspawnZombieToken.java new file mode 100644 index 0000000000..f70ebe0947 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/TombspawnZombieToken.java @@ -0,0 +1,50 @@ +/* +* 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.game.permanent.token; + +import mage.constants.CardType; +import mage.constants.SubType; +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; + +/** + * + * @author L_J + */ +public class TombspawnZombieToken extends Token { + + public TombspawnZombieToken() { + super("Tombspawn", "2/2 black Zombie creature token with haste named Tombspawn"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.ZOMBIE); + color.setBlack(true); + power = new MageInt(2); + toughness = new MageInt(2); + this.addAbility(HasteAbility.getInstance()); + } +} From 7750823bbb8b7eed3338a5336c23601798548290 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 15 Nov 2017 16:26:05 +0100 Subject: [PATCH 47/65] Implemented False Orders --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 160 ++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FalseOrders.java diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java new file mode 100644 index 0000000000..d68dda2ddc --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -0,0 +1,160 @@ +/* + * 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.f; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RemoveFromCombatTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.ObjectPlayer; +import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetAttackingCreature; + +/** + * + * @author L_J + */ +public class FalseOrders extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creature defending player controls"); + + static { + filter.add(new CardTypePredicate(CardType.CREATURE)); + filter.add(new FalseOrdersDefendingPlayerControlsPredicate()); + } + + public FalseOrders(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Cast False Orders only during the declare blockers step. + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, PhaseStep.DECLARE_BLOCKERS, null, "Cast {this} only during the declare blockers step")); + + // Remove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice. + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new FalseOrdersUnblockEffect()); + } + + public FalseOrders(final FalseOrders card) { + super(card); + } + + @Override + public FalseOrders copy() { + return new FalseOrders(this); + } + +} + +class FalseOrdersDefendingPlayerControlsPredicate implements ObjectPlayerPredicate> { + + @Override + public boolean apply(ObjectPlayer input, Game game) { + return game.getCombat().getPlayerDefenders(game).contains(input.getObject().getControllerId()); + } +} + +class FalseOrdersUnblockEffect extends OneShotEffect { + + public FalseOrdersUnblockEffect() { + super(Outcome.Benefit); + this.staticText = "Remove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice"; + } + + public FalseOrdersUnblockEffect(final FalseOrdersUnblockEffect effect) { + super(effect); + } + + @Override + public FalseOrdersUnblockEffect copy() { + return new FalseOrdersUnblockEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getTargets().getFirstTarget()); + if (controller != null && permanent != null) { + List combatGroups = new ArrayList<>(); + // Check for creatures blocked by target creature + for (CombatGroup combatGroup : game.getCombat().getGroups()) { + if (combatGroup.getBlockers().contains(permanent.getId())) { + combatGroups.add(combatGroup); + } + } + // Remove target creature from combat + Effect effect = new RemoveFromCombatTargetEffect(); + effect.apply(game, source); + // Make blocked creatures unblocked + for (CombatGroup combatGroup: combatGroups) { + if (combatGroup.getBlockers().isEmpty()) { + combatGroup.setBlocked(false); + } + } + // Choose new creature to block + if (permanent.isCreature()) { + if (controller.chooseUse(Outcome.Benefit, "Do you want " + permanent.getLogName() + " to block an attacking creature?", source, game)) { + TargetAttackingCreature target = new TargetAttackingCreature(1, 1, new FilterAttackingCreature(), true); + if (target.canChoose(source.getSourceId(), controller.getId(), game)) { + while (!target.isChosen() && target.canChoose(controller.getId(), game) && controller.canRespond()) { + controller.chooseTarget(outcome, target, source, game); + } + } else { + return true; + } + Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); + if (chosenPermanent != null && permanent != null && chosenPermanent.isCreature() && controller != null) { + CombatGroup chosenGroup = game.getCombat().findGroup(chosenPermanent.getId()); + if (chosenGroup != null) { + chosenGroup.addBlockerToGroup(permanent.getId(), controller.getId(), game); + } + } + } + return true; + } + } + return false; + } +} From 561504ee9f7aef374128fd6dbec96c41fc8c5afe Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 15 Nov 2017 16:28:01 +0100 Subject: [PATCH 48/65] Implemented False Orders --- Mage.Sets/src/mage/sets/UnlimitedEdition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/UnlimitedEdition.java b/Mage.Sets/src/mage/sets/UnlimitedEdition.java index cfecaacda2..7e25dad5e6 100644 --- a/Mage.Sets/src/mage/sets/UnlimitedEdition.java +++ b/Mage.Sets/src/mage/sets/UnlimitedEdition.java @@ -94,6 +94,7 @@ public class UnlimitedEdition extends ExpansionSet { cards.add(new SetCardInfo("Earthquake", 147, Rarity.RARE, mage.cards.e.Earthquake.class)); cards.add(new SetCardInfo("Elvish Archers", 100, Rarity.RARE, mage.cards.e.ElvishArchers.class)); cards.add(new SetCardInfo("Evil Presence", 16, Rarity.UNCOMMON, mage.cards.e.EvilPresence.class)); + cards.add(new SetCardInfo("False Orders", 148, Rarity.COMMON, mage.cards.f.FalseOrders.class)); cards.add(new SetCardInfo("Farmstead", 204, Rarity.RARE, mage.cards.f.Farmstead.class)); cards.add(new SetCardInfo("Fastbond", 101, Rarity.RARE, mage.cards.f.Fastbond.class)); cards.add(new SetCardInfo("Fear", 17, Rarity.COMMON, mage.cards.f.Fear.class)); From c216bfe13b32f530b3ccf2f11f01f40620a3ca63 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 15 Nov 2017 16:28:36 +0100 Subject: [PATCH 49/65] Implemented False Orders --- Mage.Sets/src/mage/sets/LimitedEditionAlpha.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java index 6cd763f630..5aa69d73d1 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java @@ -93,6 +93,7 @@ public class LimitedEditionAlpha extends ExpansionSet { cards.add(new SetCardInfo("Earthquake", 147, Rarity.RARE, mage.cards.e.Earthquake.class)); cards.add(new SetCardInfo("Elvish Archers", 100, Rarity.RARE, mage.cards.e.ElvishArchers.class)); cards.add(new SetCardInfo("Evil Presence", 16, Rarity.UNCOMMON, mage.cards.e.EvilPresence.class)); + cards.add(new SetCardInfo("False Orders", 148, Rarity.COMMON, mage.cards.f.FalseOrders.class)); cards.add(new SetCardInfo("Farmstead", 203, Rarity.RARE, mage.cards.f.Farmstead.class)); cards.add(new SetCardInfo("Fastbond", 101, Rarity.RARE, mage.cards.f.Fastbond.class)); cards.add(new SetCardInfo("Fear", 17, Rarity.COMMON, mage.cards.f.Fear.class)); From ee88370a1c6f89a7a5d86996c8b9b0a3657daf8f Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 15 Nov 2017 16:29:13 +0100 Subject: [PATCH 50/65] Implemented False Orders --- Mage.Sets/src/mage/sets/LimitedEditionBeta.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java index 27abd09157..54499c973c 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java @@ -94,6 +94,7 @@ public class LimitedEditionBeta extends ExpansionSet { cards.add(new SetCardInfo("Earthquake", 148, Rarity.RARE, mage.cards.e.Earthquake.class)); cards.add(new SetCardInfo("Elvish Archers", 100, Rarity.RARE, mage.cards.e.ElvishArchers.class)); cards.add(new SetCardInfo("Evil Presence", 16, Rarity.UNCOMMON, mage.cards.e.EvilPresence.class)); + cards.add(new SetCardInfo("False Orders", 149, Rarity.COMMON, mage.cards.f.FalseOrders.class)); cards.add(new SetCardInfo("Farmstead", 205, Rarity.RARE, mage.cards.f.Farmstead.class)); cards.add(new SetCardInfo("Fastbond", 101, Rarity.RARE, mage.cards.f.Fastbond.class)); cards.add(new SetCardInfo("Fear", 17, Rarity.COMMON, mage.cards.f.Fear.class)); From 1527b09dc02424eee625a225b01625594890d3e4 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 19:05:33 +0100 Subject: [PATCH 51/65] Major overhaul of False Orders --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 97 ++++++++++++++++++--- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index d68dda2ddc..00aa61cf8f 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -27,9 +27,12 @@ */ package mage.cards.f; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.UUID; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.effects.Effect; @@ -40,6 +43,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.PhaseStep; +import mage.constants.WatcherScope; import mage.filter.FilterPermanent; import mage.filter.common.FilterAttackingCreature; import mage.filter.predicate.ObjectPlayer; @@ -48,10 +52,12 @@ import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Controllable; import mage.game.Game; import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetAttackingCreature; +import mage.watchers.Watcher; /** * @@ -75,6 +81,7 @@ public class FalseOrders extends CardImpl { // Remove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice. this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new FalseOrdersUnblockEffect()); + this.getSpellAbility().addWatcher(new BecameBlockedByOnlyOneCreatureWatcher()); } public FalseOrders(final FalseOrders card) { @@ -117,22 +124,24 @@ class FalseOrdersUnblockEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Permanent permanent = game.getPermanent(source.getTargets().getFirstTarget()); if (controller != null && permanent != null) { - List combatGroups = new ArrayList<>(); - // Check for creatures blocked by target creature - for (CombatGroup combatGroup : game.getCombat().getGroups()) { - if (combatGroup.getBlockers().contains(permanent.getId())) { - combatGroups.add(combatGroup); - } - } + // Remove target creature from combat Effect effect = new RemoveFromCombatTargetEffect(); effect.apply(game, source); + // Make blocked creatures unblocked - for (CombatGroup combatGroup: combatGroups) { - if (combatGroup.getBlockers().isEmpty()) { - combatGroup.setBlocked(false); + BecameBlockedByOnlyOneCreatureWatcher watcher = (BecameBlockedByOnlyOneCreatureWatcher) game.getState().getWatchers().get(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName()); + if (watcher != null) { + Set combatGroups = watcher.getBlockedOnlyByCreature(new MageObjectReference(permanent.getId(), game)); + if (combatGroups != null) { + for (CombatGroup combatGroup : combatGroups) { + if (combatGroup != null) { + combatGroup.setBlocked(false); + } + } } } + // Choose new creature to block if (permanent.isCreature()) { if (controller.chooseUse(Outcome.Benefit, "Do you want " + permanent.getLogName() + " to block an attacking creature?", source, game)) { @@ -148,7 +157,19 @@ class FalseOrdersUnblockEffect extends OneShotEffect { if (chosenPermanent != null && permanent != null && chosenPermanent.isCreature() && controller != null) { CombatGroup chosenGroup = game.getCombat().findGroup(chosenPermanent.getId()); if (chosenGroup != null) { - chosenGroup.addBlockerToGroup(permanent.getId(), controller.getId(), game); + // Relevant ruling for Balduvian Warlord: + // 7/15/2006 If an attacking creature has an ability that triggers “When this creature becomes blocked,” + // it triggers when a creature blocks it due to the Warlord’s ability only if it was unblocked at that point. + + boolean notYetBlocked = true; + if (!chosenGroup.getBlockers().isEmpty()) { + notYetBlocked = false; + } + chosenGroup.addBlocker(permanent.getId(), controller.getId(), game); + if (notYetBlocked) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, chosenPermanent.getId(), null)); + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); } } } @@ -158,3 +179,53 @@ class FalseOrdersUnblockEffect extends OneShotEffect { return false; } } + +class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { + + private final Map blockedByOneCreature = new HashMap<>(); + + public BecameBlockedByOnlyOneCreatureWatcher() { + super(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public BecameBlockedByOnlyOneCreatureWatcher(final BecameBlockedByOnlyOneCreatureWatcher watcher) { + super(watcher); + this.blockedByOneCreature.putAll(watcher.blockedByOneCreature); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) { + this.blockedByOneCreature.clear(); + } + else if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { + CombatGroup combatGroup = game.getCombat().findGroup(event.getTargetId()); + if (combatGroup != null) { + if (combatGroup.getBlockers().size() == 1) { + blockedByOneCreature.put(combatGroup, new MageObjectReference(combatGroup.getBlockers().get(0), game)); + } + else if (combatGroup.getBlockers().size() > 1) { + blockedByOneCreature.remove(combatGroup); + } + } + } + } + + public Set getBlockedOnlyByCreature(MageObjectReference creature) { + Set combatGroups = new HashSet<>(); + for (Map.Entry entry : blockedByOneCreature.entrySet()) { + if (entry.getValue().equals(creature)) { + combatGroups.add(entry.getKey()); + } + } + if (combatGroups.size() > 0) { + return combatGroups; + } + return null; + } + + @Override + public BecameBlockedByOnlyOneCreatureWatcher copy() { + return new BecameBlockedByOnlyOneCreatureWatcher(this); + } +} From 187b558b7b1d2a2068af7cbff7e0a5395a14103d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 19:21:05 +0100 Subject: [PATCH 52/65] Small fix --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index 00aa61cf8f..1730e29dc7 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -202,7 +202,9 @@ class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { CombatGroup combatGroup = game.getCombat().findGroup(event.getTargetId()); if (combatGroup != null) { if (combatGroup.getBlockers().size() == 1) { - blockedByOneCreature.put(combatGroup, new MageObjectReference(combatGroup.getBlockers().get(0), game)); + if (!blockedByOneCreature.containsKey(combatGroup)) { + blockedByOneCreature.put(combatGroup, new MageObjectReference(combatGroup.getBlockers().get(0), game)); + } } else if (combatGroup.getBlockers().size() > 1) { blockedByOneCreature.remove(combatGroup); From 8154f045502ed378d60747635b6b0b8971f90f8b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 20:21:45 +0100 Subject: [PATCH 53/65] Some more fixes --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index 1730e29dc7..2e1dfd51cc 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -207,7 +207,7 @@ class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { } } else if (combatGroup.getBlockers().size() > 1) { - blockedByOneCreature.remove(combatGroup); + blockedByOneCreature.put(combatGroup, null); } } } @@ -216,9 +216,11 @@ class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { public Set getBlockedOnlyByCreature(MageObjectReference creature) { Set combatGroups = new HashSet<>(); for (Map.Entry entry : blockedByOneCreature.entrySet()) { - if (entry.getValue().equals(creature)) { - combatGroups.add(entry.getKey()); - } + if (entry.getValue() != null) { + if (entry.getValue().equals(creature)) { + combatGroups.add(entry.getKey()); + } + } } if (combatGroups.size() > 0) { return combatGroups; From 3231fef13102c11697e8664df3169fd60a266e33 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 23:03:11 +0100 Subject: [PATCH 54/65] Final version (hopefully) ...until Banding becomes a thing, at least. Using MageObjectReference instead of UUID for the watcher was buggy in certain fringe scenarios (casting False Orders twice on one blocker - remove blocker then block again, then remove blocker and don't block - would result in a blocked attacker without a blocker) --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index 2e1dfd51cc..1e8c97e640 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -32,7 +32,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.effects.Effect; @@ -132,7 +131,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect { // Make blocked creatures unblocked BecameBlockedByOnlyOneCreatureWatcher watcher = (BecameBlockedByOnlyOneCreatureWatcher) game.getState().getWatchers().get(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName()); if (watcher != null) { - Set combatGroups = watcher.getBlockedOnlyByCreature(new MageObjectReference(permanent.getId(), game)); + Set combatGroups = watcher.getBlockedOnlyByCreature(permanent.getId()); if (combatGroups != null) { for (CombatGroup combatGroup : combatGroups) { if (combatGroup != null) { @@ -182,7 +181,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect { class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { - private final Map blockedByOneCreature = new HashMap<>(); + private final Map blockedByOneCreature = new HashMap<>(); public BecameBlockedByOnlyOneCreatureWatcher() { super(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName(), WatcherScope.GAME); @@ -203,7 +202,10 @@ class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { if (combatGroup != null) { if (combatGroup.getBlockers().size() == 1) { if (!blockedByOneCreature.containsKey(combatGroup)) { - blockedByOneCreature.put(combatGroup, new MageObjectReference(combatGroup.getBlockers().get(0), game)); + blockedByOneCreature.put(combatGroup, event.getSourceId()); + } + else if (blockedByOneCreature.get(combatGroup) != event.getSourceId()) { + blockedByOneCreature.put(combatGroup, null); } } else if (combatGroup.getBlockers().size() > 1) { @@ -213,9 +215,9 @@ class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { } } - public Set getBlockedOnlyByCreature(MageObjectReference creature) { + public Set getBlockedOnlyByCreature(UUID creature) { Set combatGroups = new HashSet<>(); - for (Map.Entry entry : blockedByOneCreature.entrySet()) { + for (Map.Entry entry : blockedByOneCreature.entrySet()) { if (entry.getValue() != null) { if (entry.getValue().equals(creature)) { combatGroups.add(entry.getKey()); From 5cc042779a34799bf56e2583c012c25958770007 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 23:45:40 +0100 Subject: [PATCH 55/65] Implemented False Orders and Balduvian Warlord --- ...kedByOnlyOneCreatureThisCombatWatcher.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Mage/src/main/java/mage/watchers/common/BlockedByOnlyOneCreatureThisCombatWatcher.java diff --git a/Mage/src/main/java/mage/watchers/common/BlockedByOnlyOneCreatureThisCombatWatcher.java b/Mage/src/main/java/mage/watchers/common/BlockedByOnlyOneCreatureThisCombatWatcher.java new file mode 100644 index 0000000000..3e91f1f0b0 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/BlockedByOnlyOneCreatureThisCombatWatcher.java @@ -0,0 +1,100 @@ +/* + * 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.watchers.common; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +/** + * + * @author L_J + */ +public class BlockedByOnlyOneCreatureThisCombatWatcher extends Watcher { + + private final Map blockedByOneCreature = new HashMap<>(); + + public BlockedByOnlyOneCreatureThisCombatWatcher() { + super(BlockedByOnlyOneCreatureThisCombatWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public BlockedByOnlyOneCreatureThisCombatWatcher(final BlockedByOnlyOneCreatureThisCombatWatcher watcher) { + super(watcher); + this.blockedByOneCreature.putAll(watcher.blockedByOneCreature); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) { + this.blockedByOneCreature.clear(); + } + else if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { + CombatGroup combatGroup = game.getCombat().findGroup(event.getTargetId()); + if (combatGroup != null) { + if (combatGroup.getBlockers().size() == 1) { + if (!blockedByOneCreature.containsKey(combatGroup)) { + blockedByOneCreature.put(combatGroup, event.getSourceId()); + } + else if (blockedByOneCreature.get(combatGroup) != event.getSourceId()) { + blockedByOneCreature.put(combatGroup, null); + } + } + else if (combatGroup.getBlockers().size() > 1) { + blockedByOneCreature.put(combatGroup, null); + } + } + } + } + + public Set getBlockedOnlyByCreature(UUID creature) { + Set combatGroups = new HashSet<>(); + for (Map.Entry entry : blockedByOneCreature.entrySet()) { + if (entry.getValue() != null) { + if (entry.getValue().equals(creature)) { + combatGroups.add(entry.getKey()); + } + } + } + if (combatGroups.size() > 0) { + return combatGroups; + } + return null; + } + + @Override + public BlockedByOnlyOneCreatureThisCombatWatcher copy() { + return new BlockedByOnlyOneCreatureThisCombatWatcher(this); + } +} From f2087498855f4830e708b6b9e4dd2b4b6aa28cb1 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 23:47:03 +0100 Subject: [PATCH 56/65] Split off watcher into mage.watchers.common --- Mage.Sets/src/mage/cards/f/FalseOrders.java | 67 +-------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index 1e8c97e640..91abb17e93 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -27,9 +27,6 @@ */ package mage.cards.f; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.UUID; import mage.abilities.Ability; @@ -42,7 +39,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.PhaseStep; -import mage.constants.WatcherScope; import mage.filter.FilterPermanent; import mage.filter.common.FilterAttackingCreature; import mage.filter.predicate.ObjectPlayer; @@ -56,7 +52,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetAttackingCreature; -import mage.watchers.Watcher; +import mage.watchers.common.BlockedByOnlyOneCreatureThisCombatWatcher; /** * @@ -80,7 +76,7 @@ public class FalseOrders extends CardImpl { // Remove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice. this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new FalseOrdersUnblockEffect()); - this.getSpellAbility().addWatcher(new BecameBlockedByOnlyOneCreatureWatcher()); + this.getSpellAbility().addWatcher(new BlockedByOnlyOneCreatureThisCombatWatcher()); } public FalseOrders(final FalseOrders card) { @@ -129,7 +125,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect { effect.apply(game, source); // Make blocked creatures unblocked - BecameBlockedByOnlyOneCreatureWatcher watcher = (BecameBlockedByOnlyOneCreatureWatcher) game.getState().getWatchers().get(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName()); + BlockedByOnlyOneCreatureThisCombatWatcher watcher = (BlockedByOnlyOneCreatureThisCombatWatcher) game.getState().getWatchers().get(BlockedByOnlyOneCreatureThisCombatWatcher.class.getSimpleName()); if (watcher != null) { Set combatGroups = watcher.getBlockedOnlyByCreature(permanent.getId()); if (combatGroups != null) { @@ -178,60 +174,3 @@ class FalseOrdersUnblockEffect extends OneShotEffect { return false; } } - -class BecameBlockedByOnlyOneCreatureWatcher extends Watcher { - - private final Map blockedByOneCreature = new HashMap<>(); - - public BecameBlockedByOnlyOneCreatureWatcher() { - super(BecameBlockedByOnlyOneCreatureWatcher.class.getSimpleName(), WatcherScope.GAME); - } - - public BecameBlockedByOnlyOneCreatureWatcher(final BecameBlockedByOnlyOneCreatureWatcher watcher) { - super(watcher); - this.blockedByOneCreature.putAll(watcher.blockedByOneCreature); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) { - this.blockedByOneCreature.clear(); - } - else if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { - CombatGroup combatGroup = game.getCombat().findGroup(event.getTargetId()); - if (combatGroup != null) { - if (combatGroup.getBlockers().size() == 1) { - if (!blockedByOneCreature.containsKey(combatGroup)) { - blockedByOneCreature.put(combatGroup, event.getSourceId()); - } - else if (blockedByOneCreature.get(combatGroup) != event.getSourceId()) { - blockedByOneCreature.put(combatGroup, null); - } - } - else if (combatGroup.getBlockers().size() > 1) { - blockedByOneCreature.put(combatGroup, null); - } - } - } - } - - public Set getBlockedOnlyByCreature(UUID creature) { - Set combatGroups = new HashSet<>(); - for (Map.Entry entry : blockedByOneCreature.entrySet()) { - if (entry.getValue() != null) { - if (entry.getValue().equals(creature)) { - combatGroups.add(entry.getKey()); - } - } - } - if (combatGroups.size() > 0) { - return combatGroups; - } - return null; - } - - @Override - public BecameBlockedByOnlyOneCreatureWatcher copy() { - return new BecameBlockedByOnlyOneCreatureWatcher(this); - } -} From b33cead4ef0c6d9e7f285c72b4aec3af67ca7d14 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 23:48:42 +0100 Subject: [PATCH 57/65] Implemented Balduvian Warlord --- .../src/mage/cards/b/BalduvianWarlord.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BalduvianWarlord.java diff --git a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java new file mode 100644 index 0000000000..1a3766c139 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java @@ -0,0 +1,161 @@ +/* + * 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.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.IsStepCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RemoveFromCombatTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.common.FilterBlockingCreature; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetAttackingCreature; +import mage.watchers.common.BlockedByOnlyOneCreatureThisCombatWatcher; + +/** + * + * @author L_J + */ +public class BalduvianWarlord extends CardImpl { + + public BalduvianWarlord(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BARBARIAN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // {T}: Remove target blocking creature from combat. Creatures it was blocking that hadn't become blocked by another creature this combat become unblocked, then it blocks an attacking creature of your choice. Activate this ability only during the declare blockers step. + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new BalduvianWarlordUnblockEffect(), new TapSourceCost(), new IsStepCondition(PhaseStep.DECLARE_BLOCKERS, false)); + ability.addTarget(new TargetPermanent(new FilterBlockingCreature())); + this.addAbility(ability, new BlockedByOnlyOneCreatureThisCombatWatcher()); + } + + public BalduvianWarlord(final BalduvianWarlord card) { + super(card); + } + + @Override + public BalduvianWarlord copy() { + return new BalduvianWarlord(this); + } + +} + +class BalduvianWarlordUnblockEffect extends OneShotEffect { + + public BalduvianWarlordUnblockEffect() { + super(Outcome.Benefit); + this.staticText = " Remove target blocking creature from combat. Creatures it was blocking that hadn't become blocked by another creature this combat become unblocked, then it blocks an attacking creature of your choice"; + } + + public BalduvianWarlordUnblockEffect(final BalduvianWarlordUnblockEffect effect) { + super(effect); + } + + @Override + public BalduvianWarlordUnblockEffect copy() { + return new BalduvianWarlordUnblockEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getTargets().getFirstTarget()); + if (controller != null && permanent != null) { + + // Remove target creature from combat + Effect effect = new RemoveFromCombatTargetEffect(); + effect.apply(game, source); + + // Make blocked creatures unblocked + BlockedByOnlyOneCreatureThisCombatWatcher watcher = (BlockedByOnlyOneCreatureThisCombatWatcher) game.getState().getWatchers().get(BlockedByOnlyOneCreatureThisCombatWatcher.class.getSimpleName()); + if (watcher != null) { + Set combatGroups = watcher.getBlockedOnlyByCreature(permanent.getId()); + if (combatGroups != null) { + for (CombatGroup combatGroup : combatGroups) { + if (combatGroup != null) { + combatGroup.setBlocked(false); + } + } + } + } + + // Choose new creature to block + if (permanent.isCreature()) { + TargetAttackingCreature target = new TargetAttackingCreature(1, 1, new FilterAttackingCreature(), true); + if (target.canChoose(source.getSourceId(), controller.getId(), game)) { + while (!target.isChosen() && target.canChoose(controller.getId(), game) && controller.canRespond()) { + controller.chooseTarget(outcome, target, source, game); + } + } else { + return true; + } + Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); + if (chosenPermanent != null && permanent != null && chosenPermanent.isCreature() && controller != null) { + CombatGroup chosenGroup = game.getCombat().findGroup(chosenPermanent.getId()); + if (chosenGroup != null) { + // Relevant ruling for Balduvian Warlord: + // 7/15/2006 If an attacking creature has an ability that triggers “When this creature becomes blocked,” + // it triggers when a creature blocks it due to the Warlord’s ability only if it was unblocked at that point. + + boolean notYetBlocked = true; + if (!chosenGroup.getBlockers().isEmpty()) { + notYetBlocked = false; + } + chosenGroup.addBlocker(permanent.getId(), controller.getId(), game); + if (notYetBlocked) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, chosenPermanent.getId(), null)); + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); + } + } + return true; + } + } + return false; + } +} From d6f63b3393914435f3ddfd7bf2eada7ee335671c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 17 Nov 2017 23:49:38 +0100 Subject: [PATCH 58/65] Implemented Balduvian Warlord --- Mage.Sets/src/mage/sets/Coldsnap.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Coldsnap.java b/Mage.Sets/src/mage/sets/Coldsnap.java index 2274bb439f..5d924af0ca 100644 --- a/Mage.Sets/src/mage/sets/Coldsnap.java +++ b/Mage.Sets/src/mage/sets/Coldsnap.java @@ -62,6 +62,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Arcum Dagsson", 27, Rarity.RARE, mage.cards.a.ArcumDagsson.class)); cards.add(new SetCardInfo("Aurochs Herd", 103, Rarity.COMMON, mage.cards.a.AurochsHerd.class)); cards.add(new SetCardInfo("Balduvian Rage", 76, Rarity.UNCOMMON, mage.cards.b.BalduvianRage.class)); + cards.add(new SetCardInfo("Balduvian Warlord", 77, Rarity.UNCOMMON, mage.cards.b.BalduvianWarlord.class)); cards.add(new SetCardInfo("Blizzard Specter", 126, Rarity.UNCOMMON, mage.cards.b.BlizzardSpecter.class)); cards.add(new SetCardInfo("Boreal Centaur", 104, Rarity.COMMON, mage.cards.b.BorealCentaur.class)); cards.add(new SetCardInfo("Boreal Druid", 105, Rarity.COMMON, mage.cards.b.BorealDruid.class)); @@ -84,7 +85,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Diamond Faerie", 128, Rarity.RARE, mage.cards.d.DiamondFaerie.class)); cards.add(new SetCardInfo("Disciple of Tevesh Szat", 55, Rarity.COMMON, mage.cards.d.DiscipleOfTeveshSzat.class)); cards.add(new SetCardInfo("Drelnoch", 32, Rarity.COMMON, mage.cards.d.Drelnoch.class)); - cards.add(new SetCardInfo("Earthen Goo", 80, Rarity.UNCOMMON, mage.cards.e.EarthenGoo.class)); + cards.add(new SetCardInfo("Earthen Goo", 80, Rarity.UNCOMMON, mage.cards.e.EarthenGoo.class)); cards.add(new SetCardInfo("Field Marshal", 5, Rarity.RARE, mage.cards.f.FieldMarshal.class)); cards.add(new SetCardInfo("Flashfreeze", 33, Rarity.UNCOMMON, mage.cards.f.Flashfreeze.class)); cards.add(new SetCardInfo("Freyalise's Radiance", 108, Rarity.UNCOMMON, mage.cards.f.FreyalisesRadiance.class)); @@ -116,7 +117,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Karplusan Strider", 112, Rarity.UNCOMMON, mage.cards.k.KarplusanStrider.class)); cards.add(new SetCardInfo("Karplusan Wolverine", 87, Rarity.COMMON, mage.cards.k.KarplusanWolverine.class)); cards.add(new SetCardInfo("Kjeldoran Gargoyle", 10, Rarity.UNCOMMON, mage.cards.k.KjeldoranGargoyle.class)); - cards.add(new SetCardInfo("Kjeldoran Javelineer", 11, Rarity.COMMON, mage.cards.k.KjeldoranJavelineer.class)); + cards.add(new SetCardInfo("Kjeldoran Javelineer", 11, Rarity.COMMON, mage.cards.k.KjeldoranJavelineer.class)); cards.add(new SetCardInfo("Kjeldoran Outrider", 12, Rarity.COMMON, mage.cards.k.KjeldoranOutrider.class)); cards.add(new SetCardInfo("Krovikan Mist", 38, Rarity.COMMON, mage.cards.k.KrovikanMist.class)); cards.add(new SetCardInfo("Krovikan Rot", 63, Rarity.UNCOMMON, mage.cards.k.KrovikanRot.class)); @@ -126,7 +127,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Lightning Storm", 89, Rarity.UNCOMMON, mage.cards.l.LightningStorm.class)); cards.add(new SetCardInfo("Lovisa Coldeyes", 90, Rarity.RARE, mage.cards.l.LovisaColdeyes.class)); cards.add(new SetCardInfo("Luminesce", 14, Rarity.UNCOMMON, mage.cards.l.Luminesce.class)); - cards.add(new SetCardInfo("Magmatic Core", 91, Rarity.UNCOMMON, mage.cards.m.MagmaticCore.class)); + cards.add(new SetCardInfo("Magmatic Core", 91, Rarity.UNCOMMON, mage.cards.m.MagmaticCore.class)); cards.add(new SetCardInfo("Martyr of Ashes", 92, Rarity.COMMON, mage.cards.m.MartyrOfAshes.class)); cards.add(new SetCardInfo("Martyr of Bones", 65, Rarity.COMMON, mage.cards.m.MartyrOfBones.class)); cards.add(new SetCardInfo("Martyr of Frost", 40, Rarity.COMMON, mage.cards.m.MartyrOfFrost.class)); @@ -153,7 +154,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Rimewind Taskmage", 44, Rarity.COMMON, mage.cards.r.RimewindTaskmage.class)); cards.add(new SetCardInfo("Rite of Flame", 96, Rarity.COMMON, mage.cards.r.RiteOfFlame.class)); cards.add(new SetCardInfo("Ronom Hulk", 119, Rarity.COMMON, mage.cards.r.RonomHulk.class)); - cards.add(new SetCardInfo("Ronom Serpent", 45, Rarity.COMMON, mage.cards.r.RonomSerpent.class)); + cards.add(new SetCardInfo("Ronom Serpent", 45, Rarity.COMMON, mage.cards.r.RonomSerpent.class)); cards.add(new SetCardInfo("Ronom Unicorn", 16, Rarity.COMMON, mage.cards.r.RonomUnicorn.class)); cards.add(new SetCardInfo("Rune Snag", 46, Rarity.COMMON, mage.cards.r.RuneSnag.class)); cards.add(new SetCardInfo("Scrying Sheets", 149, Rarity.RARE, mage.cards.s.ScryingSheets.class)); From a7bcad4868cfc2d3a0b5d7ac79800c7c4beae30d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 18 Nov 2017 00:11:56 +0100 Subject: [PATCH 59/65] Formatting edit --- .../src/mage/cards/b/BalduvianWarlord.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java index 1a3766c139..bd0ee58372 100644 --- a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java +++ b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java @@ -126,33 +126,33 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect { // Choose new creature to block if (permanent.isCreature()) { - TargetAttackingCreature target = new TargetAttackingCreature(1, 1, new FilterAttackingCreature(), true); - if (target.canChoose(source.getSourceId(), controller.getId(), game)) { - while (!target.isChosen() && target.canChoose(controller.getId(), game) && controller.canRespond()) { - controller.chooseTarget(outcome, target, source, game); - } - } else { - return true; + TargetAttackingCreature target = new TargetAttackingCreature(1, 1, new FilterAttackingCreature(), true); + if (target.canChoose(source.getSourceId(), controller.getId(), game)) { + while (!target.isChosen() && target.canChoose(controller.getId(), game) && controller.canRespond()) { + controller.chooseTarget(outcome, target, source, game); } - Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); - if (chosenPermanent != null && permanent != null && chosenPermanent.isCreature() && controller != null) { - CombatGroup chosenGroup = game.getCombat().findGroup(chosenPermanent.getId()); - if (chosenGroup != null) { - // Relevant ruling for Balduvian Warlord: - // 7/15/2006 If an attacking creature has an ability that triggers “When this creature becomes blocked,” - // it triggers when a creature blocks it due to the Warlord’s ability only if it was unblocked at that point. - - boolean notYetBlocked = true; - if (!chosenGroup.getBlockers().isEmpty()) { - notYetBlocked = false; - } - chosenGroup.addBlocker(permanent.getId(), controller.getId(), game); - if (notYetBlocked) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, chosenPermanent.getId(), null)); - } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); + } else { + return true; + } + Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); + if (chosenPermanent != null && permanent != null && chosenPermanent.isCreature() && controller != null) { + CombatGroup chosenGroup = game.getCombat().findGroup(chosenPermanent.getId()); + if (chosenGroup != null) { + // Relevant ruling for Balduvian Warlord: + // 7/15/2006 If an attacking creature has an ability that triggers “When this creature becomes blocked,” + // it triggers when a creature blocks it due to the Warlord’s ability only if it was unblocked at that point. + + boolean notYetBlocked = true; + if (!chosenGroup.getBlockers().isEmpty()) { + notYetBlocked = false; } + chosenGroup.addBlocker(permanent.getId(), controller.getId(), game); + if (notYetBlocked) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, chosenPermanent.getId(), null)); + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, chosenPermanent.getId(), permanent.getId(), permanent.getControllerId())); } + } return true; } } From f81489c37778afb985a1a954a83ccac284865f6a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Nov 2017 05:13:38 +0400 Subject: [PATCH 60/65] Add fallback card render (origin card render), see #4168 --- .gitignore | 2 +- .../src/main/java/mage/client/cards/Card.java | 2 +- .../main/java/mage/client/cards/CardArea.java | 17 +- .../java/mage/client/cards/DragCardGrid.java | 2 +- .../collection/viewer/MageBook.java | 3 + .../java/org/mage/card/arcane/CardPanel.java | 10 +- .../card/arcane/CardPanelComponentImpl.java | 181 ++++++++++++++---- .../mage/card/arcane/CardPanelRenderImpl.java | 2 +- .../mage/plugins/card/images/ImageCache.java | 77 +++++++- .../src/main/java/mage/cards/MageCard.java | 4 +- 10 files changed, 235 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index d084fe143d..61d6b76f85 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ syntax: glob Mage.Client/*.dck Mage.Client/db Mage.Client/gamelogs -Mage.Client/mageclient.log +Mage.Client/*.log Mage.Client/plugins/images Mage.Client/plugins/plugin.data Mage.Client/target diff --git a/Mage.Client/src/main/java/mage/client/cards/Card.java b/Mage.Client/src/main/java/mage/client/cards/Card.java index e984b30592..cb90ad56f4 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Card.java +++ b/Mage.Client/src/main/java/mage/client/cards/Card.java @@ -562,7 +562,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis } @Override - public void setTextOffset(int yOffset) { + public void setCardCaptionTopOffset(int yOffsetPercent) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } diff --git a/Mage.Client/src/main/java/mage/client/cards/CardArea.java b/Mage.Client/src/main/java/mage/client/cards/CardArea.java index 5fa3840253..cb63d2e6ea 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardArea.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardArea.java @@ -53,7 +53,7 @@ public class CardArea extends JPanel implements MouseListener { private boolean reloaded = false; private final javax.swing.JLayeredPane cardArea; private final javax.swing.JScrollPane scrollPane; - private int yTextOffset; + private int yCardCaptionOffsetPercent = 0; // card caption offset (use for moving card caption view center, below mana icons -- for more good UI) private Dimension cardDimension; private int verticalCardOffset; @@ -68,8 +68,6 @@ public class CardArea extends JPanel implements MouseListener { setGUISize(); cardArea = new JLayeredPane(); scrollPane.setViewportView(cardArea); - yTextOffset = 10; - } public void cleanUp() { @@ -103,10 +101,10 @@ public class CardArea extends JPanel implements MouseListener { this.reloaded = true; cardArea.removeAll(); if (showCards != null && showCards.size() < 10) { - yTextOffset = 10; + yCardCaptionOffsetPercent = 8; // TODO: need to test loadCardsFew(showCards, bigCard, gameId); } else { - yTextOffset = 0; + yCardCaptionOffsetPercent = 0; loadCardsMany(showCards, bigCard, gameId); } cardArea.revalidate(); @@ -118,8 +116,10 @@ public class CardArea extends JPanel implements MouseListener { public void loadCardsNarrow(CardsView showCards, BigCard bigCard, UUID gameId) { this.reloaded = true; cardArea.removeAll(); - yTextOffset = 0; + + yCardCaptionOffsetPercent = 0; // TODO: need to test loadCardsMany(showCards, bigCard, gameId); + cardArea.revalidate(); this.revalidate(); @@ -152,7 +152,10 @@ public class CardArea extends JPanel implements MouseListener { cardArea.moveToFront(cardPanel); cardPanel.update(card); cardPanel.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); - cardPanel.setTextOffset(yTextOffset); + + // new card have same settings as current view + cardPanel.setCardCaptionTopOffset(yCardCaptionOffsetPercent); + cardPanel.showCardTitle(); } diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index c93f0fecb9..f96fc2f863 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1726,7 +1726,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Create the card view final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true); cardPanel.update(card); - cardPanel.setTextOffset(0); + cardPanel.setCardCaptionTopOffset(0); // Remove mouse wheel listeners so that scrolling works // Scrolling works on all areas without cards or by using the scroll bar, that's enough diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java index 3e27b580ae..2488110b58 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java @@ -236,6 +236,7 @@ public class MageBook extends JComponent { for (int i = 0; i < Math.min(conf.CARDS_PER_PAGE / 2, size); i++) { Card card = cards.get(i).getMockCard(); addCard(new CardView(card), bigCard, null, rectangle); + rectangle = CardPosition.translatePosition(i, rectangle, conf); } @@ -341,6 +342,8 @@ public class MageBook extends JComponent { cardImg.update(card); cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimensions.frameWidth, cardDimensions.frameHeight); + cardImg.setCardCaptionTopOffset(8); // card caption below real card caption to see full name even with mana icons + boolean implemented = card.getRarity() != Rarity.NA; GlowText label = new GlowText(); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 4bf8218156..b05a03ea26 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -91,7 +91,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, private JPanel cardArea; - private int yTextOffset = 10; + private int yCardCaptionOffsetPercent = 0; // card caption offset (use for moving card caption view center, below mana icons -- for more good UI) // if this is set, it's opened if the user right clicks on the card panel private JPopupMenu popupMenu; @@ -819,12 +819,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, } @Override - public void setTextOffset(int yOffset) { - yTextOffset = yOffset; + public void setCardCaptionTopOffset(int yOffsetPercent) { + yCardCaptionOffsetPercent = yOffsetPercent; } - public int getTextOffset() { - return yTextOffset; + public int getCardCaptionTopOffset() { + return yCardCaptionOffsetPercent; } @Override diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index c57a41f043..abe3770ffb 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -34,7 +34,7 @@ import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; * Class for drawing the mage card object by using a form based JComponent * approach * - * @author arcane, nantuko, noxx, stravant + * @author arcane, nantuko, noxx, stravant, JayDi85 */ @SuppressWarnings({"unchecked", "rawtypes"}) public class CardPanelComponentImpl extends CardPanel { @@ -47,9 +47,14 @@ public class CardPanelComponentImpl extends CardPanel { private static final float ROUNDED_CORNER_SIZE = 0.1f; private static final float BLACK_BORDER_SIZE = 0.03f; + private static final float SELECTION_BORDER_SIZE = 0.03f; private static final int TEXT_GLOW_SIZE = 6; private static final float TEXT_GLOW_INTENSITY = 3f; + // size to show icons and text (help to see full size card without text) + private static final int CARD_MIN_SIZE_FOR_ICONS = 60; + private static final int CARD_MAX_SIZE_FOR_ICONS = 200; + public final ScaledImagePanel imagePanel; public ImagePanel overlayPanel; @@ -177,6 +182,31 @@ public class CardPanelComponentImpl extends CardPanel { IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) key -> createImage(key))); } + static private boolean canShowCardIcons(int fullWidth){ + return (fullWidth > 60) && (fullWidth < 200); + } + + private static class CardSizes{ + Rectangle rectFull; + Rectangle rectSelection; + Rectangle rectBorder; + Rectangle rectCard; + + CardSizes(int offsetX, int offsetY, int fullWidth, int fullHeight){ + + int realBorderSizeX = Math.round(fullWidth * BLACK_BORDER_SIZE); + int realBorderSizeY = Math.round(fullWidth * BLACK_BORDER_SIZE); + int realSelectionSizeX = Math.round(fullWidth * SELECTION_BORDER_SIZE); + int realSelectionSizeY = Math.round(fullWidth * SELECTION_BORDER_SIZE); + + // card full size = select border + black border + real card + rectFull = new Rectangle(offsetX, offsetY, fullWidth, fullHeight); + rectSelection = new Rectangle(rectFull.x, rectFull.y, rectFull.width, rectFull.height); + rectBorder = new Rectangle(rectSelection.x + realSelectionSizeX, rectSelection.y + realSelectionSizeY, rectSelection.width - 2 * realSelectionSizeX, rectSelection.height - 2 * realSelectionSizeY); + rectCard = new Rectangle(rectBorder.x + realBorderSizeX, rectBorder.y + realBorderSizeY, rectBorder.width - 2 * realBorderSizeX, rectBorder.height - 2 * realBorderSizeY); + } + } + public CardPanelComponentImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { // Call to super super(newGameCard, gameId, loadImage, callback, foil, dimension); @@ -368,32 +398,45 @@ public class CardPanelComponentImpl extends CardPanel { Graphics2D g2d = image.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - if (!key.hasImage) { - g2d.setColor(new Color(30, 200, 200, 120)); - } else { - g2d.setColor(new Color(0, 0, 0, 0)); - } + // card full size = select border + black border + real card + CardSizes sizes = new CardSizes(cardXOffset, cardYOffset, cardWidth, cardHeight); - int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE)); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + // corners for selection and for border + int cornerSizeSelection = Math.max(4, Math.round(sizes.rectSelection.width * ROUNDED_CORNER_SIZE)); + int cornerSizeBorder = Math.max(4, Math.round(sizes.rectBorder.width * ROUNDED_CORNER_SIZE)); + // DRAW ORDER from big to small: select -> select info -> border -> card + + // draw selection if (key.isSelected) { g2d.setColor(Color.green); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + g2d.fillRoundRect(sizes.rectSelection.x + 1, sizes.rectSelection.y + 1, sizes.rectSelection.width - 2, sizes.rectSelection.height - 2, cornerSizeSelection, cornerSizeSelection); } else if (key.isChoosable) { g2d.setColor(new Color(250, 250, 0, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + g2d.fillRoundRect(sizes.rectSelection.x + 1, sizes.rectSelection.y + 1, sizes.rectSelection.width - 2, sizes.rectSelection.height - 2, cornerSizeSelection, cornerSizeSelection); } else if (key.isPlayable) { g2d.setColor(new Color(153, 102, 204, 200)); - //g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + g2d.fillRoundRect(sizes.rectSelection.x, sizes.rectSelection.y, sizes.rectSelection.width, sizes.rectSelection.height, cornerSizeSelection, cornerSizeSelection); } + // draw empty card with border + if (!key.hasImage) { + // gray 1 px border + g2d.setColor(new Color(125, 125, 125, 255)); + g2d.fillRoundRect(sizes.rectBorder.x, sizes.rectBorder.y, sizes.rectBorder.width, sizes.rectBorder.height, cornerSizeBorder, cornerSizeBorder); + // color plate + g2d.setColor(new Color(30, 200, 200, 200)); + g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder); + } + + // draw attack border (inner part of border, not selection -- must always show attack) if (key.canAttack) { g2d.setColor(new Color(0, 0, 255, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder); } + // draw real card by component (see imagePanel and other layout's items) + //TODO:uncomment /* if (gameCard.isAttacking()) { @@ -409,18 +452,54 @@ public class CardPanelComponentImpl extends CardPanel { protected void paintChildren(Graphics g) { super.paintChildren(g); - if (getShowCastingCost() && !isAnimationPanel() && getCardWidth() < 200 && getCardWidth() > 60) { + CardSizes realCard = new CardSizes(getCardXOffset(), getCardYOffset(), getCardWidth(), getCardHeight()); + + /* + // draw recs for debug + + // full card + g.setColor(new Color(255, 0, 0)); + g.drawRect(realCard.rectFull.x, realCard.rectFull.y, realCard.rectFull.width, realCard.rectFull.height); + + // real card - image + g.setColor(new Color(0, 0, 255)); + g.drawRect(imagePanel.getX(), imagePanel.getY(), imagePanel.getBounds().width, imagePanel.getBounds().height); + + // caption + g.setColor(new Color(0, 255, 255)); + g.drawRect(titleText.getX(), titleText.getY(), titleText.getBounds().width, titleText.getBounds().height); + + // life points + g.setColor(new Color(120, 0, 120)); + g.drawRect(ptText.getX(), ptText.getY(), ptText.getBounds().width, ptText.getBounds().height); + //*/ + + if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth())) { + String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); - int width = getWidth(manaCost); + int manaWidth = getManaWidth(manaCost); + + // right top corner with margin (sizes from any sample card, length from black border to mana icon) + int manaMarginRight = Math.round(22f / 672f * getCardWidth()); + int manaMarginTop = Math.round(24f / 936f * getCardHeight()); + + int manaX = getCardXOffset() + getCardWidth() - manaMarginRight - manaWidth; + int manaY = getCardYOffset() + manaMarginTop; + if (hasImage) { - ManaSymbols.draw(g, manaCost, getCardXOffset() + getCardWidth() - width - 5, getCardYOffset() + 5, getSymbolWidth()); + // top right corner if have image like real card + ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth()); } else { - ManaSymbols.draw(g, manaCost, getCardXOffset() + 8, getCardHeight() - 9, getSymbolWidth()); + // old version - bottom left corner if haven't card + // ManaSymbols.draw(g, manaCost, getCardXOffset() + manaMarginRight, getCardYOffset() + getCardHeight() - manaMarginTop - getSymbolWidth(), getSymbolWidth()); + + // new version - like a normal image (it's best to view and construct decks) + ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth()); } } } - private int getWidth(String manaCost) { + private int getManaWidth(String manaCost) { int width = 0; manaCost = manaCost.replace("\\", ""); StringTokenizer tok = new StringTokenizer(manaCost, " "); @@ -439,24 +518,28 @@ public class CardPanelComponentImpl extends CardPanel { int cardHeight = getCardHeight(); int cardXOffset = getCardXOffset(); int cardYOffset = getCardYOffset(); - int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE); - imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + + CardSizes sizes = new CardSizes(cardXOffset, cardYOffset, cardWidth, cardHeight); + + // origin card without selection + Rectangle realCardSize = sizes.rectBorder; + imagePanel.setLocation(realCardSize.x, realCardSize.y); + imagePanel.setSize(realCardSize.width, realCardSize.height); if (hasSickness() && gameCard.isCreature() && isPermanent()) { - overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + overlayPanel.setLocation(realCardSize.x, realCardSize.y); + overlayPanel.setSize(realCardSize.width, realCardSize.height); } else { overlayPanel.setVisible(false); } if (iconPanel != null) { - iconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - iconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + iconPanel.setLocation(realCardSize.x, realCardSize.y); + iconPanel.setSize(realCardSize.width, realCardSize.height); } if (counterPanel != null) { - counterPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - counterPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + counterPanel.setLocation(realCardSize.x, realCardSize.y); + counterPanel.setSize(realCardSize.width, realCardSize.height); int size = cardWidth > WIDTH_LIMIT ? 40 : 20; minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2); @@ -472,32 +555,52 @@ public class CardPanelComponentImpl extends CardPanel { otherCounterLabel.setSize(size, size); } - int fontHeight = Math.round(cardHeight * (27f / 680)); - boolean showText = (!isAnimationPanel() && fontHeight < 12); + + // TITLE + + //old version - text hide on small fonts, why? + //int fontHeight = Math.round(cardHeight * (26f / 672)); + //boolean showText = (!isAnimationPanel() && fontHeight < 12); + + boolean showText = !isAnimationPanel() && canShowCardIcons(cardWidth); titleText.setVisible(showText); ptText.setVisible(showText); fullImageText.setVisible(fullImagePath != null); if (showText) { - int fontSize = cardHeight / 11; + int fontSize = cardHeight / 13; // startup font size (it same size on all zoom levels) titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - int titleX = Math.round(cardWidth * (20f / 480)); - int titleY = Math.round(cardHeight * (9f / 680)) + getTextOffset(); - titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight - titleY); + // margins from card black border to text, not need? text show up good without margins + int titleMarginLeft = 0; //Math.round(28f / 672f * cardWidth); + int titleMarginRight = 0; + int titleMarginTop = 0 + Math.round(getCardCaptionTopOffset() / 100f * cardHeight);//Math.round(28f / 936f * cardHeight); + int titleMarginBottom = 0; + titleText.setBounds( + imagePanel.getX() + titleMarginLeft, + imagePanel.getY() + titleMarginTop, + imagePanel.getBounds().width - titleMarginLeft - titleMarginRight, + imagePanel.getBounds().height - titleMarginTop - titleMarginBottom + ); fullImageText.setFont(getFont().deriveFont(Font.PLAIN, 10)); - fullImageText.setBounds(cardXOffset, cardYOffset + titleY, cardWidth, cardHeight - titleY); + fullImageText.setBounds(titleText.getX(), titleText.getY(), titleText.getBounds().width, titleText.getBounds().height); + // life points location (font as title) ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); Dimension ptSize = ptText.getPreferredSize(); ptText.setSize(ptSize.width, ptSize.height); - int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2; - int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height; - int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f); + // right bottom corner with margin (sizes from any sample card) + int ptMarginRight = Math.round(64f / 672f * cardWidth); + int ptMarginBottom = Math.round(62f / 936f * cardHeight); - ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); + int ptX = cardXOffset + cardWidth - ptMarginRight - ptSize.width; + int ptY = cardYOffset + cardHeight - ptMarginBottom - ptSize.height; + ptText.setLocation(ptX, ptY); + + // old version was with TEXT_GLOW_SIZE + //ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); } } @@ -581,7 +684,7 @@ public class CardPanelComponentImpl extends CardPanel { } else if (this.gameCard instanceof StackAbilityView) { return ImageCache.getMorphImage(); } else { - return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + return ImageCache.getCardbackImage(); } } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java index b418cd22df..c5c2582435 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java @@ -398,7 +398,7 @@ public class CardPanelRenderImpl extends CardPanel { } else if (this.gameCard instanceof StackAbilityView) { return ImageCache.getMorphImage(); } else { - return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + return ImageCache.getCardbackImage(); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index 97cbd4af82..86f22e06b2 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -3,7 +3,9 @@ package org.mage.plugins.card.images; import com.google.common.base.Function; import com.google.common.collect.ComputationException; import com.google.common.collect.MapMaker; -import java.awt.Graphics2D; + +import java.awt.*; +import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -81,12 +83,14 @@ public final class ImageCache { CardDownloadData info = new CardDownloadData(name, set, collectorId, usesVariousArt, type, tokenSetCode, tokenDescriptor); + boolean cardback = false; String path; if (collectorId.isEmpty() || "0".equals(collectorId)) { info.setToken(true); path = CardImageUtils.generateTokenImagePath(info); if (path == null) { - path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; + cardback = true; + path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback } } else { path = CardImageUtils.generateImagePath(info); @@ -101,6 +105,7 @@ public final class ImageCache { } if (thumbnail && path.endsWith(".jpg")) { + // need thumbnail image String thumbnailPath = buildThumbnailPath(path); TFile thumbnailFile = null; try { @@ -118,19 +123,35 @@ public final class ImageCache { if (exists) { LOGGER.debug("loading thumbnail for " + key + ", path=" + thumbnailPath); BufferedImage thumbnailImage = loadImage(thumbnailFile); + if (thumbnailImage == null) { // thumbnail exists but broken for some reason LOGGER.warn("failed loading thumbnail for " + key + ", path=" + thumbnailPath + ", thumbnail file is probably broken, attempting to recreate it..."); thumbnailImage = makeThumbnailByFile(key, file, thumbnailPath); } + + if (cardback){ + // unknown tokens on opponent desk + thumbnailImage = getRoundCorner(thumbnailImage); + } + return thumbnailImage; } else { return makeThumbnailByFile(key, file, thumbnailPath); } } else { - BufferedImage image = loadImage(file); - image = getWizardsCard(image); - return image; + if (cardback){ + // need cardback image + BufferedImage image = loadImage(file); + image = getRoundCorner(image); + return image; + }else { + // need normal card image + BufferedImage image = loadImage(file); + image = getWizardsCard(image); + image = getRoundCorner(image); + return image; + } } } else { throw new RuntimeException( @@ -148,6 +169,7 @@ public final class ImageCache { public BufferedImage makeThumbnailByFile(String key, TFile file, String thumbnailPath) { BufferedImage image = loadImage(file); image = getWizardsCard(image); + image = getRoundCorner(image); if (image == null) { return null; } @@ -189,7 +211,7 @@ public final class ImageCache { info.setToken(true); path = CardImageUtils.generateFullTokenImagePath(info); if (path == null) { - path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; + path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback } } else { path = CardImageUtils.generateImagePath(info); @@ -207,6 +229,12 @@ public final class ImageCache { private ImageCache() { } + public static BufferedImage getCardbackImage() { + BufferedImage image = ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + image = getRoundCorner(image); + return image; + } + public static BufferedImage getMorphImage() { CardDownloadData info = new CardDownloadData("Morph", "KTK", "0", false, 0, "KTK", ""); info.setToken(true); @@ -215,7 +243,10 @@ public final class ImageCache { return null; } TFile file = getTFile(path); - return loadImage(file); + + BufferedImage image = loadImage(file); + image = getRoundCorner(image); + return image; } public static BufferedImage getManifestImage() { @@ -226,7 +257,10 @@ public final class ImageCache { return null; } TFile file = getTFile(path); - return loadImage(file); + + BufferedImage image = loadImage(file); + image = getRoundCorner(image); + return image; } private static String buildThumbnailPath(String path) { @@ -239,6 +273,32 @@ public final class ImageCache { return thumbnailPath; } + public static BufferedImage getRoundCorner(BufferedImage image){ + if (image != null) { + BufferedImage cornerImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + + // corner + float ROUNDED_CORNER_SIZE = 0.15f; // see CardPanelComponentImpl + int cornerSizeBorder = Math.max(4, Math.round(image.getWidth() * ROUNDED_CORNER_SIZE)); + + // corner mask + Graphics2D gg = cornerImage.createGraphics(); + gg.setComposite(AlphaComposite.Src); + gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gg.setColor(Color.white); + gg.fill(new RoundRectangle2D.Float(0, 0, cornerImage.getWidth(), cornerImage.getHeight(), cornerSizeBorder, cornerSizeBorder)); + + // image draw to buffer + gg.setComposite(AlphaComposite.SrcAtop); + gg.drawImage(image, 0, 0, null); + //gg.dispose(); + + return cornerImage; + } else { + return image; + } + } + public static BufferedImage getWizardsCard(BufferedImage image) { if (image != null && image.getWidth() == 265 && image.getHeight() == 370) { BufferedImage crop = new BufferedImage(256, 360, BufferedImage.TYPE_INT_RGB); @@ -314,6 +374,7 @@ public final class ImageCache { // return alternateName + "#" + card.getExpansionSetCode() + "#" +card.getType()+ "#" + card.getCardNumber() + "#" // + (card.getTokenSetCode() == null ? "":card.getTokenSetCode()); // } + /** * Load image from file * diff --git a/Mage.Common/src/main/java/mage/cards/MageCard.java b/Mage.Common/src/main/java/mage/cards/MageCard.java index 61908d4dae..baea1105f2 100644 --- a/Mage.Common/src/main/java/mage/cards/MageCard.java +++ b/Mage.Common/src/main/java/mage/cards/MageCard.java @@ -25,8 +25,8 @@ public abstract class MageCard extends JPanel { public abstract CardView getOriginal(); - // sets the vertical text offset for the card name on the image - public abstract void setTextOffset(int yOffset); + // sets the vertical text offset for the card name on the image, use to move caption to card center + public abstract void setCardCaptionTopOffset(int yOffsetPercent); public abstract void setCardBounds(int x, int y, int width, int height); From a19e8790215df9eab7969f3c396923b1565c10e7 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Nov 2017 05:40:24 +0400 Subject: [PATCH 61/65] "Can attack" draw fix --- .../main/java/org/mage/card/arcane/CardPanelComponentImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index abe3770ffb..79d864a0df 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -432,7 +432,7 @@ public class CardPanelComponentImpl extends CardPanel { // draw attack border (inner part of border, not selection -- must always show attack) if (key.canAttack) { g2d.setColor(new Color(0, 0, 255, 230)); - g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder); + g2d.fillRoundRect(sizes.rectBorder.x - 1, sizes.rectBorder.y - 1, sizes.rectBorder.width + 2, sizes.rectBorder.height + 2, cornerSizeBorder, cornerSizeBorder); } // draw real card by component (see imagePanel and other layout's items) From 0689bfca40c146c280d4bd556b09d0582b74d965 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Nov 2017 05:49:31 +0400 Subject: [PATCH 62/65] comment type --- .../java/org/mage/card/arcane/CardPanelComponentImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index 79d864a0df..2002dc06a3 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -429,10 +429,10 @@ public class CardPanelComponentImpl extends CardPanel { g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder); } - // draw attack border (inner part of border, not selection -- must always show attack) + // draw attack border (inner part of selection) if (key.canAttack) { g2d.setColor(new Color(0, 0, 255, 230)); - g2d.fillRoundRect(sizes.rectBorder.x - 1, sizes.rectBorder.y - 1, sizes.rectBorder.width + 2, sizes.rectBorder.height + 2, cornerSizeBorder, cornerSizeBorder); + g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder); } // draw real card by component (see imagePanel and other layout's items) From 0a0475bd38842c9a982b40e44cee34b747ccbd99 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Nov 2017 06:40:01 +0400 Subject: [PATCH 63/65] Battlefield text position fix --- Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index b05a03ea26..230728563c 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -91,7 +91,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, private JPanel cardArea; - private int yCardCaptionOffsetPercent = 0; // card caption offset (use for moving card caption view center, below mana icons -- for more good UI) + // default offset, e.g. for battlefield + private int yCardCaptionOffsetPercent = 8; // card caption offset (use for moving card caption view center, below mana icons -- for more good UI) // if this is set, it's opened if the user right clicks on the card panel private JPopupMenu popupMenu; From 8ffa590a08a87fd9d99d0cbb35b9006636f1c7c8 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sat, 18 Nov 2017 06:59:31 +0100 Subject: [PATCH 64/65] Alleged text fix Will this mess up anything? --- Mage/src/main/java/mage/constants/PhaseStep.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/constants/PhaseStep.java b/Mage/src/main/java/mage/constants/PhaseStep.java index f6bb690515..9a0d167b74 100644 --- a/Mage/src/main/java/mage/constants/PhaseStep.java +++ b/Mage/src/main/java/mage/constants/PhaseStep.java @@ -13,7 +13,7 @@ public enum PhaseStep { PRECOMBAT_MAIN ("Precombat Main", 3,"precombat main step"), BEGIN_COMBAT ("Begin Combat", 4, "begin combat step"), DECLARE_ATTACKERS ("Declare Attackers", 5, "declare attackers step"), - DECLARE_BLOCKERS ("Declare Blockers", 6, "declare blockers"), + DECLARE_BLOCKERS ("Declare Blockers", 6, "declare blockers step"), FIRST_COMBAT_DAMAGE ("First Combat Damage", 7, "first combat damage"), COMBAT_DAMAGE ("Combat Damage", 8, "combat damage step"), END_COMBAT ("End Combat", 9, "end combat step"), From b78dab27a16b1bfc601179907da157e7a24134f1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Nov 2017 16:44:34 +0400 Subject: [PATCH 65/65] Little fix to always show icons and names for cards without images. --- .../org/mage/card/arcane/CardPanelComponentImpl.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index 2002dc06a3..311e1dd969 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -182,8 +182,11 @@ public class CardPanelComponentImpl extends CardPanel { IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) key -> createImage(key))); } - static private boolean canShowCardIcons(int fullWidth){ - return (fullWidth > 60) && (fullWidth < 200); + static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage){ + // cards without images show icons and text always + // TODO: apply "card names on card" setting to icon too? + // TODO: fix card min-max size to hide (compare to settings size, not direct 60 and 200) + return ((cardFullWidth > 60) && (cardFullWidth < 200)) || (!cardHasImage); } private static class CardSizes{ @@ -474,7 +477,7 @@ public class CardPanelComponentImpl extends CardPanel { g.drawRect(ptText.getX(), ptText.getY(), ptText.getBounds().width, ptText.getBounds().height); //*/ - if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth())) { + if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth(), hasImage)) { String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); int manaWidth = getManaWidth(manaCost); @@ -562,7 +565,7 @@ public class CardPanelComponentImpl extends CardPanel { //int fontHeight = Math.round(cardHeight * (26f / 672)); //boolean showText = (!isAnimationPanel() && fontHeight < 12); - boolean showText = !isAnimationPanel() && canShowCardIcons(cardWidth); + boolean showText = !isAnimationPanel() && canShowCardIcons(cardWidth, hasImage); titleText.setVisible(showText); ptText.setVisible(showText); fullImageText.setVisible(fullImagePath != null);