From 2a9d8edee885158911a9fdf4adc4c783c47cf9e8 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 01:28:17 +0200 Subject: [PATCH 01/28] - added Autobot and Dinosaur Subtypes - added TransformedPredicate(checks isTransformed()) --- .../src/main/java/mage/constants/SubType.java | 2 + .../permanent/TransformedPredicate.java | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index bb7a8e8f9b9..92618afc59a 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -56,6 +56,7 @@ public enum SubType { ATOG("Atog", SubTypeSet.CreatureType, false), ATAT("AT-AT", SubTypeSet.CreatureType, true), AUROCHS("Aurochs", SubTypeSet.CreatureType, false), + AUTOBOT("Autobot", SubTypeSet.CreatureType, false), AVATAR("Avatar", SubTypeSet.CreatureType, false), // B BADGER("Badger", SubTypeSet.CreatureType, false), @@ -99,6 +100,7 @@ public enum SubType { DEMON("Demon", SubTypeSet.CreatureType, false), DESERTER("Deserter", SubTypeSet.CreatureType, false), DEVIL("Devil", SubTypeSet.CreatureType, false), + DINOSAUR("Dinosaur", SubTypeSet.CreatureType, false), DJINN("Djinn", SubTypeSet.CreatureType, false), DRAGON("Dragon", SubTypeSet.CreatureType, false), DRAKE("Drake", SubTypeSet.CreatureType, false), diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java new file mode 100644 index 00000000000..a3b5ae63e93 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java @@ -0,0 +1,49 @@ +/* + * 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.filter.predicate.permanent; + +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author Saga + */ +public class TransformedPredicate implements Predicate { + + @Override + public boolean apply(Permanent input, Game game) { + return input.isTransformed(); + } + + @Override + public String toString() { + return "Transformed" ; + } +} \ No newline at end of file From e1dd26f7998094fa967356d4625f9d1ca3d9d86e Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 01:28:37 +0200 Subject: [PATCH 02/28] - added Grimlock, Dinobot Leader (including Grimlock, Ferocious King) - added Grimlock to H17 set --- .../mage/cards/g/GrimlockDinobotLeader.java | 98 +++++++++++++++++++ .../mage/cards/g/GrimlockFerociousKing.java | 79 +++++++++++++++ Mage.Sets/src/mage/sets/HasconPromo2017.java | 2 + 3 files changed, 179 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java create mode 100644 Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java diff --git a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java new file mode 100644 index 00000000000..14fb96a756c --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java @@ -0,0 +1,98 @@ +/* + * 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.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TransformedPredicate; + + +/** + * + * @author Saga + */ +public class GrimlockDinobotLeader extends CardImpl{ + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Dinosaurs and Vehicles"); + static { + filter.add(Predicates.or(new SubtypePredicate(SubType.DINOSAUR), new SubtypePredicate(SubType.VEHICLE))); + } + + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("Transformers creatures"); + static { + filter2.add(Predicates.not(new SubtypePredicate(SubType.DINOSAUR))); + filter2.add(Predicates.not(new SubtypePredicate(SubType.VEHICLE))); + filter2.add(Predicates.or(new AbilityPredicate(TransformAbility.class), new TransformedPredicate())); + } + + public GrimlockDinobotLeader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT,CardType.CREATURE}, "{1}{R}{G}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add("Autobot"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + this.transformable = true; + this.secondSideCardClazz = GrimlockFerociousKing.class; + + // Dinosaurs, Vehicles and other Transformers creatures you control get +2/+0. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 0, Duration.WhileOnBattlefield, filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 0, Duration.WhileOnBattlefield, filter2, true))); + + // {2}: Grimlock, Dinobot Leader becomes Grimlock, Ferocious King. + this.addAbility(new TransformAbility()); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(true), new ManaCostsImpl("{2}"))); + } + + public GrimlockDinobotLeader(final GrimlockDinobotLeader card) { + super(card); + } + + @Override + public GrimlockDinobotLeader copy() { + return new GrimlockDinobotLeader(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java new file mode 100644 index 00000000000..62b1662b151 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java @@ -0,0 +1,79 @@ +/* + * 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.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; + +/** + * + * @author Saga + */ +public class GrimlockFerociousKing extends CardImpl{ + + public GrimlockFerociousKing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},""); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add("Dinosaur"); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + this.color.setRed(true); + this.color.setGreen(true); + this.color.setWhite(true); + + this.transformable = true; + this.nightCard = true; + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // {2}: Grimlock, Ferocious King becomes Grimlock, Dinobot Leader. + this.addAbility(new TransformAbility()); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(false), new ManaCostsImpl("{2}"))); + } + + public GrimlockFerociousKing(final GrimlockFerociousKing card) { + super(card); + } + + @Override + public GrimlockFerociousKing copy() { + return new GrimlockFerociousKing(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/HasconPromo2017.java b/Mage.Sets/src/mage/sets/HasconPromo2017.java index 7b837618266..ad69eeeff86 100644 --- a/Mage.Sets/src/mage/sets/HasconPromo2017.java +++ b/Mage.Sets/src/mage/sets/HasconPromo2017.java @@ -44,6 +44,8 @@ public class HasconPromo2017 extends ExpansionSet { private HasconPromo2017() { super("HASCON Promo 2017", "H17", ExpansionSet.buildDate(2017, 9, 8), SetType.JOKESET); + cards.add(new ExpansionSet.SetCardInfo("Grimlock, Dinobot Leader", 1, Rarity.MYTHIC, mage.cards.g.GrimlockDinobotLeader.class)); + cards.add(new ExpansionSet.SetCardInfo("Grimlock, Ferocious King", 1, Rarity.MYTHIC, mage.cards.g.GrimlockFerociousKing.class)); cards.add(new ExpansionSet.SetCardInfo("Sword of Dungeons & Dragons", 3, Rarity.MYTHIC, mage.cards.s.SwordOfDungeonsAndDragons.class)); } } From c99c3f1765782f42797ebf6982c1a435157395c9 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 17:02:29 +0200 Subject: [PATCH 03/28] - added Balan, Wandering Knight - added Balan to C17 --- .../mage/cards/b/BalanWanderingKnight.java | 125 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 126 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java diff --git a/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java new file mode 100644 index 00000000000..bea551f55ff --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.EquippedMultipleSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author Saga + */ +public class BalanWanderingKnight extends CardImpl { + + private static final String rule = "{this} has double strike as long as two or more Equipment are attached to it."; + + public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add("Cat"); + this.subtype.add("Knight"); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First Strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it. + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), EquippedMultipleSourceCondition.instance, rule); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // {1}{W}: Attach all Equipment you control to Balan. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BalanWanderingKnightEffect(), new ManaCostsImpl("{1}{W}"))); + } + + public BalanWanderingKnight(final BalanWanderingKnight card) { + super(card); + } + + @Override + public BalanWanderingKnight copy() { + return new BalanWanderingKnight(this); + } + + static class BalanWanderingKnightEffect extends OneShotEffect { + + public BalanWanderingKnightEffect() { + super(Outcome.Benefit); + this.staticText = "Attach all Equipment you control to {this}."; + } + + public BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) { + super(effect); + } + + @Override + public BalanWanderingKnightEffect copy() { + return new BalanWanderingKnightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent balan = game.getPermanent(source.getSourceId()); + if (balan != null) { + FilterPermanent filter = new FilterPermanent(); + filter.add(new SubtypePredicate(SubType.EQUIPMENT)); + for (Permanent equipment : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(),game)) { + if (equipment != null) { + //If an Equipment can’t equip, it isn’t attached, and it doesn’t become unattached (if it’s attached to a creature). + if (!balan.cantBeAttachedBy(equipment, game)) { + balan.addAttachment(equipment.getId(), game); + } + } + } + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index cc123715bf8..d0955136aff 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -47,6 +47,7 @@ public class Commander2017 extends ExpansionSet { super("Commander 2017 Edition", "C17", ExpansionSet.buildDate(2017, 8, 25), SetType.SUPPLEMENTAL); this.blockName = "Command Zone"; + cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 3, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); From 21b892a9b442877a03c0986b734e60b7ff4065fc Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 17:03:21 +0200 Subject: [PATCH 04/28] -added EquippedMultipleSourceCondition (to check if permanent has 2 or more Equipments attached) --- .../EquippedMultipleSourceCondition.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java diff --git a/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java new file mode 100644 index 00000000000..35249427434 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * Describes condition when creature is equipped with more than one Equipment. + * + * @author Saga + */ +public enum EquippedMultipleSourceCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); + int countEquipped = 0; + if (permanent != null) { + for (UUID uuid : permanent.getAttachments()) { + Permanent attached = game.getBattlefield().getPermanent(uuid); + if (attached != null && attached.getSubtype(game).contains("Equipment")) { + countEquipped++; + if (countEquipped >= 2) { + return true; + } + } + } + } + return false; + } + + @Override + public String toString() { + return "has multiple Equipments attached"; + } + +} \ No newline at end of file From 05f5b99a99136d061ea90556c7a96ec45a8ae76f Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 22:35:08 +0200 Subject: [PATCH 05/28] - changed Autobot and Dinosaur to fantasy types --- .../mage/cards/b/BalanWanderingKnight.java | 125 ------------------ .../src/main/java/mage/constants/SubType.java | 11 +- 2 files changed, 6 insertions(+), 130 deletions(-) delete mode 100644 Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java diff --git a/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java deleted file mode 100644 index bea551f55ff..00000000000 --- a/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package mage.cards.b; - -import java.util.UUID; -import mage.MageInt; -import mage.abilities.Ability;; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedMultipleSourceCondition; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; -import mage.abilities.keyword.DoubleStrikeAbility; -import mage.abilities.keyword.FirstStrikeAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.filter.predicate.mageobject.SubtypePredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; - -/** - * - * @author Saga - */ -public class BalanWanderingKnight extends CardImpl { - - private static final String rule = "{this} has double strike as long as two or more Equipment are attached to it."; - - public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}{W}"); - this.addSuperType(SuperType.LEGENDARY); - this.subtype.add("Cat"); - this.subtype.add("Knight"); - this.power = new MageInt(3); - this.toughness = new MageInt(3); - - // First Strike - this.addAbility(FirstStrikeAbility.getInstance()); - - // Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), EquippedMultipleSourceCondition.instance, rule); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); - - // {1}{W}: Attach all Equipment you control to Balan. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BalanWanderingKnightEffect(), new ManaCostsImpl("{1}{W}"))); - } - - public BalanWanderingKnight(final BalanWanderingKnight card) { - super(card); - } - - @Override - public BalanWanderingKnight copy() { - return new BalanWanderingKnight(this); - } - - static class BalanWanderingKnightEffect extends OneShotEffect { - - public BalanWanderingKnightEffect() { - super(Outcome.Benefit); - this.staticText = "Attach all Equipment you control to {this}."; - } - - public BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) { - super(effect); - } - - @Override - public BalanWanderingKnightEffect copy() { - return new BalanWanderingKnightEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent balan = game.getPermanent(source.getSourceId()); - if (balan != null) { - FilterPermanent filter = new FilterPermanent(); - filter.add(new SubtypePredicate(SubType.EQUIPMENT)); - for (Permanent equipment : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(),game)) { - if (equipment != null) { - //If an Equipment can’t equip, it isn’t attached, and it doesn’t become unattached (if it’s attached to a creature). - if (!balan.cantBeAttachedBy(equipment, game)) { - balan.addAttachment(equipment.getId(), game); - } - } - } - return true; - } - return false; - } - } -} \ No newline at end of file diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 92618afc59a..1bb17a3a628 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -3,6 +3,7 @@ package mage.constants; import mage.util.SubTypeList; import java.util.Arrays; +import java.util.EnumSet; import java.util.Set; import java.util.stream.Collectors; @@ -56,7 +57,7 @@ public enum SubType { ATOG("Atog", SubTypeSet.CreatureType, false), ATAT("AT-AT", SubTypeSet.CreatureType, true), AUROCHS("Aurochs", SubTypeSet.CreatureType, false), - AUTOBOT("Autobot", SubTypeSet.CreatureType, false), + AUTOBOT("Autobot", SubTypeSet.CreatureType, true), // H17, Grimlock AVATAR("Avatar", SubTypeSet.CreatureType, false), // B BADGER("Badger", SubTypeSet.CreatureType, false), @@ -100,7 +101,7 @@ public enum SubType { DEMON("Demon", SubTypeSet.CreatureType, false), DESERTER("Deserter", SubTypeSet.CreatureType, false), DEVIL("Devil", SubTypeSet.CreatureType, false), - DINOSAUR("Dinosaur", SubTypeSet.CreatureType, false), + DINOSAUR("Dinosaur", SubTypeSet.CreatureType, true), // only Grimlock right now, until Ixalan DJINN("Djinn", SubTypeSet.CreatureType, false), DRAGON("Dragon", SubTypeSet.CreatureType, false), DRAKE("Drake", SubTypeSet.CreatureType, false), @@ -420,10 +421,10 @@ public enum SubType { return subTypeSet; } - public static SubTypeList getCreatureTypes(boolean customSet) { - SubTypeList subTypes = new SubTypeList(); + public static Set getCreatureTypes(boolean customSet) { + Set subTypes = EnumSet.noneOf(SubType.class); for (SubType s : values()) { - if (!s.customSet) { + if (s.customSet == customSet && s.getSubTypeSet() == SubTypeSet.CreatureType) { subTypes.add(s); } } From 8819d8abaed7fc2bf956f71577bf4851fffe14a2 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 22:37:01 +0200 Subject: [PATCH 06/28] - changed to new SubType.add method --- Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java index 62b1662b151..7b301c4f980 100644 --- a/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java +++ b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java @@ -37,6 +37,7 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; @@ -49,7 +50,7 @@ public class GrimlockFerociousKing extends CardImpl{ public GrimlockFerociousKing(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},""); this.addSuperType(SuperType.LEGENDARY); - this.subtype.add("Dinosaur"); + this.subtype.add(SubType.DINOSAUR); this.power = new MageInt(8); this.toughness = new MageInt(8); this.color.setRed(true); From dbda9eb4b2f32df2dcbf9e6782e59fa2addaaf52 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 22:37:23 +0200 Subject: [PATCH 07/28] - changed to new SubType.add method --- Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java index 14fb96a756c..664360080e7 100644 --- a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java +++ b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java @@ -70,7 +70,7 @@ public class GrimlockDinobotLeader extends CardImpl{ public GrimlockDinobotLeader(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT,CardType.CREATURE}, "{1}{R}{G}{W}"); this.addSuperType(SuperType.LEGENDARY); - this.subtype.add("Autobot"); + this.subtype.add(SubType.AUTOBOT); this.power = new MageInt(4); this.toughness = new MageInt(4); From 53c31d4a8140f3760889de1652513c7b97c7edd7 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Mon, 7 Aug 2017 22:38:42 +0200 Subject: [PATCH 08/28] - changed to new SubType.add method --- .../mage/cards/b/BalanWanderingKnight.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java diff --git a/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java new file mode 100644 index 00000000000..27c266242e0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java @@ -0,0 +1,124 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.EquippedMultipleSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author Saga + */ +public class BalanWanderingKnight extends CardImpl { + + private static final String rule = "{this} has double strike as long as two or more Equipment are attached to it."; + + public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT, SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First Strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it. + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), EquippedMultipleSourceCondition.instance, rule); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // {1}{W}: Attach all Equipment you control to Balan. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BalanWanderingKnightEffect(), new ManaCostsImpl("{1}{W}"))); + } + + public BalanWanderingKnight(final BalanWanderingKnight card) { + super(card); + } + + @Override + public BalanWanderingKnight copy() { + return new BalanWanderingKnight(this); + } + + static class BalanWanderingKnightEffect extends OneShotEffect { + + public BalanWanderingKnightEffect() { + super(Outcome.Benefit); + this.staticText = "Attach all Equipment you control to {this}."; + } + + public BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) { + super(effect); + } + + @Override + public BalanWanderingKnightEffect copy() { + return new BalanWanderingKnightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent balan = game.getPermanent(source.getSourceId()); + if (balan != null) { + FilterPermanent filter = new FilterPermanent(); + filter.add(new SubtypePredicate(SubType.EQUIPMENT)); + for (Permanent equipment : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(),game)) { + if (equipment != null) { + //If an Equipment can’t equip, it isn’t attached, and it doesn’t become unattached (if it’s attached to a creature). + if (!balan.cantBeAttachedBy(equipment, game)) { + balan.addAttachment(equipment.getId(), game); + } + } + } + return true; + } + return false; + } + } +} From 3a52ce609d9e3f2f0c1d9f7c2ad2d436110839d8 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Thu, 10 Aug 2017 00:13:36 -0400 Subject: [PATCH 09/28] Implemented Carrion, Cephalid Pathmage, Cephalid Inkshrouder and Ceremonial Guard (#3793) * Implement Epicenter * Implement Infested Roothold * Implement Brown Ouphe * Implement Rust * Implement Ouphe Vandals * Implement Fatal Attraction * Implement Fighting Chance * Implement Arcbound Fiend * Implement Carry Away * Implement Caverns of Despair * Implement Amulet of Unmaking * Implement Aphetto Exterminator * Implement Balduvian Conjurer, Barrin's Unmaking, Each Mana Battery, Blood Hound and Boa Constrictor * Implement Bola Warrior, Brass Talon Chimera, Briar Patch and Brine Shaman * Implemented Call to Arms, Goham Djinn, Halam Djinn, Ruham Djinn, Sulam Djinn, Zanam Djinn, Heroic Defiance and Tsabo's Assassin, updated Barrin's Unmaking and Epicenter * Implement Brown Ouphe * Implemented Break Open * Implemented Carrion * Implemented Cephalid Pathmage * Implemented Cephalid Inkshrouder * Implemented Ceremonial Guard --- Mage.Sets/src/mage/cards/b/BreakOpen.java | 103 ++++++++++++++++++ Mage.Sets/src/mage/cards/c/Carrion.java | 65 +++++++++++ .../src/mage/cards/c/CephalidInkshrouder.java | 71 ++++++++++++ .../src/mage/cards/c/CephalidPathmage.java | 77 +++++++++++++ .../src/mage/cards/c/CeremonialGuard.java | 69 ++++++++++++ Mage.Sets/src/mage/sets/IceAge.java | 8 +- Mage.Sets/src/mage/sets/Judgment.java | 1 + Mage.Sets/src/mage/sets/Legions.java | 1 + Mage.Sets/src/mage/sets/MercadianMasques.java | 1 + Mage.Sets/src/mage/sets/Mirage.java | 1 + Mage.Sets/src/mage/sets/Onslaught.java | 1 + .../token/CarrionBlackInsectToken.java | 47 ++++++++ .../token/NestOfScarabsBlackInsectToken.java | 2 +- 13 files changed, 442 insertions(+), 5 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/b/BreakOpen.java create mode 100644 Mage.Sets/src/mage/cards/c/Carrion.java create mode 100644 Mage.Sets/src/mage/cards/c/CephalidInkshrouder.java create mode 100644 Mage.Sets/src/mage/cards/c/CephalidPathmage.java create mode 100644 Mage.Sets/src/mage/cards/c/CeremonialGuard.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/CarrionBlackInsectToken.java diff --git a/Mage.Sets/src/mage/cards/b/BreakOpen.java b/Mage.Sets/src/mage/cards/b/BreakOpen.java new file mode 100644 index 00000000000..44cda67427b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BreakOpen.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.abilities.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.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.FaceDownPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public class BreakOpen extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Face-down creature an opponent controls"); + + static { + filter.add(new FaceDownPredicate()); + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public BreakOpen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Turn target face-down creature an opponent controls face up. + this.getSpellAbility().addEffect(new TurnFaceUpTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + } + + public BreakOpen(final BreakOpen card) { + super(card); + } + + @Override + public BreakOpen copy() { + return new BreakOpen(this); + } +} + +class TurnFaceUpTargetEffect extends OneShotEffect { + + public TurnFaceUpTargetEffect() { + super(Outcome.Benefit); + this.staticText = "Turn target face-down creature an opponen controls face up."; + } + + public TurnFaceUpTargetEffect(final TurnFaceUpTargetEffect effect) { + super(effect); + } + + @Override + public TurnFaceUpTargetEffect copy() { + return new TurnFaceUpTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID target = targetPointer.getFirst(game, source); + if (target != null) { + Permanent permanent = game.getPermanent(target); + if (permanent != null) { + return permanent.turnFaceUp(game, source.getControllerId()); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/Carrion.java b/Mage.Sets/src/mage/cards/c/Carrion.java new file mode 100644 index 00000000000..36707992bd3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Carrion.java @@ -0,0 +1,65 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.dynamicvalue.common.SacrificeCostCreaturesPower; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.permanent.token.CarrionBlackInsectToken; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public class Carrion extends CardImpl { + + public Carrion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}{B}"); + + // As an additional cost to cast Carrion, sacrifice a creature. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent("a creature"), false))); + + // Put X 0/1 black Insect creature tokens onto the battlefield, where X is the sacrificed creature's power. + this.getSpellAbility().addEffect(new CreateTokenEffect(new CarrionBlackInsectToken(), new SacrificeCostCreaturesPower())); + } + + public Carrion(final Carrion card) { + super(card); + } + + @Override + public Carrion copy() { + return new Carrion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CephalidInkshrouder.java b/Mage.Sets/src/mage/cards/c/CephalidInkshrouder.java new file mode 100644 index 00000000000..8f2c4260edd --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CephalidInkshrouder.java @@ -0,0 +1,71 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.ShroudAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; + +/** + * + * @author TheElk801 + */ +public class CephalidInkshrouder extends CardImpl { + + public CephalidInkshrouder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add("Cephalid"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Discard a card: Cephalid Inkshrouder gains shroud until end of turn and is unblockable this turn. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(ShroudAbility.getInstance(),Duration.EndOfTurn), new DiscardCardCost()); + ability.addEffect(new CantBeBlockedSourceEffect(Duration.EndOfTurn)); + this.addAbility(ability); + } + + public CephalidInkshrouder(final CephalidInkshrouder card) { + super(card); + } + + @Override + public CephalidInkshrouder copy() { + return new CephalidInkshrouder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CephalidPathmage.java b/Mage.Sets/src/mage/cards/c/CephalidPathmage.java new file mode 100644 index 00000000000..7dfd9e2ebc5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CephalidPathmage.java @@ -0,0 +1,77 @@ +/* + * 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.c; + +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.common.TapSourceCost; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public class CephalidPathmage extends CardImpl { + + public CephalidPathmage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add("Cephalid"); + this.subtype.add("Wizard"); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Cephalid Pathmage is unblockable. + this.addAbility(new CantBeBlockedSourceAbility()); + + // {tap}, Sacrifice Cephalid Pathmage: Target creature is unblockable this turn. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CantBeBlockedTargetEffect(Duration.EndOfTurn), new SacrificeSourceCost()); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public CephalidPathmage(final CephalidPathmage card) { + super(card); + } + + @Override + public CephalidPathmage copy() { + return new CephalidPathmage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CeremonialGuard.java b/Mage.Sets/src/mage/cards/c/CeremonialGuard.java new file mode 100644 index 00000000000..11ebeef6d60 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CeremonialGuard.java @@ -0,0 +1,69 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.AttacksOrBlocksTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DestroySourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author TheElk801 + */ +public class CeremonialGuard extends CardImpl { + + public CeremonialGuard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add("Human"); + this.subtype.add("Soldier"); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Ceremonial Guard attacks or blocks, destroy it at end of combat. + this.addAbility(new AttacksOrBlocksTriggeredAbility( + new CreateDelayedTriggeredAbilityEffect( + new AtTheEndOfCombatDelayedTriggeredAbility(new DestroySourceEffect())), + false)); + } + + public CeremonialGuard(final CeremonialGuard card) { + super(card); + } + + @Override + public CeremonialGuard copy() { + return new CeremonialGuard(this); + } +} diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index d63d6a81410..e8f9e590ce0 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -64,7 +64,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Aurochs", 113, Rarity.COMMON, mage.cards.a.Aurochs.class)); cards.add(new SetCardInfo("Balduvian Barbarians", 172, Rarity.COMMON, mage.cards.b.BalduvianBarbarians.class)); cards.add(new SetCardInfo("Balduvian Bears", 114, Rarity.COMMON, mage.cards.b.BalduvianBears.class)); - cards.add(new SetCardInfo("Balduvian Conjurer", 58, Rarity.UNCOMMON, mage.cards.b.BalduvianConjurer.class)); + cards.add(new SetCardInfo("Balduvian Conjurer", 58, Rarity.UNCOMMON, mage.cards.b.BalduvianConjurer.class)); cards.add(new SetCardInfo("Balduvian Hydra", 173, Rarity.RARE, mage.cards.b.BalduvianHydra.class)); cards.add(new SetCardInfo("Barbed Sextant", 287, Rarity.COMMON, mage.cards.b.BarbedSextant.class)); cards.add(new SetCardInfo("Battle Frenzy", 175, Rarity.COMMON, mage.cards.b.BattleFrenzy.class)); @@ -75,11 +75,11 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Blue Scarab", 233, Rarity.UNCOMMON, mage.cards.b.BlueScarab.class)); cards.add(new SetCardInfo("Brainstorm", 61, Rarity.COMMON, mage.cards.b.Brainstorm.class)); cards.add(new SetCardInfo("Brand of Ill Omen", 177, Rarity.RARE, mage.cards.b.BrandOfIllOmen.class)); - cards.add(new SetCardInfo("Brine Shaman", 3, Rarity.COMMON, mage.cards.b.BrineShaman.class)); - cards.add(new SetCardInfo("Brown Ouphe", 116, Rarity.COMMON, mage.cards.b.BrownOuphe.class)); + cards.add(new SetCardInfo("Brine Shaman", 3, Rarity.COMMON, mage.cards.b.BrineShaman.class)); + cards.add(new SetCardInfo("Brown Ouphe", 116, Rarity.COMMON, mage.cards.b.BrownOuphe.class)); cards.add(new SetCardInfo("Brushland", 327, Rarity.RARE, mage.cards.b.Brushland.class)); cards.add(new SetCardInfo("Burnt Offering", 4, Rarity.COMMON, mage.cards.b.BurntOffering.class)); - cards.add(new SetCardInfo("Call to Arms", 234, Rarity.RARE, mage.cards.c.CallToArms.class)); + cards.add(new SetCardInfo("Call to Arms", 234, Rarity.RARE, mage.cards.c.CallToArms.class)); cards.add(new SetCardInfo("Caribou Range", 235, Rarity.RARE, mage.cards.c.CaribouRange.class)); cards.add(new SetCardInfo("Celestial Sword", 289, Rarity.RARE, mage.cards.c.CelestialSword.class)); cards.add(new SetCardInfo("Centaur Archer", 360, Rarity.UNCOMMON, mage.cards.c.CentaurArcher.class)); diff --git a/Mage.Sets/src/mage/sets/Judgment.java b/Mage.Sets/src/mage/sets/Judgment.java index b1cb49a0261..7443e32904e 100644 --- a/Mage.Sets/src/mage/sets/Judgment.java +++ b/Mage.Sets/src/mage/sets/Judgment.java @@ -78,6 +78,7 @@ public class Judgment extends ExpansionSet { cards.add(new SetCardInfo("Canopy Claws", 108, Rarity.COMMON, mage.cards.c.CanopyClaws.class)); cards.add(new SetCardInfo("Centaur Rootcaster", 109, Rarity.COMMON, mage.cards.c.CentaurRootcaster.class)); cards.add(new SetCardInfo("Cephalid Constable", 35, Rarity.RARE, mage.cards.c.CephalidConstable.class)); + cards.add(new SetCardInfo("Cephalid Inkshrouder", 36, Rarity.UNCOMMON, mage.cards.c.CephalidInkshrouder.class)); cards.add(new SetCardInfo("Chastise", 8, Rarity.UNCOMMON, mage.cards.c.Chastise.class)); cards.add(new SetCardInfo("Commander Eesha", 9, Rarity.RARE, mage.cards.c.CommanderEesha.class)); cards.add(new SetCardInfo("Crush of Wurms", 110, Rarity.RARE, mage.cards.c.CrushOfWurms.class)); diff --git a/Mage.Sets/src/mage/sets/Legions.java b/Mage.Sets/src/mage/sets/Legions.java index 47c63b27e47..ca0b546f054 100644 --- a/Mage.Sets/src/mage/sets/Legions.java +++ b/Mage.Sets/src/mage/sets/Legions.java @@ -69,6 +69,7 @@ public class Legions extends ExpansionSet { cards.add(new SetCardInfo("Brood Sliver", 120, Rarity.RARE, mage.cards.b.BroodSliver.class)); cards.add(new SetCardInfo("Caller of the Claw", 121, Rarity.RARE, mage.cards.c.CallerOfTheClaw.class)); cards.add(new SetCardInfo("Canopy Crawler", 122, Rarity.UNCOMMON, mage.cards.c.CanopyCrawler.class)); + cards.add(new SetCardInfo("Cephalid Pathmage", 31, Rarity.COMMON, mage.cards.c.CephalidPathmage.class)); cards.add(new SetCardInfo("Chromeshell Crab", 32, Rarity.RARE, mage.cards.c.ChromeshellCrab.class)); cards.add(new SetCardInfo("Clickslither", 90, Rarity.RARE, mage.cards.c.Clickslither.class)); cards.add(new SetCardInfo("Cloudreach Cavalry", 7, Rarity.UNCOMMON, mage.cards.c.CloudreachCavalry.class)); diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index ee14c79b3bb..25b51eb5dab 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -87,6 +87,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Cave-In", 180, Rarity.RARE, mage.cards.c.CaveIn.class)); cards.add(new SetCardInfo("Cavern Crawler", 181, Rarity.COMMON, mage.cards.c.CavernCrawler.class)); cards.add(new SetCardInfo("Cave Sense", 179, Rarity.COMMON, mage.cards.c.CaveSense.class)); + cards.add(new SetCardInfo("Ceremonial Guard", 182, Rarity.COMMON, mage.cards.c.CeremonialGuard.class)); cards.add(new SetCardInfo("Chambered Nautilus", 64, Rarity.UNCOMMON, mage.cards.c.ChamberedNautilus.class)); cards.add(new SetCardInfo("Charisma", 66, Rarity.RARE, mage.cards.c.Charisma.class)); cards.add(new SetCardInfo("Cho-Arrim Alchemist", 8, Rarity.RARE, mage.cards.c.ChoArrimAlchemist.class)); diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index 72418f8f492..255d8661b96 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -82,6 +82,7 @@ public class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Cadaverous Bloom", 318, Rarity.RARE, mage.cards.c.CadaverousBloom.class)); cards.add(new SetCardInfo("Cadaverous Knight", 8, Rarity.COMMON, mage.cards.c.CadaverousKnight.class)); cards.add(new SetCardInfo("Canopy Dragon", 107, Rarity.RARE, mage.cards.c.CanopyDragon.class)); + cards.add(new SetCardInfo("Carrion", 9, Rarity.RARE, mage.cards.c.Carrion.class)); cards.add(new SetCardInfo("Celestial Dawn", 210, Rarity.RARE, mage.cards.c.CelestialDawn.class)); cards.add(new SetCardInfo("Cerulean Wyvern", 57, Rarity.UNCOMMON, mage.cards.c.CeruleanWyvern.class)); cards.add(new SetCardInfo("Chaos Charm", 163, Rarity.COMMON, mage.cards.c.ChaosCharm.class)); diff --git a/Mage.Sets/src/mage/sets/Onslaught.java b/Mage.Sets/src/mage/sets/Onslaught.java index 5339c73962e..3437ea31a94 100644 --- a/Mage.Sets/src/mage/sets/Onslaught.java +++ b/Mage.Sets/src/mage/sets/Onslaught.java @@ -56,6 +56,7 @@ public class Onslaught extends ExpansionSet { cards.add(new SetCardInfo("Bloodline Shaman", 249, Rarity.UNCOMMON, mage.cards.b.BloodlineShaman.class)); cards.add(new SetCardInfo("Bloodstained Mire", 313, Rarity.RARE, mage.cards.b.BloodstainedMire.class, new CardGraphicInfo(new ObjectColor("RB"), null,false))); cards.add(new SetCardInfo("Boneknitter", 128, Rarity.UNCOMMON, mage.cards.b.Boneknitter.class)); + cards.add(new SetCardInfo("Break Open", 190, Rarity.COMMON, mage.cards.b.BreakOpen.class)); cards.add(new SetCardInfo("Brightstone Ritual", 191, Rarity.COMMON, mage.cards.b.BrightstoneRitual.class)); cards.add(new SetCardInfo("Broodhatch Nantuko", 250, Rarity.UNCOMMON, mage.cards.b.BroodhatchNantuko.class)); cards.add(new SetCardInfo("Cabal Archon", 129, Rarity.UNCOMMON, mage.cards.c.CabalArchon.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/CarrionBlackInsectToken.java b/Mage/src/main/java/mage/game/permanent/token/CarrionBlackInsectToken.java new file mode 100644 index 00000000000..764b99070a2 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/CarrionBlackInsectToken.java @@ -0,0 +1,47 @@ +/* +* 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.MageInt; + +/** + * + * @author TheElk801 + */ +public class CarrionBlackInsectToken extends Token { + + public CarrionBlackInsectToken() { + super("Insect", "0/1 black Insect creature token"); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add("Insect"); + power = new MageInt(0); + toughness = new MageInt(1); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/NestOfScarabsBlackInsectToken.java b/Mage/src/main/java/mage/game/permanent/token/NestOfScarabsBlackInsectToken.java index 8434fa6d65b..565e0863049 100644 --- a/Mage/src/main/java/mage/game/permanent/token/NestOfScarabsBlackInsectToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/NestOfScarabsBlackInsectToken.java @@ -37,7 +37,7 @@ import mage.MageInt; public class NestOfScarabsBlackInsectToken extends Token { public NestOfScarabsBlackInsectToken() { - super("Insect", "1/1 black Insect token"); + super("Insect", "1/1 black Insect creature token"); cardType.add(CardType.CREATURE); color.setBlack(true); subtype.add("Insect"); From a3e7aa62302481037d8ec8b52c345da764b6d653 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 10 Aug 2017 07:04:32 +0200 Subject: [PATCH 10/28] * Karn Liberated - fixed that after using its -14 ability the game does not properly set the starting player of the new game (fixes #3786). --- Mage.Sets/src/mage/cards/k/KarnLiberated.java | 1 + Mage/src/main/java/mage/game/Game.java | 2 + Mage/src/main/java/mage/game/GameImpl.java | 83 +++++++++++-------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KarnLiberated.java b/Mage.Sets/src/mage/cards/k/KarnLiberated.java index 6d7c6867f39..e0a35d5bd66 100644 --- a/Mage.Sets/src/mage/cards/k/KarnLiberated.java +++ b/Mage.Sets/src/mage/cards/k/KarnLiberated.java @@ -157,6 +157,7 @@ class KarnLiberatedEffect extends OneShotEffect { } } game.addDelayedTriggeredAbility(new KarnLiberatedDelayedTriggeredAbility(exileId), source); + game.setStartingPlayerId(source.getControllerId()); game.start(null); return true; } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 87040399b57..9ac4302df8e 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -442,6 +442,8 @@ public interface Game extends MageItem, Serializable { UUID getStartingPlayerId(); + void setStartingPlayerId(UUID startingPlayerId); + void saveRollBackGameState(); boolean canRollbackTurns(int turnsToRollback); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 41d2996498a..7f4f936bba3 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -27,6 +27,10 @@ */ package mage.game; +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; import mage.MageException; import mage.MageObject; import mage.abilities.*; @@ -92,11 +96,6 @@ import mage.watchers.Watchers; import mage.watchers.common.*; import org.apache.log4j.Logger; -import java.io.IOException; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; - public abstract class GameImpl implements Game, Serializable { private static final int ROLLBACK_TURNS_MAX = 4; @@ -855,31 +854,33 @@ public abstract class GameImpl implements Game, Serializable { } //20091005 - 103.2 - TargetPlayer targetPlayer = new TargetPlayer(); - targetPlayer.setTargetName("starting player"); Player choosingPlayer = null; - if (choosingPlayerId != null) { - choosingPlayer = this.getPlayer(choosingPlayerId); - if (choosingPlayer != null && !choosingPlayer.isInGame()) { - choosingPlayer = null; + if (startingPlayerId == null) { + TargetPlayer targetPlayer = new TargetPlayer(); + targetPlayer.setTargetName("starting player"); + if (choosingPlayerId != null) { + choosingPlayer = this.getPlayer(choosingPlayerId); + if (choosingPlayer != null && !choosingPlayer.isInGame()) { + choosingPlayer = null; + } } - } - if (choosingPlayer == null) { - choosingPlayerId = pickChoosingPlayer(); - if (choosingPlayerId == null) { + if (choosingPlayer == null) { + choosingPlayerId = pickChoosingPlayer(); + if (choosingPlayerId == null) { + return; + } + choosingPlayer = getPlayer(choosingPlayerId); + } + if (choosingPlayer == null) { + return; + } + getState().setChoosingPlayerId(choosingPlayerId); // needed to start/stop the timer if active + if (choosingPlayer.choose(Outcome.Benefit, targetPlayer, null, this)) { + startingPlayerId = targetPlayer.getTargets().get(0); + } else if (getState().getPlayers().size() < 3) { + // not possible to choose starting player, choosing player has probably conceded, so stop here return; } - choosingPlayer = getPlayer(choosingPlayerId); - } - if (choosingPlayer == null) { - return; - } - getState().setChoosingPlayerId(choosingPlayerId); // needed to start/stop the timer if active - if (choosingPlayer.choose(Outcome.Benefit, targetPlayer, null, this)) { - startingPlayerId = targetPlayer.getTargets().get(0); - } else if (getState().getPlayers().size() < 3) { - // not possible to choose starting player, choosing player has probably conceded, so stop here - return; } if (startingPlayerId == null) { // choose any available player as starting player @@ -898,15 +899,7 @@ public abstract class GameImpl implements Game, Serializable { logger.debug("Starting player not found. playerId:" + startingPlayerId); return; } - StringBuilder message = new StringBuilder(choosingPlayer.getLogName()).append(" chooses that "); - if (choosingPlayer.getId().equals(startingPlayerId)) { - message.append("he or she"); - } else { - message.append(startingPlayer.getLogName()); - } - message.append(" takes the first turn"); - - this.informPlayers(message.toString()); + sendStartMessage(choosingPlayer, startingPlayer); //20091005 - 103.3 int startingHandSize = 7; @@ -1019,6 +1012,21 @@ public abstract class GameImpl implements Game, Serializable { } + protected void sendStartMessage(Player choosingPlayer, Player startingPlayer) { + StringBuilder message = new StringBuilder(); + if (choosingPlayer != null) { + message.append(choosingPlayer.getLogName()).append(" chooses that "); + } + if (choosingPlayer != null && choosingPlayer.getId().equals(startingPlayer.getId())) { + message.append("he or she"); + } else { + message.append(startingPlayer.getLogName()); + } + message.append(" takes the first turn"); + + this.informPlayers(message.toString()); + } + protected UUID findWinnersAndLosers() { UUID winnerIdFound = null; for (Player player : state.getPlayers().values()) { @@ -2834,6 +2842,11 @@ public abstract class GameImpl implements Game, Serializable { return startingPlayerId; } + @Override + public void setStartingPlayerId(UUID startingPlayerId) { + this.startingPlayerId = startingPlayerId; + } + @Override public int getLife() { return startLife; From 127f8d2b963c2f195601de1a6a81ba74b3417a8d Mon Sep 17 00:00:00 2001 From: spjspj Date: Thu, 10 Aug 2017 20:52:49 +1000 Subject: [PATCH 11/28] C17 spoilers (201708) --- Utils/mtg-cards-data.txt | 53 ++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 22b29c48675..d0ef58e3cbe 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -31684,32 +31684,49 @@ Forest|Planechase Anthology|153|L||Basic Land - Forest|||| Forest|Planechase Anthology|154|L||Basic Land - Forest|||| Forest|Planechase Anthology|155|L||Basic Land - Forest|||| Forest|Planechase Anthology|156|L||Basic Land - Forest|||| -The Ur-Dragon|Commander 2017|48|M|{4}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Avatar|10|10|Eminence - As long as The Ur-Dragon is in the command zone or on the battlefield, other Dragon spells you cast cost {1} less to cast.$Flying$Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield| -O-Kagachi, Vengeful Kami|Commander 2017|45|M|{1}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Spirit|6|6|Flying, Trample$Whenever O-Kagachi, Vengeful Kami deals combat damage to a player, if that player attacked you during his or her last turn, exile target nonland permanent that player controls| +Alms Collector|Commander 2017|1|R|{3}{W}|Creature - Cat Cleric|3|4|Flash$If an opponent would draw two or more cards, instead you and that player each draw a card.| Arahbo, Roar of the World|Commander 2017|35|M|{3}{G}{W}|Legendary Creature - Cat Avatar|5|5|Eminence — At the beginning of combat on your turn, if Arahbo, Roar of the World is in the command zone or on the battlefield, another target Cat you control gets +3/+3 until end of turn.$Whenever another Cat you control attacks, you may pay {1}{G}{W}. If you do, it gains trample and gets +X/+X until end of turn, where X is its power.| -Taigam, Ojutai Master|Commander 2017|46|R|{2}{W}{U}|Legendary Creature - Human Monk|3|4|Instant, sorcery, and Dragon spells you control can't be countered by spells or abilities.$Whenever you cast an instant or sorcery spell from your hand, if Taigam, Ojutai Master attacked this turn, that spell gains rebound. (Exile the spell as it resolves. At the beginning of your next upkeep, you may cast that card from exile without paying its mana cost.)| -Wasitora, Nekoru Queen|Commander 2017|49|M|{2}{B}{R}{G}|Legendary Creature - Cat Dragon|5|4|Flying, trample$Whenever Wasitora, Nekoru Queen deals combat damage to a player, that player sacrifices a creature. If the player can't, you create a 3/3 black, red, and green Cat Dragon creature token with flying| -Ramos, Dragon Engine|Commander 2017|55|M|{6}|Legendary Artifact Creature - Dragon|4|4|Flying$Whenever you cast a spell, put a +1/+1 counter on Ramos, Dragon Engine for each of that spell's colors. Remove five +1/+1 counters from Ramos: Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G} to your mana pool. Activate this ability only once each turn.| +Balan, Wandering Knight|Commander 2017|2|R|{2}{W}{W}|Legendary Creature - Cat Knight|3|3|First strike$Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it.$1W: Attach all Equipment you control to Balan.| +Bloodforged War Axe|Commander 2017|50|R|{1}|Artifact - Equipment|||Equipped creature gets +2/+0.$Whenever equipped creature deals combat damage to a player, create a token that's a copy of Bloodforged War Axe.$Equip 2| +Bloodline Necromancer|Commander 2017|14|U|{4}{B}|Creature - Vampire Wizard|3|2|Lifelink$When Bloodline Necromancer enters the battlefield, you may return target Vampire or Wizard creature card from your graveyard to the battlefield.| +Bloodsworn Steward|Commander 2017|22|R|{2}{R}{R}|Creature - Vampire Knight|4|4|Flying$Commander creatures you control get +2/+2 and have haste.| +Boneyard Scourge|Commander 2017|15|R|{2}{B}{B}|Creature - Zombie Dragon|4|3|Flying$Whenever a Dragon you control dies while Boneyard Scourge is in your graveyard, you may pay 1B. If you do, return Boneyard Scourge from your graveyard to the battlefield.| +Crimson Honor Guard|Commander 2017|23|R|{3}{R}{R}|Creature - Vampire Knight|4|5|Trample$At the beginning of each player's end step, Crimson Honor Guard deals 4 damage to that player unless he or she controls a commander.| +Curse of Bounty|Commander 2017|30|U|{1}{G}|Enchantment - Aura Curse|||Enchant player$Whenever enchanted player is attacked, untap all nonland permanents you control. Each opponent attacking that player untaps all nonland permanents he or she controls.| +Curse of Disturbance|Commander 2017|16|U|{2}{B}|Enchantment - Aura Curse|||Enchant player$Whenever enchanted player is attacked, create a 2/2 black Zombie creature token. Each opponent attacking that player does the same.| +Curse of Opulence|Commander 2017|24|U|{R}|Enchantment - Aura Curse|||Enchant player$Whenever enchanted player is attacked, create a colorless artifact token named Gold. It has "sacrifice this artifact: Add one mana of any color to your mana pool." Each opponent attacking that player does the same.| +Curse of Verbosity|Commander 2017|9|U|{2}{U}|Enchantment - Aura Curse|||Enchant player$Whenever enchanted player is attacked, draw a card. Each opponent attacking that player does the same.| +Curse of Vitality|Commander 2017|3|U|{2}{W}|Enchantment - Aura Curse|||Enchant player$Whenever enchanted player is attacked, you gain 2 life. Each opponent attacking that player does the same.| +Disrupt Decorum|Commander 2017|25|R|{2}{R}{R}|Sorcery|||Goad all creatures you don't control.$(Until your next turn, those creatures attack each combat if able and attack a player other than you if able.)| +Edgar Markov|Commander 2017|36|M|{3}{R}{W}{B}|Legendary Creature - Vampire Knight|4|4|Eminence - Whenever you cast another Vampire spell, if Edgar Markov is in the command zone or on the battlefield, create a 1/1 black Vampire creature token.$First strike, haste$Whenever Edgar Markov attacks, put a +1/+1 counter on each Vampire you control.| +Fortunate Few|Commander 2017|4|R|{3}{W}{W}|Sorcery|||Choose a nonland permanent you don't control, then each other player chooses a nonland permanent he or she doesn't control that hasn't been chosen this way. Destroy all other nonland permanents.| +Fractured Identity|Commander 2017|37|R|{3}{W}{U}|Sorcery|||Exile target nonland permanent. Each player other than its controller creates a token that's a copy of it.| +Hammer of Nazahn|Commander 2017|51|R|{4}|Legendary Artifact - Equipment|||Whenever Hammer of Nazahn or another Equipment enters the battlefield under your control, you may attach that Equipment to target creature you control.$Equipped creature gets +2/+0 and has indestructible.$Equip 4| +Heirloom Blade|Commander 2017|52|U|{3}|Artifact - Equipment|||Equipped creature gets +3/+1.$Whenever equipped creature dies, you may reveal cards from the top of your library until you reveal a creature card that shares a creature type with it. Put that card into your hand and the rest on the bottom of your library in a random order.$Equip 1| +Herald's Horn|Commander 2017|53|U|{3}|Artifact|||When Herald's Horn enters the battlefield, choose a creature type.$Creature spells you cast of the chosen type cost 1 less.$At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand.| +Hungry Lynx|Commander 2017|31|R|{1}{G}|Creature - Cat|2|2|Cats you control have protection from Rats. (They can't be blocked, targeted, or dealt damage by Rats.)$At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch.$Whenever a Rat dies, put a +1/+1 counter on each Cat you control.| +Inalla, Archmage Ritualist|Commander 2017|??|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Eminence - Whenever another nontoken Wizard enters the battlefield under your control, if Inalla, Archmage Ritualist is in the command zone or on the battlefield, you may pay {1}. If you do, create a token that's a copy of that Wizard. The token gains haste. Exile it at the beginning of the next end step.$Tap five untapped Wizards you control: Target player loses 7 life.| +Kheru Mind-Eater|Commander 2017|17|R|{2}{B}|Creature - Vampire|1|3|Menace$Whenever Kheru Mind-Eater deals combat damage to a player, that player exiles a card from his or her hand face down.$You may look at and play cards exiled with Kheru Mind-Eater.| +Kindred Charge|Commander 2017|27|R|{4}{R}{R}|Sorcery|||Choose a creature type. For each creature you control of the chosen type, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step.| +Kindred Discovery|Commander 2017|11|R|{3}{U}{U}|Enchantment|||As Kindred Discovery enters the battlefield, choose a creature type.$Whenever a creature you control of the chose type enters the battlefield or attacks, draw a card.| Licia, Sanguine Tribune|Commander 2017|40|M|{5}{R}{W}{B}|Legendary Creature - Vampire Soldier|4|4|Licia, Sanguine Tribune costs 1 less to cast for each 1 life you gained this turn.$First strike, lifelink$Pay 5 life: Put three +1/+1 counters on Licia. Activate this ability only on your turn and only once each turn.| +Mathas, Fiend Seeker|Commander 2017|42|M|{R}{W}{B}|Legendary Creature - Vampire|3|3|Menace$At the beginning of your end step, put a bounty counter on target creature an opponent controls. For as long as that creature has a bounty counter on it, it has "When this creature dies, each opponent draws a card and gains 2 life."| Mirri, Weatherlight Duelist|Commander 2017|43|M|{1}{G}{W}|Legendary Creature - Cat Warrior|3|2|First Strike$Whenever Mirri, Weatherlight Duelist attacks, each opponent can't block with more than one creature this combat.$As long as Mirri, Weatherlight Duelist is tapped, no more than one creature can attack you each combat.| Nazahn, Revered Bladesmith|Commander 2017|44|M|{4}{G}{W}|Legendary Creature - Cat Artificer|5|4|When Nazahn, Revered Bladesmith enters the battlefield, search your library for an Equipment card and reveal it. If you reveal a card named Hammer of Nazahn this way, put it onto the battlefield. Otherwise, put that card into your hand. Then shuffle your library.$Whenever an equipped creature you control attacks, you may tap target creature defending player controls.| -Bloodforged War Axe|Commander 2017|50|R|{1}|Artifact - Equipment|||Equipped creature gets +2/+0.$Whenever equipped creature deals combat damage to a player, create a token that's a copy of Bloodforged War Axe.$Equip 2| -Hammer of Nazahn|Commander 2017|51|R|{4}|Legendary Artifact - Equipment|||Whenever Hammer of Nazahn or another Equipment enters the battlefield under your control, you may attach that Equipment to target creature you control.$Equipped creature gets +2/+0 and has indestructible.$Equip 4| -Herald's Horn|Commander 2017|53|U|{3}|Artifact|||When Herald's Horn enters the battlefield, choose a creature type.$Creature spells you cast of the chosen type cost 1 less.$At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand.| -Fractured Identity|Commander 2017|37|R|{3}{W}{U}|Sorcery|||Exile target nonland permanent. Each player other than its controller creates a token that's a copy of it.| +New Blood|Commander 2017|19|R|{2}{B}{B}|Sorcery|||As an additional cost to cast New Blood, tap an untapped Vampire you control.$Gain control of target creature. Change the text of that creature by replacing all instances of one creature type with Vampire.| +O-Kagachi, Vengeful Kami|Commander 2017|45|M|{1}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Spirit|6|6|Flying, Trample$Whenever O-Kagachi, Vengeful Kami deals combat damage to a player, if that player attacked you during his or her last turn, exile target nonland permanent that player controls| Path of Ancestry|Commander 2017|56|C||Land|||Path of Ancestry enters the battlefield tapped.$T: Add to your mana pool one mana of any color in your commander's color identity. When that mana is spent to cast a creature spell that shares a creature type with your commander, scry 1.| -Fortunate Few|Commander 2017|4|R|{3}{W}{W}|Sorcery|||Choose a nonland permanent you don't control, then each other player chooses a nonland permanent he or she doesn't control that hasn't been chosen this way. Destroy all other nonland permanents.| +Patron of the Vein|Commander 2017|20|R|{4}{B}{B}|Creature - Vampire Shaman|4|4|Flying$When Patron of the Vein enters the battlefield, destroy target creature an opponent controls.$Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control.| +Ramos, Dragon Engine|Commander 2017|55|M|{6}|Legendary Artifact Creature - Dragon|4|4|Flying$Whenever you cast a spell, put a +1/+1 counter on Ramos, Dragon Engine for each of that spell's colors. Remove five +1/+1 counters from Ramos: Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G} to your mana pool. Activate this ability only once each turn.| Scalelord Reckoner|Commander 2017|??|R|{5}{W}{W}|Creature - Dragon|4|4|Flying$Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls.| -Alms Collector|Commander 2017|1|R|{3}{W}|Creature - Cat Cleric|3|4|Flash$If an opponent would draw two or more cards, instead you and that player each draw a card.| -Balan, Wandering Knight|Commander 2017|2|R|{2}{W}{W}|Legendary Creature - Cat Knight|3|3|First strike$Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it.$1W: Attach all Equipment you control to Balan.| Stalking Leonin|Commander 2017|7|R|{2}{W}|Creature - Cat Archer|3|3|When Stalking Leonin enters the battlefield, secretly choose an opponent.$Reveal the player you chose: Exile target creature that's attacking you if it's controlled by the chosen player. Activate this ability only once.| -Kindred Discovery|Commander 2017|11|R|{3}{U}{U}|Enchantment|||As Kindred Discovery enters the battlefield, choose a creature type.$Whenever a creature you control of the chose type enters the battlefield or attacks, draw a card.| -Boneyard Scourge|Commander 2017|15|R|{2}{B}{B}|Creature - Zombie Dragon|4|3|Flying$Whenever a Dragon you control dies while Boneyard Scourge is in your graveyard, you may pay 1B. If you do, return Boneyard Scourge from your graveyard to the battlefield.| -Bloodline Necromancer|Commander 2017|14|U|{4}{B}|Creature - Vampire Wizard|3|2|Lifelink$When Bloodline Necromancer enters the battlefield, you may return target Vampire or Wizard creature card from your graveyard to the battlefield.| -Territorial Hellkite|Commander 2017|??|R|{2}{R}{R}|Creature - Dragon|6|5|Flying, haste$At the beginning of combat on your turn, choose an opponent at random that Territorial Hellkite didn't attack during your last combat. Territorial Hellkite attacks that player this combat if able. If you can't choose an opponent this way, tap Territorial Hellkite.| Summon the Tribe|Commander 2017|32|R|{5}{G}{G}|Instant|||Choose a creature type. Reveal the top card card of your library until you reveal X creatures of the chosen type, where X is the number of creatures you control of the chosen type, and place them onto the battlefield. Shuffle the other revealed cards into your library.| -Hungry Lynx|Commander 2017|31|R|{1}{G}|Creature - Cat|2|2|Cats you control have protection from Rats. (They can't be blocked, targeted, or dealt damage by Rats.)$At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch.$Whenever a Rat dies, put a +1/+1 counter on each Cat you control.| +Taigam, Ojutai Master|Commander 2017|46|R|{2}{W}{U}|Legendary Creature - Human Monk|3|4|Instant, sorcery, and Dragon spells you control can't be countered by spells or abilities.$Whenever you cast an instant or sorcery spell from your hand, if Taigam, Ojutai Master attacked this turn, that spell gains rebound. (Exile the spell as it resolves. At the beginning of your next upkeep, you may cast that card from exile without paying its mana cost.)| +Teferi's Protection|Commander 2017|8|R|{2}{W}|Instant|||Until your next turn, your life total can't change, and you have protection from everything. All permanents you control phase out. (While they're phased out, they're treated as though they don't exist. They phase in before you untap during your untap step.)$Exile Teferi's Protection.| +Territorial Hellkite|Commander 2017|??|R|{2}{R}{R}|Creature - Dragon|6|5|Flying, haste$At the beginning of combat on your turn, choose an opponent at random that Territorial Hellkite didn't attack during your last combat. Territorial Hellkite attacks that player this combat if able. If you can't choose an opponent this way, tap Territorial Hellkite.| +The Ur-Dragon|Commander 2017|48|M|{4}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Avatar|10|10|Eminence - As long as The Ur-Dragon is in the command zone or on the battlefield, other Dragon spells you cast cost {1} less to cast.$Flying$Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield| Traverse the Outlands|Commander 2017|34|R|{4}{G}|Sorcery|||Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle your library.| +Wasitora, Nekoru Queen|Commander 2017|49|M|{2}{B}{R}{G}|Legendary Creature - Cat Dragon|5|4|Flying, trample$Whenever Wasitora, Nekoru Queen deals combat damage to a player, that player sacrifices a creature. If the player can't, you create a 3/3 black, red, and green Cat Dragon creature token with flying| Aegis Angel|Archenemy: Nicol Bolas|1|R|{4}{W}{W}|Creature - Angel|5|5|Flying$When Aegis Angel enters the battlefield, another target creature gains indestructible for as long as you control Aegis Angel.| Aerial Responder|Archenemy: Nicol Bolas|2|U|{1}{W}{W}|Creature - Dwarf Soldier|2|3|Flying, vigilance, lifelink| Anointer of Champions|Archenemy: Nicol Bolas|3|U|{W}|Creature - Human Cleric|1|1|{T}: Target attacking creature gets +1/+1 until end of turn.| @@ -31866,7 +31883,7 @@ Champion of Wits|Hour of Devastation|31|R|{2}{U}|Creature - Naga Wizard|2|1|When Countervailing Winds|Hour of Devastation|32|C|{2}{U}|Instant|||Counter target spell unless its controller pays {1} for each card in your graveyard.$Cycling {2} ({2}, Discard this card: Draw a card.)| Cunning Survivor|Hour of Devastation|33|C|{1}{U}|Creature - Human Warrior|1|3|Whenever you cycle or discard a card, Cunning Survivor gets +1/+0 until end of turn and can't be blocked this turn.| Eternal of Harsh Truths|Hour of Devastation|34|U|{2}{U}|Creature - Zombie Cleric|1|3|Afflict 2 (Whenever this creature becomes blocked, defending player loses 2 life.)$Whenever Eternal of Harsh Truths attacks and isn't blocked, draw a card.| -Fraying Sanity|Hour of Devastation|35|R|{2}{U}|Enchantment - Aura Curse|||Enchant player$At the beginning of each end step, enchanted player puts the top X cards of his or her library into his or her graveyard, where X is the total number of cards put into his or her graveyard from anywhere this turn. | +Fraying Sanity|Hour of Devastation|35|R|{2}{U}|Enchantment - Aura Curse|||Enchant player$At the beginning of each end step, enchanted player puts the top X cards of his or her library into his or her graveyard, where X is the total number of cards put into his or her graveyard from anywhere this turn.| Hour of Eternity|Hour of Devastation|36|R|{X}{X}{U}{U}{U}|Sorcery|||Exile X target creature cards from your graveyard. For each card exiled this way, create a token that's a copy of that card, except it's a 4/4 black Zombie.| Imaginary Threats|Hour of Devastation|37|U|{2}{U}{U}|Instant|||Creatures target opponent controls attack this turn if able. During that player's next untap step, creatures he or she controls don't untap.$Cycling {2} ({2}, Discard this card: Draw a card.)| Jace's Defeat|Hour of Devastation|38|U|{1}{U}|Instant|||Counter target blue spell. If it was a Jace planeswalker spell, scry 2.| From 8b88e9eed4c2d7ff6b735ce8da21300521a93ae7 Mon Sep 17 00:00:00 2001 From: spjspj Date: Fri, 11 Aug 2017 14:18:44 +1000 Subject: [PATCH 12/28] C17 spoilers (201708) --- Utils/mtg-cards-data.txt | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index d0ef58e3cbe..41b2ec251f6 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -31701,31 +31701,44 @@ Disrupt Decorum|Commander 2017|25|R|{2}{R}{R}|Sorcery|||Goad all creatures you d Edgar Markov|Commander 2017|36|M|{3}{R}{W}{B}|Legendary Creature - Vampire Knight|4|4|Eminence - Whenever you cast another Vampire spell, if Edgar Markov is in the command zone or on the battlefield, create a 1/1 black Vampire creature token.$First strike, haste$Whenever Edgar Markov attacks, put a +1/+1 counter on each Vampire you control.| Fortunate Few|Commander 2017|4|R|{3}{W}{W}|Sorcery|||Choose a nonland permanent you don't control, then each other player chooses a nonland permanent he or she doesn't control that hasn't been chosen this way. Destroy all other nonland permanents.| Fractured Identity|Commander 2017|37|R|{3}{W}{U}|Sorcery|||Exile target nonland permanent. Each player other than its controller creates a token that's a copy of it.| +Galecaster Colossus|Commander 2017|10|R|{5}{U}{U}|Creature - Giant Wizard|5|6|Tap an untapped Wizard you control: Return target non-land permanent you don't control to its owner's hand.| Hammer of Nazahn|Commander 2017|51|R|{4}|Legendary Artifact - Equipment|||Whenever Hammer of Nazahn or another Equipment enters the battlefield under your control, you may attach that Equipment to target creature you control.$Equipped creature gets +2/+0 and has indestructible.$Equip 4| Heirloom Blade|Commander 2017|52|U|{3}|Artifact - Equipment|||Equipped creature gets +3/+1.$Whenever equipped creature dies, you may reveal cards from the top of your library until you reveal a creature card that shares a creature type with it. Put that card into your hand and the rest on the bottom of your library in a random order.$Equip 1| Herald's Horn|Commander 2017|53|U|{3}|Artifact|||When Herald's Horn enters the battlefield, choose a creature type.$Creature spells you cast of the chosen type cost 1 less.$At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand.| Hungry Lynx|Commander 2017|31|R|{1}{G}|Creature - Cat|2|2|Cats you control have protection from Rats. (They can't be blocked, targeted, or dealt damage by Rats.)$At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch.$Whenever a Rat dies, put a +1/+1 counter on each Cat you control.| -Inalla, Archmage Ritualist|Commander 2017|??|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Eminence - Whenever another nontoken Wizard enters the battlefield under your control, if Inalla, Archmage Ritualist is in the command zone or on the battlefield, you may pay {1}. If you do, create a token that's a copy of that Wizard. The token gains haste. Exile it at the beginning of the next end step.$Tap five untapped Wizards you control: Target player loses 7 life.| +Inalla, Archmage Ritualist|Commander 2017|38|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Eminence - Whenever another nontoken Wizard enters the battlefield under your control, if Inalla, Archmage Ritualist is in the command zone or on the battlefield, you may pay {1}. If you do, create a token that's a copy of that Wizard. The token gains haste. Exile it at the beginning of the next end step.$Tap five untapped Wizards you control: Target player loses 7 life.| +Izzet Chemister|Commander 2017|26|R|{2}{R}|Creature - Goblin Chemister|1|3|Haste$R, T: Exile target instant or sorcery card from your graveyard.$1R, T: Sacrifice Izzet Chemister: Cast any number of cards exiled with Izzet Chemister without paying their mana costs.| +Kess, Dissident Mage|Commander 2017|39|M|{1}{U}{B}{R}|Legendary Creature - Human Wizard|3|4|Flying$During each of your turns, you may cast an instant or sorcery card from your graveyard. If a card cast this way would be put into your graveyard this turn, exile it instead.| Kheru Mind-Eater|Commander 2017|17|R|{2}{B}|Creature - Vampire|1|3|Menace$Whenever Kheru Mind-Eater deals combat damage to a player, that player exiles a card from his or her hand face down.$You may look at and play cards exiled with Kheru Mind-Eater.| +Kindred Boon|Commander 2017|5|R|{2}{W}{W}|Enchantment|||When Kindred Boon enters the battlefield choose a creature type.$1W: Put a divinity counter on target creature you control with the chosen type.$All creatures you control with a divinity counter become indestructible.| Kindred Charge|Commander 2017|27|R|{4}{R}{R}|Sorcery|||Choose a creature type. For each creature you control of the chosen type, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step.| Kindred Discovery|Commander 2017|11|R|{3}{U}{U}|Enchantment|||As Kindred Discovery enters the battlefield, choose a creature type.$Whenever a creature you control of the chose type enters the battlefield or attacks, draw a card.| +Kindred Dominance|Commander 2017|18|R|{5}{B}{B}|Sorcery|||Choose a creature type. Destroy all creatures that are not the chosen type.| +Kindred Summons|Commander 2017|32|R|{5}{G}{G}|Instant|||Choose a creature type. Reveal the top card card of your library until you reveal X creatures of the chosen type, where X is the number of creatures you control of the chosen type, and place them onto the battlefield. Shuffle the other revealed cards into your library.| Licia, Sanguine Tribune|Commander 2017|40|M|{5}{R}{W}{B}|Legendary Creature - Vampire Soldier|4|4|Licia, Sanguine Tribune costs 1 less to cast for each 1 life you gained this turn.$First strike, lifelink$Pay 5 life: Put three +1/+1 counters on Licia. Activate this ability only on your turn and only once each turn.| +Magus of the Mind|Commander 2017|12|R|{4}{U}{U}|Creature - Human Wizard|4|5|U, T: Sacrifice Magus of the Mind: Shuffle your library, then exile the top X cards, where X is one plus the number of spells cast this turn. Until end of turn, you may play cards exiled this way without paying their mana costs.| +Mairsil, the Pretender|Commander 2017|41|M|{1}{U}{B}{R}|Legendary Creature - Human Wizard|4|4|When Mairsil, the Pretender enters the battlefield, you may exile an artifact or creature card from your hand or graveyard and put a cage counter on it.$Mairsil, the Pretender has all activated abilities of all cards you own in exile with cage counters on them. You may activate each of those abilities only once each turn.| Mathas, Fiend Seeker|Commander 2017|42|M|{R}{W}{B}|Legendary Creature - Vampire|3|3|Menace$At the beginning of your end step, put a bounty counter on target creature an opponent controls. For as long as that creature has a bounty counter on it, it has "When this creature dies, each opponent draws a card and gains 2 life."| Mirri, Weatherlight Duelist|Commander 2017|43|M|{1}{G}{W}|Legendary Creature - Cat Warrior|3|2|First Strike$Whenever Mirri, Weatherlight Duelist attacks, each opponent can't block with more than one creature this combat.$As long as Mirri, Weatherlight Duelist is tapped, no more than one creature can attack you each combat.| +Mirror of the Forebears|Commander 2017|54|U|{2}|Artifact|||As Mirror of the Forebears enters the battlefield, choose a creature type.$1: Until end of turn, Mirror of the Forebears becomes a copy of target creature you control of the chosen type, except it's an artifact in addition to its other types.| Nazahn, Revered Bladesmith|Commander 2017|44|M|{4}{G}{W}|Legendary Creature - Cat Artificer|5|4|When Nazahn, Revered Bladesmith enters the battlefield, search your library for an Equipment card and reveal it. If you reveal a card named Hammer of Nazahn this way, put it onto the battlefield. Otherwise, put that card into your hand. Then shuffle your library.$Whenever an equipped creature you control attacks, you may tap target creature defending player controls.| New Blood|Commander 2017|19|R|{2}{B}{B}|Sorcery|||As an additional cost to cast New Blood, tap an untapped Vampire you control.$Gain control of target creature. Change the text of that creature by replacing all instances of one creature type with Vampire.| O-Kagachi, Vengeful Kami|Commander 2017|45|M|{1}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Spirit|6|6|Flying, Trample$Whenever O-Kagachi, Vengeful Kami deals combat damage to a player, if that player attacked you during his or her last turn, exile target nonland permanent that player controls| Path of Ancestry|Commander 2017|56|C||Land|||Path of Ancestry enters the battlefield tapped.$T: Add to your mana pool one mana of any color in your commander's color identity. When that mana is spent to cast a creature spell that shares a creature type with your commander, scry 1.| Patron of the Vein|Commander 2017|20|R|{4}{B}{B}|Creature - Vampire Shaman|4|4|Flying$When Patron of the Vein enters the battlefield, destroy target creature an opponent controls.$Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control.| +Portal Mage|Commander 2017|13|R|{2}{U}|Creature - Human Wizard|2|2|Flash$If Portal Mage enters the battlefield during the declare attackers step, you may reselect the player or planeswalker that the target attacking creature attacks.| +Qasali Slingers|Commander 2017|33|R|{4}{G}|Creature - Cat Warrior|3|5|Reach$Whenever Qasali Slingers or another Cat enters the battlefield under your control, you may destroy target artifact or enchantment.| Ramos, Dragon Engine|Commander 2017|55|M|{6}|Legendary Artifact Creature - Dragon|4|4|Flying$Whenever you cast a spell, put a +1/+1 counter on Ramos, Dragon Engine for each of that spell's colors. Remove five +1/+1 counters from Ramos: Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G} to your mana pool. Activate this ability only once each turn.| -Scalelord Reckoner|Commander 2017|??|R|{5}{W}{W}|Creature - Dragon|4|4|Flying$Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls.| +Scalelord Reckoner|Commander 2017|6|R|{5}{W}{W}|Creature - Dragon|4|4|Flying$Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls.| +Shifting Shadow|Commander 2017|28|R|{2}{R}|Enchantment - Aura|||Enchant creature$Enchanted creature has haste and "At the beginning of your upkeep, destroy this creature. Reveal cards from the top of your library until you reveal a creature card. Put that card onto the battlefield and attach Shifting Shadow to it, then put all other cards revealed this way on the bottom of your library in a random order."| Stalking Leonin|Commander 2017|7|R|{2}{W}|Creature - Cat Archer|3|3|When Stalking Leonin enters the battlefield, secretly choose an opponent.$Reveal the player you chose: Exile target creature that's attacking you if it's controlled by the chosen player. Activate this ability only once.| -Summon the Tribe|Commander 2017|32|R|{5}{G}{G}|Instant|||Choose a creature type. Reveal the top card card of your library until you reveal X creatures of the chosen type, where X is the number of creatures you control of the chosen type, and place them onto the battlefield. Shuffle the other revealed cards into your library.| Taigam, Ojutai Master|Commander 2017|46|R|{2}{W}{U}|Legendary Creature - Human Monk|3|4|Instant, sorcery, and Dragon spells you control can't be countered by spells or abilities.$Whenever you cast an instant or sorcery spell from your hand, if Taigam, Ojutai Master attacked this turn, that spell gains rebound. (Exile the spell as it resolves. At the beginning of your next upkeep, you may cast that card from exile without paying its mana cost.)| +Taigam, Sidisi's Hand|Commander 2017|47|R|{3}{U}{B}|Legendary Creature - Human Wizard|3|4|Skip your draw step.$At the beginning of your upkeep, look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard.$B, T, Exile X cards from your graveyard: Target creature gets -X/-X until end of turn.| Teferi's Protection|Commander 2017|8|R|{2}{W}|Instant|||Until your next turn, your life total can't change, and you have protection from everything. All permanents you control phase out. (While they're phased out, they're treated as though they don't exist. They phase in before you untap during your untap step.)$Exile Teferi's Protection.| -Territorial Hellkite|Commander 2017|??|R|{2}{R}{R}|Creature - Dragon|6|5|Flying, haste$At the beginning of combat on your turn, choose an opponent at random that Territorial Hellkite didn't attack during your last combat. Territorial Hellkite attacks that player this combat if able. If you can't choose an opponent this way, tap Territorial Hellkite.| +Territorial Hellkite|Commander 2017|29|R|{2}{R}{R}|Creature - Dragon|6|5|Flying, haste$At the beginning of combat on your turn, choose an opponent at random that Territorial Hellkite didn't attack during your last combat. Territorial Hellkite attacks that player this combat if able. If you can't choose an opponent this way, tap Territorial Hellkite.| The Ur-Dragon|Commander 2017|48|M|{4}{W}{U}{B}{R}{G}|Legendary Creature - Dragon Avatar|10|10|Eminence - As long as The Ur-Dragon is in the command zone or on the battlefield, other Dragon spells you cast cost {1} less to cast.$Flying$Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield| Traverse the Outlands|Commander 2017|34|R|{4}{G}|Sorcery|||Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle your library.| +Vindictive Lich|Commander 2017|21|R|{3}{B}|Creature - Zombie Wizard|4|1|When Vindictive Lich dies, choose one or more. Each mode must target a different player.$*Target opponent sacrifices a creature.$*Target opponent discards two cards.$*Target opponent loses 5 life.| Wasitora, Nekoru Queen|Commander 2017|49|M|{2}{B}{R}{G}|Legendary Creature - Cat Dragon|5|4|Flying, trample$Whenever Wasitora, Nekoru Queen deals combat damage to a player, that player sacrifices a creature. If the player can't, you create a 3/3 black, red, and green Cat Dragon creature token with flying| Aegis Angel|Archenemy: Nicol Bolas|1|R|{4}{W}{W}|Creature - Angel|5|5|Flying$When Aegis Angel enters the battlefield, another target creature gains indestructible for as long as you control Aegis Angel.| Aerial Responder|Archenemy: Nicol Bolas|2|U|{1}{W}{W}|Creature - Dwarf Soldier|2|3|Flying, vigilance, lifelink| From 625620efcc3708eb0de8aca8b6d8a7ab52af4105 Mon Sep 17 00:00:00 2001 From: spjspj Date: Fri, 11 Aug 2017 16:50:57 +1000 Subject: [PATCH 13/28] Implement Traverse the Outlands (C17) --- .../src/mage/cards/t/TraverseTheOutlands.java | 113 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 114 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java diff --git a/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java b/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java new file mode 100644 index 00000000000..1e5a20f88fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.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.t; + +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author spjspj + */ +public class TraverseTheOutlands extends CardImpl { + + public TraverseTheOutlands(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); + + // Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle your library. + this.getSpellAbility().addEffect(new TraverseTheOutlandsEffect()); + } + + public TraverseTheOutlands(final TraverseTheOutlands card) { + super(card); + } + + @Override + public TraverseTheOutlands copy() { + return new TraverseTheOutlands(this); + } +} + +class TraverseTheOutlandsEffect extends OneShotEffect { + + public TraverseTheOutlandsEffect() { + super(Outcome.Benefit); + this.staticText = "Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle your library."; + } + + public TraverseTheOutlandsEffect(final TraverseTheOutlandsEffect effect) { + super(effect); + } + + @Override + public TraverseTheOutlandsEffect copy() { + return new TraverseTheOutlandsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + FilterLandPermanent filter = new FilterLandPermanent(); + filter.add(new ControllerPredicate(TargetController.YOU)); + + List creatures = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, controller.getId(), game); + int amount = 0; + for (Permanent creature : creatures) { + int power = creature.getPower().getValue(); + if (amount < power) { + amount = power; + } + } + + TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_BASIC_LAND_CARD); + if (controller.searchLibrary(target, game)) { + controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); + } + controller.shuffleLibrary(source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index fccef4be601..55ba042ce5c 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -50,6 +50,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); + cards.add(new SetCardInfo("Traverse the Outlands", 34, Rarity.RARE, mage.cards.t.TraverseTheOutlands.class)); cards.add(new SetCardInfo("Wasitora, Nekoru Queen", 49, Rarity.MYTHIC, mage.cards.w.WasitoraNekoruQueen.class)); } From 7d2c4b9ab3281be57a73e17a7c67ed9371adbcec Mon Sep 17 00:00:00 2001 From: spjspj Date: Fri, 11 Aug 2017 17:28:03 +1000 Subject: [PATCH 14/28] Implement Teferi's Protection (C17) --- .../src/mage/cards/t/TeferisProtection.java | 139 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 140 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TeferisProtection.java diff --git a/Mage.Sets/src/mage/cards/t/TeferisProtection.java b/Mage.Sets/src/mage/cards/t/TeferisProtection.java new file mode 100644 index 00000000000..1c606c02f2d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TeferisProtection.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.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.NameACardEffect; +import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; +import mage.abilities.effects.common.continuous.LifeTotalCantChangeControllerEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterObject; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class TeferisProtection extends CardImpl { + + public TeferisProtection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Until your next turn, your life total can't change, and you have protection from everything. All permanents you control phase out. (While they're phased out, they're treated as though they don't exist. They phase in before you untap during your untap step.) + this.getSpellAbility().addEffect(new LifeTotalCantChangeControllerEffect(Duration.UntilYourNextTurn)); + this.getSpellAbility().addEffect(new TeferisProtectionEffect()); + this.getSpellAbility().addEffect(new TeferisProtectionPhaseOutEffect()); + + // Exile Teferi's Protection. + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + public TeferisProtection(final TeferisProtection card) { + super(card); + } + + @Override + public TeferisProtection copy() { + return new TeferisProtection(this); + } +} + +class TeferisProtectionEffect extends OneShotEffect { + + public TeferisProtectionEffect() { + super(Outcome.Protect); + staticText = "

You have protection from everything(You can't be targeted, dealt damage, or enchanted by anything.)"; + } + + public TeferisProtectionEffect(final TeferisProtectionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + String cardName = (String) game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY); + if (controller != null) { + FilterObject filter = new FilterObject("the name [everything]"); + filter.add(new NamePredicate("everything")); + ContinuousEffect effect = new GainAbilityControllerEffect(new ProtectionAbility(filter), Duration.Custom); + game.addEffect(effect, source); + return true; + } + return false; + } + + @Override + public TeferisProtectionEffect copy() { + return new TeferisProtectionEffect(this); + } +} + +class TeferisProtectionPhaseOutEffect extends OneShotEffect { + + private FilterControlledPermanent permanentsYouControl = new FilterControlledPermanent("all permanents you control"); + + public TeferisProtectionPhaseOutEffect() { + super(Outcome.Benefit); + this.staticText = "All permanents you control phase out. (While they're phased out, they're treated as though they don't exist. They phase in before you untap during your untap step.)"; + } + + public TeferisProtectionPhaseOutEffect(final TeferisProtectionPhaseOutEffect effect) { + super(effect); + } + + @Override + public TeferisProtectionPhaseOutEffect copy() { + return new TeferisProtectionPhaseOutEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(permanentsYouControl, controller.getId(), game)) { + permanent.phaseOut(game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 55ba042ce5c..ee2da1568b9 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -50,6 +50,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); + cards.add(new SetCardInfo("Teferi's Protection", 8, Rarity.RARE, mage.cards.t.TeferisProtection.class)); cards.add(new SetCardInfo("Traverse the Outlands", 34, Rarity.RARE, mage.cards.t.TraverseTheOutlands.class)); cards.add(new SetCardInfo("Wasitora, Nekoru Queen", 49, Rarity.MYTHIC, mage.cards.w.WasitoraNekoruQueen.class)); From 10159cd157cde83e1eb354be588ce9cda9a5fd3b Mon Sep 17 00:00:00 2001 From: spjspj Date: Fri, 11 Aug 2017 19:56:25 +1000 Subject: [PATCH 15/28] Implement Taigam, Sidisi's Hand (C17) --- .../src/mage/cards/t/TaigamSidisisHand.java | 180 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 181 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TaigamSidisisHand.java diff --git a/Mage.Sets/src/mage/cards/t/TaigamSidisisHand.java b/Mage.Sets/src/mage/cards/t/TaigamSidisisHand.java new file mode 100644 index 00000000000..a80ef013ffc --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TaigamSidisisHand.java @@ -0,0 +1,180 @@ +/* + * 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.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SkipDrawStepEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author spjspj + */ +public class TaigamSidisisHand extends CardImpl { + + public TaigamSidisisHand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add("Human"); + this.subtype.add("Wizard"); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Skip your draw step. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SkipDrawStepEffect())); + + // At the beginning of your upkeep, look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TaigamSidisisHandDrawEffect(), TargetController.YOU, false)); + + // {B}, {T}, Exile X cards from your graveyard: Target creature gets -X/-X until end of turn. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TaigamSidisisHandEffect(), new ManaCostsImpl("{B}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, new FilterCard("cards from your graveyard")))); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public TaigamSidisisHand(final TaigamSidisisHand card) { + super(card); + } + + @Override + public TaigamSidisisHand copy() { + return new TaigamSidisisHand(this); + } +} + +class TaigamSidisisHandEffect extends OneShotEffect { + + public TaigamSidisisHandEffect() { + super(Outcome.Benefit); + this.staticText = "creature gets -X/-X until end of turn"; + } + + public TaigamSidisisHandEffect(final TaigamSidisisHandEffect effect) { + super(effect); + } + + @Override + public TaigamSidisisHandEffect copy() { + return new TaigamSidisisHandEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); + + if (targetCreature != null) { + int amount = 0; + for (Cost cost : source.getCosts()) { + if (cost instanceof ExileFromGraveCost) { + amount = ((ExileFromGraveCost) cost).getExiledCards().size(); + ContinuousEffect effect = new BoostTargetEffect(-amount, -amount, Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(source.getTargets().getFirstTarget())); + game.addEffect(effect, source); + } + } + } + } + return false; + } +} + +class TaigamSidisisHandDrawEffect extends OneShotEffect { + + public TaigamSidisisHandDrawEffect() { + super(Outcome.DrawCard); + this.staticText = "Look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard"; + } + + public TaigamSidisisHandDrawEffect(final TaigamSidisisHandDrawEffect effect) { + super(effect); + } + + @Override + public TaigamSidisisHandDrawEffect copy() { + return new TaigamSidisisHandDrawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + if (controller != null) { + Cards cards = new CardsImpl(); + cards.addAll(controller.getLibrary().getTopCards(game, 3)); + if (!cards.isEmpty()) { + controller.lookAtCards("Taigam, Sidisi's Hand", cards, game); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to put in your hand")); + if (controller.choose(Outcome.Benefit, cards, target, game)) { + Card card = cards.get(target.getFirstTarget(), game); + if (card != null) { + controller.moveCards(card, Zone.HAND, source, game); + cards.remove(card); + } + } + controller.moveCards(cards, Zone.GRAVEYARD, source, game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index ee2da1568b9..e37371c2806 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -50,6 +50,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); + cards.add(new SetCardInfo("Taigam, Sidisi's Hand", 47, Rarity.RARE, mage.cards.t.TaigamSidisisHand.class)); cards.add(new SetCardInfo("Teferi's Protection", 8, Rarity.RARE, mage.cards.t.TeferisProtection.class)); cards.add(new SetCardInfo("Traverse the Outlands", 34, Rarity.RARE, mage.cards.t.TraverseTheOutlands.class)); cards.add(new SetCardInfo("Wasitora, Nekoru Queen", 49, Rarity.MYTHIC, mage.cards.w.WasitoraNekoruQueen.class)); From 04037a6d700870cb144dc02085805d700aace421 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sat, 12 Aug 2017 01:12:30 +1000 Subject: [PATCH 16/28] Implement Scalelord Reckoner (C17) --- .../src/mage/cards/s/ScalelordReckoner.java | 127 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 128 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/ScalelordReckoner.java diff --git a/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java b/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java new file mode 100644 index 00000000000..4426c44be61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java @@ -0,0 +1,127 @@ +/* + * 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.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +/** + * + * @author spjspj + */ +public class ScalelordReckoner extends CardImpl { + + public ScalelordReckoner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); + + this.subtype.add("Dragon"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls. + this.addAbility(new ScalelardReckonerTriggeredAbility(new DestroyTargetEffect())); + } + + public ScalelordReckoner(final ScalelordReckoner card) { + super(card); + } + + @Override + public ScalelordReckoner copy() { + return new ScalelordReckoner(this); + } +} + +class ScalelardReckonerTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Dragon creature you control"); + + static { + filter.add(new SubtypePredicate(SubType.DRAGON)); + } + + public ScalelardReckonerTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, new DestroyTargetEffect(), false); + } + + public ScalelardReckonerTriggeredAbility(final ScalelardReckonerTriggeredAbility ability) { + super(ability); + } + + @Override + public ScalelardReckonerTriggeredAbility copy() { + return new ScalelardReckonerTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.TARGETED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (game.getOpponents(this.controllerId).contains(event.getPlayerId())) { + Permanent creature = game.getPermanent(event.getTargetId()); + if (creature != null && filter.match(creature, getSourceId(), getControllerId(), game)) { + FilterNonlandPermanent filter = new FilterNonlandPermanent("nonland permanent that player controls"); + filter.add(new ControllerIdPredicate(event.getPlayerId())); + // filter.setMessage("nonland permanent controlled by " + game.getPlayer(event.getTargetId()).getLogName()); + this.getTargets().clear(); + this.addTarget(new TargetPermanent(filter)); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls."; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index e37371c2806..19f7ddeb0c3 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -49,6 +49,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); + cards.add(new SetCardInfo("Scalelord Reckoner", 6, Rarity.RARE, mage.cards.s.ScalelordReckoner.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); cards.add(new SetCardInfo("Taigam, Sidisi's Hand", 47, Rarity.RARE, mage.cards.t.TaigamSidisisHand.class)); cards.add(new SetCardInfo("Teferi's Protection", 8, Rarity.RARE, mage.cards.t.TeferisProtection.class)); From cfdf83929e1ffd8415fb9e6a101dfe0b0ead7b16 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 11 Aug 2017 18:01:57 -0400 Subject: [PATCH 17/28] Implemented Chain of Acid, Chain of Silence, Chain of Smog and Chain Stasis (#3796) * Implement Epicenter * Implement Infested Roothold * Implement Brown Ouphe * Implement Rust * Implement Ouphe Vandals * Implement Fatal Attraction * Implement Fighting Chance * Implement Arcbound Fiend * Implement Carry Away * Implement Caverns of Despair * Implement Amulet of Unmaking * Implement Aphetto Exterminator * Implement Balduvian Conjurer, Barrin's Unmaking, Each Mana Battery, Blood Hound and Boa Constrictor * Implement Bola Warrior, Brass Talon Chimera, Briar Patch and Brine Shaman * Implemented Call to Arms, Goham Djinn, Halam Djinn, Ruham Djinn, Sulam Djinn, Zanam Djinn, Heroic Defiance and Tsabo's Assassin, updated Barrin's Unmaking and Epicenter * Implement Brown Ouphe * Implemented Break Open * Implemented Carrion * Implemented Cephalid Pathmage * Implemented Cephalid Inkshrouder * Implemented Ceremonial Guard * Implemented Chain of Acid, Chain of Silence, and Chain of Smog * Implemented Chain of Acid, Chain of Silence, and Chain of Smog * Implemented Chain Stasis --- Mage.Sets/src/mage/cards/c/ChainOfAcid.java | 120 ++++++++++++++++ .../src/mage/cards/c/ChainOfSilence.java | 132 ++++++++++++++++++ Mage.Sets/src/mage/cards/c/ChainOfSmog.java | 107 ++++++++++++++ Mage.Sets/src/mage/cards/c/ChainStasis.java | 131 +++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + Mage.Sets/src/mage/sets/Onslaught.java | 3 + 6 files changed, 494 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/ChainOfAcid.java create mode 100644 Mage.Sets/src/mage/cards/c/ChainOfSilence.java create mode 100644 Mage.Sets/src/mage/cards/c/ChainOfSmog.java create mode 100644 Mage.Sets/src/mage/cards/c/ChainStasis.java diff --git a/Mage.Sets/src/mage/cards/c/ChainOfAcid.java b/Mage.Sets/src/mage/cards/c/ChainOfAcid.java new file mode 100644 index 00000000000..e7ae7f47c8b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChainOfAcid.java @@ -0,0 +1,120 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class ChainOfAcid extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.CREATURE))); + } + + public ChainOfAcid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // Destroy target noncreature permanent. Then that permanent's controller may copy this spell and may choose a new target for that copy. + this.getSpellAbility().addEffect(new ChainOfAcidEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + public ChainOfAcid(final ChainOfAcid card) { + super(card); + } + + @Override + public ChainOfAcid copy() { + return new ChainOfAcid(this); + } +} + +class ChainOfAcidEffect extends OneShotEffect { + + ChainOfAcidEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy target noncreature permanent. Then that permanent's controller may copy this spell and may choose a new target for that copy."; + } + + ChainOfAcidEffect(final ChainOfAcidEffect effect) { + super(effect); + } + + @Override + public ChainOfAcidEffect copy() { + return new ChainOfAcidEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + UUID targetId = source.getFirstTarget(); + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + Player affectedPlayer = game.getPlayer(permanent.getControllerId()); + if (affectedPlayer != null) { + Effect effect = new DestroyTargetEffect(); + effect.setTargetPointer(new FixedTarget(targetId)); + effect.apply(game, source); + if (affectedPlayer.chooseUse(Outcome.Copy, "Copy the spell?", source, game)) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell != null) { + spell.createCopyOnStack(game, source, affectedPlayer.getId(), true); + game.informPlayers(affectedPlayer.getLogName() + " copies " + spell.getName() + '.'); + } + } + return true; + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChainOfSilence.java b/Mage.Sets/src/mage/cards/c/ChainOfSilence.java new file mode 100644 index 00000000000..d2780688ddb --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChainOfSilence.java @@ -0,0 +1,132 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +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.filter.common.FilterControlledLandPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public class ChainOfSilence extends CardImpl { + + public ChainOfSilence(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + + // Prevent all damage target creature would deal this turn. That creature's controller may sacrifice a land. If the player does, he or she may copy this spell and may choose a new target for that copy. + this.getSpellAbility().addEffect(new ChainOfSilenceEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + public ChainOfSilence(final ChainOfSilence card) { + super(card); + } + + @Override + public ChainOfSilence copy() { + return new ChainOfSilence(this); + } +} +class ChainOfSilenceEffect extends OneShotEffect { + + public ChainOfSilenceEffect() { + super(Outcome.PreventDamage); + } + + public ChainOfSilenceEffect(final ChainOfSilenceEffect effect) { + super(effect); + } + + @Override + public ChainOfSilenceEffect copy() { + return new ChainOfSilenceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + ContinuousEffect effect = new PreventDamageByTargetEffect(Duration.EndOfTurn); + game.addEffect(effect, source); + Player player = game.getPlayer(permanent.getControllerId()); + TargetControlledPermanent target = new TargetControlledPermanent(0, 1, new FilterControlledLandPermanent("a land to sacrifice (to be able to copy " + sourceObject.getName() + ')'), true); + if (player.chooseTarget(Outcome.Sacrifice, target, source, game)) { + Permanent land = game.getPermanent(target.getFirstTarget()); + if (land != null && land.sacrifice(source.getSourceId(), game)) { + if (player.chooseUse(outcome, "Copy the spell?", source, game)) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell != null) { + StackObject newStackObject = spell.createCopyOnStack(game, source, player.getId(), true); + if (newStackObject != null && newStackObject instanceof Spell) { + String activateMessage = ((Spell) newStackObject).getActivatedMessage(game); + if (activateMessage.startsWith(" casts ")) { + activateMessage = activateMessage.substring(6); + } + game.informPlayers(player.getLogName() + ' ' + activateMessage); + } + } + } + } + } + return true; + } + + return false; + } + + @Override + public String getText(Mode mode) { + return "Prevent all damage target creature would deal this turn. That creature's controller may sacrifice a land. If the player does, he or she may copy this spell and may choose a new target for that copy"; + } + +} diff --git a/Mage.Sets/src/mage/cards/c/ChainOfSmog.java b/Mage.Sets/src/mage/cards/c/ChainOfSmog.java new file mode 100644 index 00000000000..5f6d76f0e4d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChainOfSmog.java @@ -0,0 +1,107 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class ChainOfSmog extends CardImpl { + + public ChainOfSmog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Target player discards two cards. That player may copy this spell and may choose a new target for that copy. + this.getSpellAbility().addEffect(new ChainOfSmogEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public ChainOfSmog(final ChainOfSmog card) { + super(card); + } + + @Override + public ChainOfSmog copy() { + return new ChainOfSmog(this); + } +} + +class ChainOfSmogEffect extends OneShotEffect { + + ChainOfSmogEffect() { + super(Outcome.Discard); + this.staticText = "Target player discards two cards. That player may copy this spell and may choose a new target for that copy."; + } + + ChainOfSmogEffect(final ChainOfSmogEffect effect) { + super(effect); + } + + @Override + public ChainOfSmogEffect copy() { + return new ChainOfSmogEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + UUID targetId = source.getFirstTarget(); + Player affectedPlayer = game.getPlayer(targetId); + if (affectedPlayer != null) { + Effect effect = new DiscardTargetEffect(2); + effect.setTargetPointer(new FixedTarget(targetId)); + effect.apply(game, source); + if (affectedPlayer.chooseUse(Outcome.Copy, "Copy the spell?", source, game)) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell != null) { + spell.createCopyOnStack(game, source, affectedPlayer.getId(), true); + game.informPlayers(affectedPlayer.getLogName() + " copies " + spell.getName() + '.'); + } + } + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChainStasis.java b/Mage.Sets/src/mage/cards/c/ChainStasis.java new file mode 100644 index 00000000000..4db9cb1646d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChainStasis.java @@ -0,0 +1,131 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MayTapOrUntapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class ChainStasis extends CardImpl { + + public ChainStasis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // You may tap or untap target creature. Then that creature's controller may pay {2}{U}. If the player does, he or she may copy this spell and may choose a new target for that copy. + this.getSpellAbility().addEffect(new ChainStasisEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + public ChainStasis(final ChainStasis card) { + super(card); + } + + @Override + public ChainStasis copy() { + return new ChainStasis(this); + } +} + +class ChainStasisEffect extends OneShotEffect { + + public ChainStasisEffect() { + super(Outcome.Benefit); + } + + public ChainStasisEffect(final ChainStasisEffect effect) { + super(effect); + } + + @Override + public ChainStasisEffect copy() { + return new ChainStasisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + Effect effect = new MayTapOrUntapTargetEffect(); + effect.setTargetPointer(new FixedTarget(source.getFirstTarget())); + effect.apply(game, source); + Player player = game.getPlayer(permanent.getControllerId()); + Cost cost = new ManaCostsImpl("{2}{U}"); + if (cost.pay(source, game, player.getId(), controller.getId(), false)) { + if (player.chooseUse(outcome, "Copy the spell?", source, game)) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell != null) { + StackObject newStackObject = spell.createCopyOnStack(game, source, player.getId(), true); + if (newStackObject != null && newStackObject instanceof Spell) { + String activateMessage = ((Spell) newStackObject).getActivatedMessage(game); + if (activateMessage.startsWith(" casts ")) { + activateMessage = activateMessage.substring(6); + } + game.informPlayers(player.getLogName() + ' ' + activateMessage); + } + } + } + } + return true; + } + + return false; + } + + @Override + public String getText(Mode mode + ) { + return "You may tap or untap target creature. Then that creature's controller may pay {2}{U}. If the player does, he or she may copy this spell and may choose a new target for that copy"; + } + +} diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 5c5ec0f9b15..2a39d1d41d6 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -91,6 +91,7 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Castle Sengir", 138, Rarity.UNCOMMON, mage.cards.c.CastleSengir.class)); cards.add(new SetCardInfo("Cemetery Gate", 4, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cemetery Gate", 5, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chain Stasis", 28, Rarity.RARE, mage.cards.c.ChainStasis.class)); cards.add(new SetCardInfo("Chandler", 88, Rarity.COMMON, mage.cards.c.Chandler.class)); cards.add(new SetCardInfo("Clockwork Gnomes", 127, Rarity.COMMON, mage.cards.c.ClockworkGnomes.class)); cards.add(new SetCardInfo("Coral Reef", 29, Rarity.COMMON, mage.cards.c.CoralReef.class)); diff --git a/Mage.Sets/src/mage/sets/Onslaught.java b/Mage.Sets/src/mage/sets/Onslaught.java index 3437ea31a94..80f9e9d99a9 100644 --- a/Mage.Sets/src/mage/sets/Onslaught.java +++ b/Mage.Sets/src/mage/sets/Onslaught.java @@ -65,7 +65,10 @@ public class Onslaught extends ExpansionSet { cards.add(new SetCardInfo("Catapult Master", 10, Rarity.RARE, mage.cards.c.CatapultMaster.class)); cards.add(new SetCardInfo("Catapult Squad", 11, Rarity.UNCOMMON, mage.cards.c.CatapultSquad.class)); cards.add(new SetCardInfo("Centaur Glade", 251, Rarity.UNCOMMON, mage.cards.c.CentaurGlade.class)); + cards.add(new SetCardInfo("Chain of Acid", 252, Rarity.UNCOMMON, mage.cards.c.ChainOfAcid.class)); cards.add(new SetCardInfo("Chain of Plasma", 193, Rarity.UNCOMMON, mage.cards.c.ChainOfPlasma.class)); + cards.add(new SetCardInfo("Chain of Silence", 12, Rarity.UNCOMMON, mage.cards.c.ChainOfSilence.class)); + cards.add(new SetCardInfo("Chain of Smog", 132, Rarity.UNCOMMON, mage.cards.c.ChainOfSmog.class)); cards.add(new SetCardInfo("Chain of Vapor", 73, Rarity.UNCOMMON, mage.cards.c.ChainOfVapor.class)); cards.add(new SetCardInfo("Charging Slateback", 194, Rarity.COMMON, mage.cards.c.ChargingSlateback.class)); cards.add(new SetCardInfo("Choking Tethers", 74, Rarity.COMMON, mage.cards.c.ChokingTethers.class)); From c83ad3a4ed8a24c98feb171a4c7ed6637558d952 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sat, 12 Aug 2017 17:05:33 +1000 Subject: [PATCH 18/28] Implement Patron of the Vein / Qasali Slingers (C17) --- .../src/mage/cards/p/PatronOfTheVein.java | 189 ++++++++++++++++++ .../src/mage/cards/q/QasaliSlingers.java | 116 +++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 2 + 3 files changed, 307 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PatronOfTheVein.java create mode 100644 Mage.Sets/src/mage/cards/q/QasaliSlingers.java diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java new file mode 100644 index 00000000000..110c064487d --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -0,0 +1,189 @@ +/* + * 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.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlyingAbility; +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.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent.EventType; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author spjspj + */ +public class PatronOfTheVein extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public PatronOfTheVein(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add("Vampire"); + this.subtype.add("Shaman"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Patron of the Vein enters the battlefield, destroy target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), false); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + + // Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control. + Ability ability2 = new PatronOfTheVeinCreatureDiesTriggeredAbility(); + this.addAbility(ability2); + } + + public PatronOfTheVein(final PatronOfTheVein card) { + super(card); + } + + @Override + public PatronOfTheVein copy() { + return new PatronOfTheVein(this); + } +} + +class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { + + public PatronOfTheVeinCreatureDiesTriggeredAbility() { + super(Zone.BATTLEFIELD, new PatronOfTheVeinExileCreatureEffect(), false); + } + + public PatronOfTheVeinCreatureDiesTriggeredAbility(final PatronOfTheVeinCreatureDiesTriggeredAbility ability) { + super(ability); + } + + @Override + public PatronOfTheVeinCreatureDiesTriggeredAbility copy() { + return new PatronOfTheVeinCreatureDiesTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((ZoneChangeEvent) event).isDiesEvent()) { + if (game.getOpponents(this.controllerId).contains(event.getPlayerId())) { + Card creature = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (creature != null && creature.isCreature()) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(creature.getId())); + } + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control"; + } +} + +class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(new SubtypePredicate(SubType.VAMPIRE)); + } + + public PatronOfTheVeinExileCreatureEffect() { + super(Outcome.Benefit); + this.staticText = "exile it and put a +1/+1 counter on each Vampire you control"; + } + + public PatronOfTheVeinExileCreatureEffect(final PatronOfTheVeinExileCreatureEffect effect) { + super(effect); + } + + @Override + public PatronOfTheVeinExileCreatureEffect copy() { + return new PatronOfTheVeinExileCreatureEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); + + if (card != null) { + Effect effect = new ExileTargetEffect(); + effect.setTargetPointer(new FixedTarget(card.getId())); + effect.apply(game, source); + } + + for (Permanent permanent : game.getState().getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + game.informPlayers(sourceObject.getName() + ": Put a +1/+1 counter on " + permanent.getLogName()); + } + return true; + + } +} diff --git a/Mage.Sets/src/mage/cards/q/QasaliSlingers.java b/Mage.Sets/src/mage/cards/q/QasaliSlingers.java new file mode 100644 index 00000000000..90a8d7a4fdf --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QasaliSlingers.java @@ -0,0 +1,116 @@ +/* + * 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.q; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +/** + * + * @author spjspj + */ +public class QasaliSlingers extends CardImpl { + + public QasaliSlingers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add("Cat"); + this.subtype.add("Warrior"); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Qasali Slingers or another Cat enters the battlefield under your control, you may destroy target artifact or enchantment. + this.addAbility(new QasaliSlingersTriggeredAbility()); + } + + public QasaliSlingers(final QasaliSlingers card) { + super(card); + } + + @Override + public QasaliSlingers copy() { + return new QasaliSlingers(this); + } +} + +class QasaliSlingersTriggeredAbility extends TriggeredAbilityImpl { + + public QasaliSlingersTriggeredAbility() { + super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); + this.addTarget(new TargetPermanent(StaticFilters.ARTIFACT_OR_ENCHANTMENT_PERMANENT)); + } + + public QasaliSlingersTriggeredAbility(final QasaliSlingersTriggeredAbility ability) { + super(ability); + } + + @Override + public QasaliSlingersTriggeredAbility copy() { + return new QasaliSlingersTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + if (permanent.getId().equals(this.getSourceId())) { + return true; + } + if (permanent.hasSubtype(SubType.CAT, game) && permanent.getControllerId().equals(this.getControllerId())) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} or another Cat enters the battlefield under your control, you may destroy target artifact or enchantment."; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 19f7ddeb0c3..cae501ca3b6 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -48,6 +48,8 @@ public class Commander2017 extends ExpansionSet { this.blockName = "Command Zone"; cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); + cards.add(new SetCardInfo("Patron of the Vein", 20, Rarity.RARE, mage.cards.p.PatronOfTheVein.class)); + cards.add(new SetCardInfo("Qasali Slingers", 33, Rarity.RARE, mage.cards.q.QasaliSlingers.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Scalelord Reckoner", 6, Rarity.RARE, mage.cards.s.ScalelordReckoner.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); From f41effe97967d6e843c6aa23606c49ca0da5ff46 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sat, 12 Aug 2017 17:56:55 +1000 Subject: [PATCH 19/28] Implement Nazahn, Revered Bladesmith (C17) --- .../mage/cards/n/NazahnReveredBladesmith.java | 146 ++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + ...LibraryPutInHandOrOnBattlefieldEffect.java | 159 ++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java diff --git a/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java b/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java new file mode 100644 index 00000000000..7936a680836 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.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.n; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandOrOnBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.permanent.EquippedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author spjspj + */ +public class NazahnReveredBladesmith extends CardImpl { + + private static final FilterControlledCreaturePermanent equippedFilter = new FilterControlledCreaturePermanent("equipped creatures you control"); + + static { + equippedFilter.add(new EquippedPredicate()); + equippedFilter.add(new ControllerPredicate(TargetController.YOU)); + } + + private static final FilterCard filter = new FilterCard("Equipment card"); + + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + filter.add(new SubtypePredicate(SubType.EQUIPMENT)); + } + + public NazahnReveredBladesmith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{W}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add("Cat"); + this.subtype.add("Artificer"); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // When Nazahn, Revered Bladesmith enters the battlefield, search your library for an Equipment card and reveal it. If you reveal a card named Hammer of Nazahn this way, put it onto the battlefield. Otherwise, put that card into your hand. Then shuffle your library. + TargetCardInLibrary target = new TargetCardInLibrary(1, 1, filter); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandOrOnBattlefieldEffect(target, true, true, "Hammer of Nazahn"), true)); + + // Whenever an equipped creature you control attacks, you may tap target creature defending player controls. + Ability ability = new AttacksCreatureYouControlTriggeredAbility(new NazahnTapEffect(), false, equippedFilter, true); + ability.addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature defending player controls"))); + this.addAbility(ability); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof AttacksCreatureYouControlTriggeredAbility) { + FilterCreaturePermanent filterDefender = new FilterCreaturePermanent("creature defending player controls"); + for (Effect effect : ability.getEffects()) { + if (effect instanceof NazahnTapEffect) { + filterDefender.add(new ControllerIdPredicate(game.getCombat().getDefendingPlayerId(effect.getTargetPointer().getFirst(game, ability), game))); + break; + } + } + ability.getTargets().clear(); + TargetCreaturePermanent target = new TargetCreaturePermanent(filterDefender); + ability.addTarget(target); + } + } + + public NazahnReveredBladesmith(final NazahnReveredBladesmith card) { + super(card); + } + + @Override + public NazahnReveredBladesmith copy() { + return new NazahnReveredBladesmith(this); + } +} + +class NazahnTapEffect extends TapTargetEffect { + + NazahnTapEffect() { + super(); + } + + NazahnTapEffect(final NazahnTapEffect effect) { + super(effect); + } + + @Override + public NazahnTapEffect copy() { + return new NazahnTapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + permanent.tap(game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index cae501ca3b6..e1182f41149 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -47,6 +47,7 @@ public class Commander2017 extends ExpansionSet { super("Commander 2017 Edition", "C17", ExpansionSet.buildDate(2017, 8, 25), SetType.SUPPLEMENTAL); this.blockName = "Command Zone"; + cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Patron of the Vein", 20, Rarity.RARE, mage.cards.p.PatronOfTheVein.class)); cards.add(new SetCardInfo("Qasali Slingers", 33, Rarity.RARE, mage.cards.q.QasaliSlingers.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java new file mode 100644 index 00000000000..4366cd0c8c8 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java @@ -0,0 +1,159 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.abilities.effects.common.search; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.SearchEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.util.CardUtil; + +/** + * + * @author LokiX, BetaSteward_at_googlemail.com (spjspj) + */ +public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect { + + private boolean revealCards = false; + private boolean forceShuffle; + private String rulePrefix; + private String nameToPutOnBattlefield = null; + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, String nameToPutOnBattlefield) { + this(target, false, true, nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, String nameToPutOnBattlefield) { + this(target, revealCards, true, nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, boolean forceShuffle, String nameToPutOnBattlefield) { + this(target, revealCards, forceShuffle, "search your library for ", nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, boolean forceShuffle, String rulePrefix, String nameToPutOnBattlefield) { + super(target, Outcome.DrawCard); + this.revealCards = revealCards; + this.forceShuffle = forceShuffle; + this.rulePrefix = rulePrefix; + this.nameToPutOnBattlefield = nameToPutOnBattlefield; + setText(); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(final SearchLibraryPutInHandOrOnBattlefieldEffect effect) { + super(effect); + this.revealCards = effect.revealCards; + this.forceShuffle = effect.forceShuffle; + this.rulePrefix = effect.rulePrefix; + this.nameToPutOnBattlefield = effect.nameToPutOnBattlefield; + } + + @Override + public SearchLibraryPutInHandOrOnBattlefieldEffect copy() { + return new SearchLibraryPutInHandOrOnBattlefieldEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + target.clearChosen(); + if (controller.searchLibrary(target, game)) { + if (!target.getTargets().isEmpty()) { + Cards cards = new CardsImpl(); + boolean askToPutOntoBf = false; + Card cardToPutOnBf = null; + for (UUID cardId : target.getTargets()) { + Card card = game.getCard(cardId); + if (card != null) { + if (card.getName().equals(nameToPutOnBattlefield)) { + askToPutOntoBf = true; + cardToPutOnBf = card; + } else { + cards.add(card); + } + } + } + if (askToPutOntoBf && cardToPutOnBf != null) { + if (controller.chooseUse(Outcome.PutCardInPlay, "Put " + cardToPutOnBf.getLogName() + " onto the battlefield instead?", source, game)) { + controller.moveCards(cards, Zone.BATTLEFIELD, source, game); + } else { + controller.moveCards(cards, Zone.HAND, source, game); + } + } else { + controller.moveCards(cards, Zone.HAND, source, game); + } + + if (revealCards) { + String name = "Reveal"; + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null) { + name = sourceCard.getIdName(); + } + controller.revealCards(name, cards, game); + } + } + controller.shuffleLibrary(source, game); + return true; + } + if (forceShuffle) { + controller.shuffleLibrary(source, game); + } + return false; + } + + private void setText() { + StringBuilder sb = new StringBuilder(); + sb.append(rulePrefix); + if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { + sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); + sb.append(target.getTargetName()).append(revealCards ? ", reveal them," : "").append(" and put them into your hand"); + } else { + sb.append("a ").append(target.getTargetName()).append(revealCards ? ", reveal it," : "").append(" and put that card into your hand"); + } + if (nameToPutOnBattlefield != null) { + sb.append(". If you reveal a card named " + nameToPutOnBattlefield + " you may put it onto the battlefield instead"); + } + if (forceShuffle) { + sb.append(". Then shuffle your library"); + } else { + sb.append(". If you do, shuffle your library"); + } + staticText = sb.toString(); + } + +} From 9a2ea73ec03a4487765c51474edfcbc903bf8a96 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Sat, 12 Aug 2017 19:19:35 +0200 Subject: [PATCH 20/28] - added Rat token with Deathtouch --- .../permanent/token/DeathtouchRatToken.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java diff --git a/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java b/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java new file mode 100644 index 00000000000..7f52a9aaaad --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java @@ -0,0 +1,51 @@ +/* +* 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.MageInt; +import mage.abilities.keyword.DeathtouchAbility; +import mage.constants.SubType; + +/** + * + * @author Saga + */ +public class DeathtouchRatToken extends Token { + + public DeathtouchRatToken() { + super("Rat", "1/1 black Rat creature token with deathtouch"); + this.setExpansionSetCodeForImage("C17"); + this.cardType.add(CardType.CREATURE); + this.color.setBlack(true); + this.subtype.add(SubType.RAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.addAbility(DeathtouchAbility.getInstance()); + } +} From b49f46dcf695eb67cb91ea85841818a257cb70ba Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Sat, 12 Aug 2017 19:22:51 +0200 Subject: [PATCH 21/28] - added Bloodsworn Steward - added Hungry Lynx - added both cards to C17 --- .../src/mage/cards/b/BloodswornSteward.java | 84 ++++++++++++++ Mage.Sets/src/mage/cards/h/HungryLynx.java | 108 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 4 +- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/b/BloodswornSteward.java create mode 100644 Mage.Sets/src/mage/cards/h/HungryLynx.java diff --git a/Mage.Sets/src/mage/cards/b/BloodswornSteward.java b/Mage.Sets/src/mage/cards/b/BloodswornSteward.java new file mode 100644 index 00000000000..a4c7aebca38 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodswornSteward.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CommanderPredicate; + +/** + * + * @author Saga + */ +public class BloodswornSteward extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent("Commander creatures"); + static { + filter.add(new CommanderPredicate()); + } + + public BloodswornSteward(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}{R}"); + this.subtype.add(SubType.VAMPIRE, SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Commander creatures you control get +2/+2 and have haste. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 2, Duration.WhileOnBattlefield, filter)); + Effect effect = new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter); + effect.setText("and have haste"); + ability.addEffect(effect); + this.addAbility(ability); + } + + public BloodswornSteward(final BloodswornSteward card) { + super(card); + } + + @Override + public BloodswornSteward copy() { + return new BloodswornSteward(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HungryLynx.java b/Mage.Sets/src/mage/cards/h/HungryLynx.java new file mode 100644 index 00000000000..2b9626fa1fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HungryLynx.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.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.permanent.token.DeathtouchRatToken; +import mage.target.Target; +import mage.target.common.TargetOpponent; + +/** + * + * @author Saga + */ +public class HungryLynx extends CardImpl { + + private final static FilterControlledCreaturePermanent filterCat = new FilterControlledCreaturePermanent("Cats"); + static { + filterCat.add(new SubtypePredicate(SubType.CAT)); + } + + private static final FilterCard filterProRat = new FilterCard("Rats"); + static { + filterProRat.add(new SubtypePredicate(SubType.RAT)); + } + + private final static FilterCreaturePermanent filterRat = new FilterCreaturePermanent("a Rat"); + static { + filterRat.add(new SubtypePredicate(SubType.RAT)); + } + + public HungryLynx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); + this.subtype.add(SubType.CAT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Cats you control have protection from Rats. + Effect effect = new GainAbilityAllEffect(new ProtectionAbility(filterProRat), Duration.WhileOnBattlefield, filterCat); + effect.setText("Cats you control have protection from Rats. (They can't be blocked, targeted, or dealt damage by Rats.)"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch. + Ability ability = new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new CreateTokenTargetEffect(new DeathtouchRatToken()), TargetController.YOU, null, false); + Target target = new TargetOpponent(); + ability.addTarget(target); + this.addAbility(ability); + + // Whenever a Rat dies, put a +1/+1 counter on each Cat you control. + Effect effect2 = new AddCountersAllEffect(CounterType.P1P1.createInstance(), filterCat); + effect2.setText(" put a +1/+1 counter on each Cat you control."); + this.addAbility(new DiesCreatureTriggeredAbility(effect2, false, filterRat)); + } + + public HungryLynx(final HungryLynx card) { + super(card); + } + + @Override + public HungryLynx copy() { + return new HungryLynx(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 5ec6660fc66..cf642f2eacf 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -48,6 +48,8 @@ public class Commander2017 extends ExpansionSet { this.blockName = "Command Zone"; cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); + cards.add(new SetCardInfo("Bloodsworn Steward", 22, Rarity.RARE, mage.cards.b.BloodswornSteward.class)); + cards.add(new SetCardInfo("Hungry Lynx", 31, Rarity.RARE, mage.cards.h.HungryLynx.class)); cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Patron of the Vein", 20, Rarity.RARE, mage.cards.p.PatronOfTheVein.class)); @@ -59,6 +61,6 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Teferi's Protection", 8, Rarity.RARE, mage.cards.t.TeferisProtection.class)); cards.add(new SetCardInfo("Traverse the Outlands", 34, Rarity.RARE, mage.cards.t.TraverseTheOutlands.class)); cards.add(new SetCardInfo("Wasitora, Nekoru Queen", 49, Rarity.MYTHIC, mage.cards.w.WasitoraNekoruQueen.class)); - + } } From 497f6a9342be5324298c0dca5a9ab696ce361fe2 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Sun, 13 Aug 2017 00:20:48 +0200 Subject: [PATCH 22/28] - added Herald's Horn - added Horn to C17 --- Mage.Sets/src/mage/cards/h/HeraldsHorn.java | 127 ++++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 128 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/HeraldsHorn.java diff --git a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java new file mode 100644 index 00000000000..7ea466882b2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java @@ -0,0 +1,127 @@ +/* + * 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.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; +import mage.abilities.common.OnEventTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionAllOfChosenSubtypeEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ChosenSubtypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author Saga + */ +public class HeraldsHorn extends CardImpl { + + public HeraldsHorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + + // As Herald's Horn enters the battlefield, choose a creature type. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseCreatureTypeEffect(Outcome.BoostCreature))); + + // Creature spells you cast of the chosen type cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostReductionAllOfChosenSubtypeEffect(new FilterCreatureCard("Creature spells you cast of the chosen type"), 1))); + + // At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new HeraldsHornEffect(), TargetController.YOU, false)); + } + + public HeraldsHorn(final HeraldsHorn card) { + super(card); + } + + @Override + public HeraldsHorn copy() { + return new HeraldsHorn(this); + } +} + +class HeraldsHornEffect extends OneShotEffect { + + public HeraldsHornEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand"; + } + + public HeraldsHornEffect(final HeraldsHornEffect effect) { + super(effect); + } + + @Override + public HeraldsHornEffect copy() { + return new HeraldsHornEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = game.getObject(source.getSourceId()); + + // Look at the top card of your library. + if (controller.getLibrary().hasCards()) { + Card card = controller.getLibrary().getFromTop(game); + Cards cards = new CardsImpl(card); + controller.lookAtCards(sourceObject.getIdName(), cards, game); + + // If it's a creature card of the chosen type, you may reveal it and put it into your hand. + FilterCreatureCard filter = new FilterCreatureCard("creature card of the chosen type"); + filter.add(new ChosenSubtypePredicate(source.getSourceId())); + String message = "Reveal the top card of your library and put that card into your hand?"; + if (card != null) { + if (filter.match(card, game) && controller.chooseUse(Outcome.Benefit, message, source, game)) { + controller.moveCards(card, Zone.HAND, source, game); + controller.revealCards(sourceObject.getIdName() + " put into hand", cards, game); + } + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index cf642f2eacf..021faa8f64d 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -49,6 +49,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); cards.add(new SetCardInfo("Bloodsworn Steward", 22, Rarity.RARE, mage.cards.b.BloodswornSteward.class)); + cards.add(new SetCardInfo("Herald's Horn", 53, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class)); cards.add(new SetCardInfo("Hungry Lynx", 31, Rarity.RARE, mage.cards.h.HungryLynx.class)); cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); From ce0dded9ca489fcbd2ed6d10b5dceb3038b69053 Mon Sep 17 00:00:00 2001 From: "Saga\\Robert" Date: Sun, 13 Aug 2017 00:24:27 +0200 Subject: [PATCH 23/28] - removed unused imports --- Mage.Sets/src/mage/cards/h/HeraldsHorn.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java index 7ea466882b2..f5e49c19bbb 100644 --- a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java +++ b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java @@ -32,13 +32,10 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; -import mage.abilities.common.OnEventTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseCreatureTypeEffect; import mage.abilities.effects.common.cost.SpellsCostReductionAllOfChosenSubtypeEffect; -import mage.abilities.effects.keyword.ScryEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -51,7 +48,6 @@ import mage.constants.Zone; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.ChosenSubtypePredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; /** From 3ad7c71b42ff50a40d3f7be2ef2988f0e8199b88 Mon Sep 17 00:00:00 2001 From: Saga Date: Sun, 13 Aug 2017 14:37:48 +0200 Subject: [PATCH 24/28] Update HungryLynx.java --- Mage.Sets/src/mage/cards/h/HungryLynx.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/h/HungryLynx.java b/Mage.Sets/src/mage/cards/h/HungryLynx.java index 2b9626fa1fb..492052b883e 100644 --- a/Mage.Sets/src/mage/cards/h/HungryLynx.java +++ b/Mage.Sets/src/mage/cards/h/HungryLynx.java @@ -93,7 +93,7 @@ public class HungryLynx extends CardImpl { // Whenever a Rat dies, put a +1/+1 counter on each Cat you control. Effect effect2 = new AddCountersAllEffect(CounterType.P1P1.createInstance(), filterCat); - effect2.setText(" put a +1/+1 counter on each Cat you control."); + effect2.setText("put a +1/+1 counter on each Cat you control"); this.addAbility(new DiesCreatureTriggeredAbility(effect2, false, filterRat)); } From a39ceae9a1e48cf3b1b21bd28ae1ce1690ac7b39 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 13 Aug 2017 22:59:45 +1000 Subject: [PATCH 25/28] Implement Crimson Honor Guard (C17) --- .../src/mage/cards/c/CrimsonHonorGuard.java | 113 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 114 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java diff --git a/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java b/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java new file mode 100644 index 00000000000..137ec3a0364 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.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.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.CommanderPredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class CrimsonHonorGuard extends CardImpl { + + public CrimsonHonorGuard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add("Vampire"); + this.subtype.add("Knight"); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // At the beginning of each player's end step, Crimson Honor Guard deals 4 damage to that player unless he or she controls a commander. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new CrimsonHonorGuardEffect(), TargetController.ANY, false)); + } + + public CrimsonHonorGuard(final CrimsonHonorGuard card) { + super(card); + } + + @Override + public CrimsonHonorGuard copy() { + return new CrimsonHonorGuard(this); + } +} + +class CrimsonHonorGuardEffect extends OneShotEffect { + + private final static FilterPermanent filter = new FilterPermanent("Commander"); + + static { + filter.add(new CommanderPredicate()); + } + + public CrimsonHonorGuardEffect() { + super(Outcome.Damage); + } + + @Override + public Effect copy() { + return new CrimsonHonorGuardEffect(); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + if (player != null) { + int numCommanders = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + if (numCommanders == 0) { + player.damage(4, source.getSourceId(), game, false, true); + return true; + } + } + return false; + } + + @Override + public String getText(Mode mode) { + return "{this} deals 4 damage to that player unless he or she controls a commander"; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index e1182f41149..7dac0ace2c1 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -47,6 +47,7 @@ public class Commander2017 extends ExpansionSet { super("Commander 2017 Edition", "C17", ExpansionSet.buildDate(2017, 8, 25), SetType.SUPPLEMENTAL); this.blockName = "Command Zone"; + cards.add(new SetCardInfo("Crimson Honor Guard", 23, Rarity.RARE, mage.cards.c.CrimsonHonorGuard.class)); cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); cards.add(new SetCardInfo("Patron of the Vein", 20, Rarity.RARE, mage.cards.p.PatronOfTheVein.class)); From ae95bd6e163ec3be672a6f04d59f163673b412f0 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 13 Aug 2017 23:06:17 +1000 Subject: [PATCH 26/28] Fix --- Mage.Sets/src/mage/sets/Commander2017.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 7dac0ace2c1..83bc927ce1f 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -47,6 +47,7 @@ public class Commander2017 extends ExpansionSet { super("Commander 2017 Edition", "C17", ExpansionSet.buildDate(2017, 8, 25), SetType.SUPPLEMENTAL); this.blockName = "Command Zone"; + cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); cards.add(new SetCardInfo("Crimson Honor Guard", 23, Rarity.RARE, mage.cards.c.CrimsonHonorGuard.class)); cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); From f65876572279190a0e68f1881a99e70286dc9605 Mon Sep 17 00:00:00 2001 From: igoudt Date: Sun, 13 Aug 2017 15:53:39 +0200 Subject: [PATCH 27/28] fixes #3806 --- Mage.Sets/src/mage/cards/p/PistonSledge.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/p/PistonSledge.java b/Mage.Sets/src/mage/cards/p/PistonSledge.java index d0ceccd9c54..3f49583cac3 100644 --- a/Mage.Sets/src/mage/cards/p/PistonSledge.java +++ b/Mage.Sets/src/mage/cards/p/PistonSledge.java @@ -28,7 +28,6 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -42,9 +41,12 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** * * @author Viserion, North @@ -53,6 +55,10 @@ public class PistonSledge extends CardImpl { private static FilterControlledPermanent filter = new FilterControlledPermanent("an artifact"); + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + } + public PistonSledge (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); this.subtype.add("Equipment"); From bae7f154dfa946e4e11810599d90fb9d39005426 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 13 Aug 2017 16:22:18 +0200 Subject: [PATCH 28/28] Changed user disconnect handling. The user in the server is no longer deleted directly after connection problem, disconnect or quit. The user object remains now for 8 minutes so the rejoin not possible after disconnect problem should be solved (I hope so). We will see. Also fixed the problem, that the table panel was not shown, if a player disconected and reconected. --- .../src/main/java/mage/client/MageFrame.java | 22 +++++- .../src/main/java/mage/view/UserView.java | 8 +- .../mage/server/console/ConsolePanel.java | 39 +++++----- Mage.Server/config/config.xml | 2 +- .../main/java/mage/server/MageServerImpl.java | 17 +---- .../src/main/java/mage/server/Session.java | 51 +++++-------- .../main/java/mage/server/SessionManager.java | 50 ++++++------- .../src/main/java/mage/server/User.java | 71 +++++++++++------- .../main/java/mage/server/UserManager.java | 75 ++++++++++++++++--- .../java/mage/server/game/GamesRoomImpl.java | 55 +++++++------- 10 files changed, 220 insertions(+), 170 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 8fa1cdebb40..ec8b0a61d9e 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -1022,8 +1022,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { // Show the tables pane if there wasn't already an active pane MagePane topPanebefore = getTopMost(tablesPane); - if (topPanebefore == null) { - setActive(tablesPane); + setActive(tablesPane); + if (topPanebefore != null && topPanebefore != tablesPane) { + setActive(topPanebefore); } } @@ -1189,7 +1190,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static final long serialVersionUID = -9104885239063142218L; private ImagePanel backgroundPane; - private TablesPane tablesPane; + private final TablesPane tablesPane; // private CollectionViewerPane collectionViewerPane; public void setStatusText(String status) { @@ -1319,7 +1320,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { DownloadPictures.startDownload(null, missingCards); break; case CLIENT_DISCONNECT: - SessionHandler.disconnect(false); + if (SessionHandler.isConnected()) { + endTables(); + SessionHandler.disconnect(false); + } tablesPane.clearChat(); showMessage("You have disconnected"); setWindowTitle(); @@ -1347,6 +1351,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { break; case CLIENT_EXIT: if (SessionHandler.isConnected()) { + endTables(); SessionHandler.disconnect(false); } CardRepository.instance.closeDB(); @@ -1374,6 +1379,15 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } } + private void endTables() { + for (UUID gameId : GAMES.keySet()) { + SessionHandler.quitMatch(gameId); + } + for (UUID draftId : DRAFTS.keySet()) { + SessionHandler.quitDraft(draftId); + } + } + public void changeGUISize() { ImageCaches.flush(); setGUISize(); diff --git a/Mage.Common/src/main/java/mage/view/UserView.java b/Mage.Common/src/main/java/mage/view/UserView.java index 1b4a9d082a2..73a8057075d 100644 --- a/Mage.Common/src/main/java/mage/view/UserView.java +++ b/Mage.Common/src/main/java/mage/view/UserView.java @@ -41,6 +41,7 @@ public class UserView implements Serializable { private final String host; private final String sessionId; private final Date timeConnected; + private final Date lastActivity; private final String gameInfo; private final String userState; private final Date muteChatUntil; @@ -48,11 +49,12 @@ public class UserView implements Serializable { private final String email; private final String userIdStr; - public UserView(String userName, String host, String sessionId, Date timeConnected, String gameInfo, String userState, Date muteChatUntil, String clientVersion, String email, String userIdStr) { + public UserView(String userName, String host, String sessionId, Date timeConnected, Date lastActivity, String gameInfo, String userState, Date muteChatUntil, String clientVersion, String email, String userIdStr) { this.userName = userName; this.host = host; this.sessionId = sessionId; this.timeConnected = timeConnected; + this.lastActivity = lastActivity; this.gameInfo = gameInfo; this.userState = userState; this.muteChatUntil = muteChatUntil; @@ -93,6 +95,10 @@ public class UserView implements Serializable { return timeConnected; } + public Date getLastActivity() { + return lastActivity; + } + public String getEmail() { return email; } diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java index 072e5673e97..b2c9768a2bc 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java @@ -33,22 +33,20 @@ */ package mage.server.console; -import mage.remote.Session; -import mage.view.TableView; -import mage.view.UserView; -import org.apache.log4j.Logger; - -import javax.swing.*; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableRowSorter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; - +import javax.swing.*; import static javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN; import static javax.swing.JTable.AUTO_RESIZE_OFF; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableRowSorter; +import mage.remote.Session; +import mage.view.TableView; +import mage.view.UserView; +import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com @@ -359,13 +357,14 @@ class TableUserModel extends AbstractTableModel { public static final int POS_USER_NAME = 0; public static final int POS_HOST = 1; public static final int POS_TIME_CONNECTED = 2; - public static final int POS_SESSION_ID = 3; - public static final int POS_GAME_INFO = 4; - public static final int POS_USER_STATE = 5; - public static final int POS_CHAT_MUTE = 6; - public static final int POS_CLIENT_VERSION = 7; + public static final int POS_LAST_ACTIVITY = 3; + public static final int POS_SESSION_ID = 4; + public static final int POS_GAME_INFO = 5; + public static final int POS_USER_STATE = 6; + public static final int POS_CHAT_MUTE = 7; + public static final int POS_CLIENT_VERSION = 8; - private final String[] columnNames = new String[]{"User Name", "Host", "Time Connected", "SessionId", "Gameinfo", "User state", "Chat mute", "Client Version"}; + private final String[] columnNames = new String[]{"User Name", "Host", "Time Connected", "Last activity", "SessionId", "Gameinfo", "User state", "Chat mute", "Client Version"}; private UserView[] users = new UserView[0]; private static final DateFormat formatterTime = new SimpleDateFormat("HH:mm:ss"); private static final DateFormat formatterTimeStamp = new SimpleDateFormat("yy-M-dd HH:mm:ss"); @@ -394,6 +393,8 @@ class TableUserModel extends AbstractTableModel { return users[arg0].getHost(); case POS_TIME_CONNECTED: return formatterTime.format(users[arg0].getTimeConnected()); + case POS_LAST_ACTIVITY: + return formatterTime.format(users[arg0].getLastActivity()); case POS_SESSION_ID: return users[arg0].getSessionId(); case POS_GAME_INFO: @@ -544,10 +545,8 @@ class UpdateUsersTask extends SwingWorker> { return true; } for (UserView u1 : previousUsers) { - boolean found = false; for (UserView u2 : usersToCheck) { if (u1.getUserName().equals(u2.getUserName())) { - found = true; String s = u1.getUserName() + ',' + u1.getHost(); if (peopleIps.get(s) == null) { logger.warn("Found new user: " + u1.getUserName() + ',' + u1.getHost()); @@ -561,13 +560,9 @@ class UpdateUsersTask extends SwingWorker> { break; } } - if (!found) { - // some new user replaced old one - return true; - } } // seems nothing has been changed - return false; + return true; } @Override diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index f2ae1f3e4a6..61efe2a6f6a 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -43,7 +43,7 @@ leasePeriod="5000" socketWriteTimeout="10000" maxGameThreads="10" - maxSecondsIdle="600" + maxSecondsIdle="300" minUserNameLength="3" maxUserNameLength="14" invalidUserNamePattern="[^a-z0-9_]" diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index e207bce0d74..a2f346307b1 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -1271,22 +1271,7 @@ public class MageServerImpl implements MageServer { @Override public List execute() throws MageException { - List users = new ArrayList<>(); - for (User user : UserManager.instance.getUsers()) { - users.add(new UserView( - user.getName(), - user.getHost(), - user.getSessionId(), - user.getConnectionTime(), - user.getGameInfo(), - user.getUserState().toString(), - user.getChatLockedUntil(), - user.getClientVersion(), - user.getEmail(), - user.getUserIdStr() - )); - } - return users; + return UserManager.instance.getUserInfoList(); } } diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index e06a5613358..9ffa4121813 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -38,6 +38,7 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.net.UserData; import mage.players.net.UserGroup; +import static mage.server.DisconnectReason.LostConnection; import mage.server.game.GamesRoom; import mage.server.game.GamesRoomManager; import mage.server.util.ConfigSettings; @@ -270,12 +271,13 @@ public class Session { this.isAdmin = true; User user = UserManager.instance.createUser("Admin", host, null).orElse( UserManager.instance.getUserByName("Admin").get()); - UserData adminUserData = UserData.getDefaultUserDataView(); adminUserData.setGroupId(UserGroup.ADMIN.getGroupId()); user.setUserData(adminUserData); if (!UserManager.instance.connectToSession(sessionId, user.getId())) { logger.info("Error connecting Admin!"); + } else { + user.setUserState(User.UserState.Connected); } this.userId = user.getId(); } @@ -329,39 +331,20 @@ public class Session { // because different threads can activate this public void userLostConnection() { - boolean lockSet = false; - try { - if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) { - lockSet = true; - logger.debug("SESSION LOCK SET sessionId: " + sessionId); - } else { - logger.warn("CAN'T GET LOCK - userId: " + userId + " hold count: " + lock.getHoldCount()); - } - Optional _user = UserManager.instance.getUser(userId); - if (!_user.isPresent()) { - return; //user was already disconnected by other thread - } - User user = _user.get(); - if (!user.isConnected()) { - return; - } - if (!user.getSessionId().equals(sessionId)) { - // user already reconnected with another instance - logger.info("OLD SESSION IGNORED - " + user.getName()); - return; - } - // logger.info("LOST CONNECTION - " + user.getName() + " id: " + userId); - UserManager.instance.disconnect(userId, DisconnectReason.LostConnection); - - } catch (InterruptedException ex) { - logger.error("SESSION LOCK lost connection - userId: " + userId, ex); - } finally { - if (lockSet) { - lock.unlock(); - logger.trace("SESSION LOCK UNLOCK sessionId: " + sessionId); - } + Optional _user = UserManager.instance.getUser(userId); + if (!_user.isPresent()) { + return; //user was already disconnected by other thread + } + User user = _user.get(); + if (!user.isConnected()) { + return; + } + if (!user.getSessionId().equals(sessionId)) { + // user already reconnected with another instance + logger.info("OLD SESSION IGNORED - " + user.getName()); + } else { + // logger.info("LOST CONNECTION - " + user.getName() + " id: " + userId); } - } public void kill(DisconnectReason reason) { @@ -397,7 +380,7 @@ public class Session { logger.warn(" - method: " + call.getMethod()); logger.warn(" - cause: " + getBasicCause(ex).toString()); logger.trace("Stack trace:", ex); - userLostConnection(); + SessionManager.instance.disconnect(sessionId, LostConnection); }); } } diff --git a/Mage.Server/src/main/java/mage/server/SessionManager.java b/Mage.Server/src/main/java/mage/server/SessionManager.java index 1b66fce0c13..e32be438972 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/SessionManager.java @@ -125,34 +125,30 @@ public enum SessionManager { public void disconnect(String sessionId, DisconnectReason reason) { Session session = sessions.get(sessionId); if (session != null) { - if (reason != DisconnectReason.AdminDisconnect) { - if (!sessions.containsKey(sessionId)) { - // session was removed meanwhile by another thread so we can return - return; - } - logger.debug("DISCONNECT " + reason.toString() + " - sessionId: " + sessionId); - sessions.remove(sessionId); - switch (reason) { - case Disconnected: // regular session end or wrong client version - if (session.getUserId() != null) { // if wrong client version no userId is set - session.kill(reason); - } - break; - case SessionExpired: // session ends after no reconnect happens in the defined time span - session.kill(reason); - break; - case LostConnection: // user lost connection - session expires countdaoun starts - session.userLostConnection(); - break; - case ConnectingOtherInstance: - break; - default: - logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); - } - } else { - sessions.remove(sessionId); - session.kill(reason); + if (!sessions.containsKey(sessionId)) { + // session was removed meanwhile by another thread so we can return + return; } + logger.debug("DISCONNECT " + reason.toString() + " - sessionId: " + sessionId); + sessions.remove(sessionId); + switch (reason) { + case AdminDisconnect: + session.kill(reason); + break; + case ConnectingOtherInstance: + case Disconnected: // regular session end or wrong client version + UserManager.instance.disconnect(session.getUserId(), reason); + break; + case SessionExpired: // session ends after no reconnect happens in the defined time span + break; + case LostConnection: // user lost connection - session expires countdown starts + session.userLostConnection(); + UserManager.instance.disconnect(session.getUserId(), reason); + break; + default: + logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); + } + } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 388d85bd22e..523a8926714 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -64,7 +64,10 @@ public class User { public enum UserState { - Created, Connected, Disconnected, Reconnected, Expired + Created, // Used if user is created an not connected to the session + Connected, // Used if user is correctly connected + Disconnected, // Used if the user lost connection + Offline // set if the user was disconnected and expired or regularly left XMage. Removed is the user later after some time } private final UUID userId; @@ -164,7 +167,7 @@ public class User { userState = UserState.Connected; logger.trace("USER - created: " + userName + " id: " + userId); } else { - userState = UserState.Reconnected; + userState = UserState.Connected; reconnect(); logger.trace("USER - reconnected: " + userName + " id: " + userId); } @@ -212,23 +215,14 @@ public class User { } public boolean isConnected() { - return userState == UserState.Connected || userState == UserState.Reconnected; + return userState == UserState.Connected; } public String getDisconnectDuration() { long secondsDisconnected = getSecondsDisconnected(); - long secondsLeft; - String sign = ""; - if (secondsDisconnected > (3 * 60)) { - sign = "-"; - secondsLeft = secondsDisconnected - (3 * 60); - } else { - secondsLeft = (3 * 60) - secondsDisconnected; - } - - int minutes = (int) secondsLeft / 60; - int seconds = (int) secondsLeft % 60; - return new StringBuilder(sign).append(Integer.toString(minutes)).append(':').append(seconds > 9 ? seconds : '0' + Integer.toString(seconds)).toString(); + int minutes = (int) secondsDisconnected / 60; + int seconds = (int) secondsDisconnected % 60; + return Integer.toString(minutes) + ':' + (seconds > 9 ? seconds : '0' + Integer.toString(seconds)); } public long getSecondsDisconnected() { @@ -239,6 +233,20 @@ public class User { return connectionTime; } + public Date getLastActivity() { + return lastActivity; + } + + public String getConnectionDuration() { + int minutes = (int) SystemUtil.getDateDiff(connectionTime, new Date(), TimeUnit.SECONDS) / 60; + int hours = 0; + if (minutes > 59) { + hours = (int) minutes / 60; + minutes = minutes - (hours * 60); + } + return Integer.toString(hours) + ":" + (minutes > 9 ? Integer.toString(minutes) : '0' + Integer.toString(minutes)); + } + public void fireCallback(final ClientCallback call) { if (isConnected()) { SessionManager.instance.getSession(sessionId).ifPresent(session @@ -331,19 +339,17 @@ public class User { } lastActivity = new Date(); if (userState == UserState.Disconnected) { // this can happen if user reconnects very fast after disconnect - userState = UserState.Reconnected; + userState = UserState.Connected; } } public boolean isExpired(Date expired) { if (lastActivity.before(expired)) { logger.trace(userName + " is expired!"); - userState = UserState.Expired; return true; } logger.trace("isExpired: User " + userName + " lastActivity: " + lastActivity + " expired: " + expired); return false; - /*userState == UserState.Disconnected && */ } @@ -511,11 +517,15 @@ public class User { tournament++; break; } - - if (!isConnected()) { - tournamentPlayer.setDisconnectInfo(" (discon. " + getDisconnectDuration() + ')'); - } else { - tournamentPlayer.setDisconnectInfo(""); + switch (getUserState()) { + case Disconnected: + tournamentPlayer.setDisconnectInfo(" (discon. " + getDisconnectDuration() + ')'); + break; + case Offline: + tournamentPlayer.setDisconnectInfo(" Offline"); + break; + default: + tournamentPlayer.setDisconnectInfo(""); } } } else { @@ -586,11 +596,18 @@ public class User { return userState; } + public void setUserState(UserState userState) { + this.userState = userState; + } + public String getPingInfo() { - if (isConnected()) { - return pingInfo; - } else { - return " (discon. " + getDisconnectDuration() + ')'; + switch (getUserState()) { + case Disconnected: + return " (discon. " + getDisconnectDuration() + ')'; + case Offline: + return " Offline"; + default: + return pingInfo + " " + getConnectionDuration(); } } diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index f33b3fe0544..0379684bd11 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -33,6 +33,7 @@ import mage.server.User.UserState; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.util.ThreadExecutor; +import mage.view.UserView; import org.apache.log4j.Logger; /** @@ -44,7 +45,12 @@ import org.apache.log4j.Logger; public enum UserManager { instance; + private static final Logger logger = Logger.getLogger(UserManager.class); + protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); + protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor(); + + private List userInfoList = new ArrayList<>(); private static final Logger LOGGER = Logger.getLogger(UserManager.class); @@ -54,6 +60,8 @@ public enum UserManager { UserManager() { expireExecutor.scheduleAtFixedRate(this::checkExpired, 60, 60, TimeUnit.SECONDS); + + userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, 4, 4, TimeUnit.SECONDS); } public Optional createUser(String userName, String host, AuthorizedUser authorizedUser) { @@ -100,10 +108,16 @@ public enum UserManager { } public void disconnect(UUID userId, DisconnectReason reason) { - if (userId != null) { - getUser(userId).ifPresent(user -> user.setSessionId(""));// Session will be set again with new id if user reconnects + Optional user = UserManager.instance.getUser(userId); + if (user.isPresent()) { + user.get().setSessionId(""); + if (reason == DisconnectReason.Disconnected) { + user.get().setUserState(UserState.Offline); + } + } + if (userId != null) { + ChatManager.instance.removeUser(userId, reason); } - ChatManager.instance.removeUser(userId, reason); } public boolean isAdmin(UUID userId) { @@ -148,18 +162,57 @@ public enum UserManager { } /** - * Is the connection lost for more than 3 minutes, the user will be removed - * (within 3 minutes the user can reconnect) + * Is the connection lost for more than 3 minutes, the user will be set to + * offline status. The user will be removed in validity check after 15 + * minutes of no activities + * */ private void checkExpired() { - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.MINUTE, -3); - List usersToCheck = new ArrayList<>(users.values()); - for (User user : usersToCheck) { - if (user.getUserState() != UserState.Expired && user.isExpired(calendar.getTime())) { - removeUser(user.getId(), DisconnectReason.SessionExpired); + Calendar calendarExp = Calendar.getInstance(); + calendarExp.add(Calendar.MINUTE, -3); + Calendar calendarRemove = Calendar.getInstance(); + calendarRemove.add(Calendar.MINUTE, -8); + List toRemove = new ArrayList<>(); + for (User user : users.values()) { + if (user.getUserState() == UserState.Disconnected || user.getUserState() == UserState.Offline + && user.isExpired(calendarExp.getTime())) { + user.setUserState(UserState.Offline); + } + if (user.getUserState() == UserState.Offline && user.isExpired(calendarRemove.getTime())) { + toRemove.add(user); } } + for (User user : toRemove) { + removeUser(user.getId(), DisconnectReason.SessionExpired); + } + } + + /** + * This method recreated the user list that will be send to all clients + * + */ + private void updateUserInfoList() { + List newUserInfoList = new ArrayList<>(); + for (User user : UserManager.instance.getUsers()) { + newUserInfoList.add(new UserView( + user.getName(), + user.getHost(), + user.getSessionId(), + user.getConnectionTime(), + user.getLastActivity(), + user.getGameInfo(), + user.getUserState().toString(), + user.getChatLockedUntil(), + user.getClientVersion(), + user.getEmail(), + user.getUserIdStr() + )); + } + userInfoList = newUserInfoList; + } + + public List getUserInfoList() { + return userInfoList; } public void handleException(Exception ex) { diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java index 4c4f116a2a6..100190f24ae 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -27,6 +27,12 @@ */ package mage.server.game; +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import mage.MageException; import mage.cards.decks.DeckCardLists; import mage.constants.TableState; @@ -48,13 +54,6 @@ import mage.view.TableView; import mage.view.UsersView; import org.apache.log4j.Logger; -import java.io.Serializable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - /** * @author BetaSteward_at_googlemail.com */ @@ -107,26 +106,28 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { matchView = matchList; List users = new ArrayList<>(); for (User user : UserManager.instance.getUsers()) { - try { - users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), - user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(), - user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo(), - user.getUserData().getGeneralRating(), user.getUserData().getConstructedRating(), - user.getUserData().getLimitedRating())); - } catch (Exception ex) { - LOGGER.fatal("User update exception: " + user.getName() + " - " + ex.toString(), ex); - users.add(new UsersView( - (user.getUserData() != null && user.getUserData().getFlagName() != null) ? user.getUserData().getFlagName() : "world", - user.getName() != null ? user.getName() : "", - user.getMatchHistory() != null ? user.getMatchHistory() : "", - user.getMatchQuitRatio(), - user.getTourneyHistory() != null ? user.getTourneyHistory() : "", - user.getTourneyQuitRatio(), - "[exception]", - user.getPingInfo() != null ? user.getPingInfo() : "", - user.getUserData() != null ? user.getUserData().getGeneralRating() : 0, - user.getUserData() != null ? user.getUserData().getConstructedRating() : 0, - user.getUserData() != null ? user.getUserData().getLimitedRating() : 0)); + if (user.getUserState() != User.UserState.Offline && !user.getName().equals("Admin")) { + try { + users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), + user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(), + user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo(), + user.getUserData().getGeneralRating(), user.getUserData().getConstructedRating(), + user.getUserData().getLimitedRating())); + } catch (Exception ex) { + LOGGER.fatal("User update exception: " + user.getName() + " - " + ex.toString(), ex); + users.add(new UsersView( + (user.getUserData() != null && user.getUserData().getFlagName() != null) ? user.getUserData().getFlagName() : "world", + user.getName() != null ? user.getName() : "", + user.getMatchHistory() != null ? user.getMatchHistory() : "", + user.getMatchQuitRatio(), + user.getTourneyHistory() != null ? user.getTourneyHistory() : "", + user.getTourneyQuitRatio(), + "[exception]", + user.getPingInfo() != null ? user.getPingInfo() : "", + user.getUserData() != null ? user.getUserData().getGeneralRating() : 0, + user.getUserData() != null ? user.getUserData().getConstructedRating() : 0, + user.getUserData() != null ? user.getUserData().getLimitedRating() : 0)); + } } }