From 2f6c52f93a695113d3d5b9eacb660b06c9d1f1ae Mon Sep 17 00:00:00 2001 From: spjspj Date: Thu, 17 Aug 2017 00:34:17 +1000 Subject: [PATCH 01/10] Implement Fortunate Few (C17) --- Mage.Sets/src/mage/cards/f/FortunateFew.java | 128 +++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 3 +- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/f/FortunateFew.java diff --git a/Mage.Sets/src/mage/cards/f/FortunateFew.java b/Mage.Sets/src/mage/cards/f/FortunateFew.java new file mode 100644 index 00000000000..771feb72984 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FortunateFew.java @@ -0,0 +1,128 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.f; + +import java.util.HashMap; +import java.util.Map; +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.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author spjspj + */ +public class FortunateFew extends CardImpl { + + public FortunateFew(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{W}"); + + // 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. + this.getSpellAbility().addEffect(new FortunateFewEffect()); + } + + public FortunateFew(final FortunateFew card) { + super(card); + } + + @Override + public FortunateFew copy() { + return new FortunateFew(this); + } +} + +class FortunateFewEffect extends OneShotEffect { + + public FortunateFewEffect() { + super(Outcome.DestroyPermanent); + staticText = "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"; + } + + public FortunateFewEffect(FortunateFewEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Map chosenCards = new HashMap<>(2); + int maxCount = 0; + + // Players each choose a legal permanent + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + + FilterNonlandPermanent filter = new FilterNonlandPermanent("a nonland permanent you don't control"); + filter.add(Predicates.not(new ControllerIdPredicate(player.getId()))); + + for (Permanent chosenPerm : chosenCards.keySet()) { + filter.add(Predicates.not(new PermanentIdPredicate(chosenPerm.getId()))); + } + + Target target = new TargetNonlandPermanent(filter); + target.setNotTarget(true); + if (player.choose(Outcome.Exile, target, source.getSourceId(), game)) { + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + chosenCards.put(permanent, 1); + game.informPlayers(player.getLogName() + " has chosen: " + permanent.getName()); + } + } + } + } + + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterNonlandPermanent(), source.getControllerId(), source.getSourceId(), game)) { + if (!chosenCards.containsKey(permanent)) { + permanent.destroy(source.getSourceId(), game, false); + } + } + return true; + } + return false; + } + + @Override + public FortunateFewEffect copy() { + return new FortunateFewEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 821b6fdc714..1a8c2436688 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -58,9 +58,10 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Curse of Verbosity", 9, Rarity.UNCOMMON, mage.cards.c.CurseOfVerbosity.class)); cards.add(new SetCardInfo("Curse of Vitality", 3, Rarity.UNCOMMON, mage.cards.c.CurseOfVitality.class)); cards.add(new SetCardInfo("Edgar Markov", 36, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class)); + cards.add(new SetCardInfo("Fortunate Few", 4, Rarity.RARE, mage.cards.f.FortunateFew.class)); + cards.add(new SetCardInfo("Fractured Identity", 37, Rarity.RARE, mage.cards.f.FracturedIdentity.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("Fractured Identity", 37, Rarity.RARE, mage.cards.f.FracturedIdentity.class)); cards.add(new SetCardInfo("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); cards.add(new SetCardInfo("Kindred Dominance", 18, Rarity.RARE, mage.cards.k.KindredDominance.class)); cards.add(new SetCardInfo("Mirror of the Forebears", 54, Rarity.UNCOMMON, mage.cards.m.MirrorOfTheForebears.class)); From ffc5c1c89470e8071c31ad59983d07227c49bc11 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 16 Aug 2017 12:57:48 -0400 Subject: [PATCH 02/10] Implemented Territorial Hellkite --- .../src/mage/cards/t/TerritorialHellkite.java | 186 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 187 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TerritorialHellkite.java diff --git a/Mage.Sets/src/mage/cards/t/TerritorialHellkite.java b/Mage.Sets/src/mage/cards/t/TerritorialHellkite.java new file mode 100644 index 00000000000..250c0d6d4c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerritorialHellkite.java @@ -0,0 +1,186 @@ +/* + * 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.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttacksIfAbleTargetPlayerSourceEffect; +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.Outcome; +import mage.constants.TargetController; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.RandomUtil; +import mage.watchers.Watcher; + +/** + * + * @author TheElk801 + */ +public class TerritorialHellkite extends CardImpl { + + public TerritorialHellkite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add("Dragon"); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // 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. + this.addAbility(new BeginningOfCombatTriggeredAbility(new AttackIfAbleTargetRandoOpponentSourceEffect(), TargetController.YOU, false), new AttackedLastCombatWatcher()); + } + + public TerritorialHellkite(final TerritorialHellkite card) { + super(card); + } + + @Override + public TerritorialHellkite copy() { + return new TerritorialHellkite(this); + } +} + +class AttackedLastCombatWatcher extends Watcher { + + public final Map attackedLastCombatPlayers = new HashMap<>(); + + public AttackedLastCombatWatcher() { + super(AttackedLastCombatWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public AttackedLastCombatWatcher(final AttackedLastCombatWatcher watcher) { + super(watcher); + for (Entry entry : watcher.attackedLastCombatPlayers.entrySet()) { + attackedLastCombatPlayers.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void watch(GameEvent event, Game game) { + //TODO: this will have problems if the creature is stolen and then given back before the original controller's next combat + if (event.getType() == GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE) { + if (!attackedLastCombatPlayers.keySet().isEmpty()) { + Iterator> attackers = attackedLastCombatPlayers.entrySet().iterator(); + while (attackers.hasNext()) { + Map.Entry attacker = attackers.next(); + if (game.getPermanent(attacker.getKey()).getControllerId().equals(game.getActivePlayerId())) { + attackers.remove(); + } + } + } + } + if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) { + attackedLastCombatPlayers.put(event.getSourceId(), game.getCombat().getDefenderId(event.getSourceId())); + } + } + + public Map getAttackedLastCombatPlayers() { + return this.attackedLastCombatPlayers; + } + + @Override + public AttackedLastCombatWatcher copy() { + return new AttackedLastCombatWatcher(this); + } + +} + +class AttackIfAbleTargetRandoOpponentSourceEffect extends OneShotEffect { + + public AttackIfAbleTargetRandoOpponentSourceEffect() { + super(Outcome.Benefit); + this.staticText = "choose an opponent at random that {this} didn't attack during your last combat. {this} attacks that player this combat if able. If you can't choose an opponent this way, tap {this}"; + } + + public AttackIfAbleTargetRandoOpponentSourceEffect(final AttackIfAbleTargetRandoOpponentSourceEffect effect) { + super(effect); + } + + @Override + public AttackIfAbleTargetRandoOpponentSourceEffect copy() { + return new AttackIfAbleTargetRandoOpponentSourceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + List opponents = new ArrayList<>(); + AttackedLastCombatWatcher watcher = (AttackedLastCombatWatcher) game.getState().getWatchers().get(AttackedLastCombatWatcher.class.getSimpleName()); + if (watcher != null) { + boolean ignoreMe; + for (UUID opp : game.getOpponents(controller.getId())) { + ignoreMe = false; + if (watcher.getAttackedLastCombatPlayers().getOrDefault(source.getSourceId(), source.getControllerId()).equals(opp)) { + ignoreMe = true; + } + if (!ignoreMe) { + opponents.add(opp); + } + } + } else { + opponents.addAll(game.getOpponents(controller.getId())); + } + if (!opponents.isEmpty()) { + Player opponent = game.getPlayer(opponents.get(RandomUtil.nextInt(opponents.size()))); + if (opponent != null) { + ContinuousEffect effect = new AttacksIfAbleTargetPlayerSourceEffect(); + effect.setTargetPointer(new FixedTarget(opponent.getId())); + game.addEffect(effect, source); + return true; + } + } else { + game.getPermanent(source.getSourceId()).tap(game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 821b6fdc714..427275d115d 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -73,6 +73,7 @@ public class Commander2017 extends ExpansionSet { 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("Territorial Hellkite", 29, Rarity.RARE, mage.cards.t.TerritorialHellkite.class)); cards.add(new SetCardInfo("The Ur-Dragon", 48, Rarity.MYTHIC, mage.cards.t.TheUrDragon.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 76bac135dce01e679a699ae300f4d4d1e6ad5e65 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 16 Aug 2017 13:58:35 -0400 Subject: [PATCH 03/10] Implemented Bloodline Necromancer --- .../mage/cards/b/BloodlineNecromancer.java | 84 +++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 85 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BloodlineNecromancer.java diff --git a/Mage.Sets/src/mage/cards/b/BloodlineNecromancer.java b/Mage.Sets/src/mage/cards/b/BloodlineNecromancer.java new file mode 100644 index 00000000000..a09ecbe711c --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodlineNecromancer.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.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.Target; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author TheElk801 + */ +public class BloodlineNecromancer extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("Vampire or Wizard creature card from your graveyard"); + + static { + filter.add(Predicates.or(new SubtypePredicate(SubType.VAMPIRE), new SubtypePredicate(SubType.WIZARD))); + } + + public BloodlineNecromancer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add("Vampire"); + this.subtype.add("Wizard"); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Bloodline Necromancer enters the battlefield, you may return target Vampire or Wizard creature card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), true); + Target target = new TargetCardInYourGraveyard(filter); + ability.addTarget(target); + this.addAbility(ability); + } + + public BloodlineNecromancer(final BloodlineNecromancer card) { + super(card); + } + + @Override + public BloodlineNecromancer copy() { + return new BloodlineNecromancer(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index b6522d24185..f71af32ff02 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("Arahbo, Roar of the World", 35, Rarity.MYTHIC, mage.cards.a.ArahboRoarOfTheWorld.class)); cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); cards.add(new SetCardInfo("Bloodforged War Axe", 50, Rarity.RARE, mage.cards.b.BloodforgedWarAxe.class)); + cards.add(new SetCardInfo("Bloodline Necromancer", 14, Rarity.UNCOMMON, mage.cards.b.BloodlineNecromancer.class)); cards.add(new SetCardInfo("Bloodsworn Steward", 22, Rarity.RARE, mage.cards.b.BloodswornSteward.class)); cards.add(new SetCardInfo("Crimson Honor Guard", 23, Rarity.RARE, mage.cards.c.CrimsonHonorGuard.class)); cards.add(new SetCardInfo("Curse of Bounty", 30, Rarity.UNCOMMON, mage.cards.c.CurseOfBounty.class)); From 351feb5a99d1d7bbef2f2588332ab0a85c765d88 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 17 Aug 2017 00:36:59 +0200 Subject: [PATCH 04/10] * Fixed that at match start if a player chooses who starts, the idle check was not started. --- .../src/main/java/mage/server/game/GameController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 366404cff6a..6ea79cf2bc0 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -317,8 +317,17 @@ public class GameController implements GameCallback { for (final Entry entry : gameSessions.entrySet()) { entry.getValue().init(); } + GameWorker worker = new GameWorker(game, choosingPlayerId, this); gameFuture = gameExecutor.submit(worker); + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } + if (game.getState().getChoosingPlayerId() != null) { + // start timer to force player to choose starting player otherwise loosing by being idle + setupTimeout(game.getState().getChoosingPlayerId()); + } } } From 2b8737b026ccb198c5e68b9ecbf9ca1b9230f069 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 17 Aug 2017 01:11:03 +0200 Subject: [PATCH 05/10] * Fixed text of planeswalker to keep to only include type not name. --- Mage/src/main/java/mage/game/GameImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 7f4f936bba3..4cbe21e3e8f 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1948,7 +1948,7 @@ public abstract class GameImpl implements Game, Serializable { Player controller = this.getPlayer(planeswalker.getControllerId()); if (controller != null) { Target targetPlaneswalkerToKeep = new TargetPermanent(filterPlaneswalker); - targetPlaneswalkerToKeep.setTargetName(planeswalker.getName() + " to keep?"); + targetPlaneswalkerToKeep.setTargetName(planeswalkertype.toString() + " to keep?"); controller.chooseTarget(Outcome.Benefit, targetPlaneswalkerToKeep, null, this); for (Permanent dupPlaneswalker : this.getBattlefield().getActivePermanents(filterPlaneswalker, planeswalker.getControllerId(), this)) { if (!targetPlaneswalkerToKeep.getTargets().contains(dupPlaneswalker.getId())) { From c776e0a0e15a8f09e510ae963adf6994ea2d1f1e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 16 Aug 2017 22:05:38 -0400 Subject: [PATCH 06/10] Implemented Mirri, Weatherlight Duelist --- .../cards/m/MirriWeatherlightDuelist.java | 201 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 202 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java diff --git a/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java b/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java new file mode 100644 index 00000000000..90d94d622d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java @@ -0,0 +1,201 @@ +/* + * 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.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author TheElk801 + */ +public class MirriWeatherlightDuelist extends CardImpl { + + public MirriWeatherlightDuelist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add("Cat"); + this.subtype.add("Warrior"); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // First Strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Whenever Mirri, Weatherlight Duelist attacks, each opponent can't block with more than one creature this combat. + this.addAbility(new AttacksTriggeredAbility(new AddContinuousEffectToGame(new MirriWeatherlightDuelistBlockRestrictionEffect()), false)); + + // As long as Mirri, Weatherlight Duelist is tapped, no more than one creature can attack you each combat. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( + new MirriWeatherlightDuelistAttackRestrictionEffect(1), SourceTappedCondition.instance, + "As long as {this} is tapped, no more than one creature can attack you each combat.")); + this.addAbility(ability); + } + + public MirriWeatherlightDuelist(final MirriWeatherlightDuelist card) { + super(card); + } + + @Override + public MirriWeatherlightDuelist copy() { + return new MirriWeatherlightDuelist(this); + } +} + +class MirriWeatherlightDuelistBlockRestrictionEffect extends RestrictionEffect { + + MirriWeatherlightDuelistBlockRestrictionEffect() { + super(Duration.EndOfCombat); + staticText = "each opponent can't block with more than one creature this combat"; + } + + MirriWeatherlightDuelistBlockRestrictionEffect(final MirriWeatherlightDuelistBlockRestrictionEffect effect) { + super(effect); + } + + @Override + public MirriWeatherlightDuelistBlockRestrictionEffect copy() { + return new MirriWeatherlightDuelistBlockRestrictionEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return true; + } + + @Override + public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) { + for (UUID creature : game.getCombat().getBlockers()) { + if (game.getPlayer(game.getPermanent(creature).getOwnerId()).hasOpponent(attacker.getControllerId(), game)) { + return false; + } + } + return true; + } +} + +/*class MirriWeatherlightDuelistAttackRestrictionEffect extends RestrictionEffect { + + MirriWeatherlightDuelistAttackRestrictionEffect() { + super(Duration.WhileOnBattlefield); + staticText = "no more than one creature can attack you each combat"; + } + + MirriWeatherlightDuelistAttackRestrictionEffect(final MirriWeatherlightDuelistAttackRestrictionEffect effect) { + super(effect); + } + + @Override + public MirriWeatherlightDuelistAttackRestrictionEffect copy() { + return new MirriWeatherlightDuelistAttackRestrictionEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return true; + } + + @Override + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { + for (UUID creature : game.getCombat().getAttackers()) { + if (game.getPermanent(creature).getControllerId().equals(attacker.getControllerId()) + && game.getCombat().getDefendingPlayerId(creature, game).equals(source.getControllerId())) { + return false; + } + } + return true; + } +}*/ +class MirriWeatherlightDuelistAttackRestrictionEffect extends ContinuousEffectImpl { + + private final int maxAttackedBy; + + public MirriWeatherlightDuelistAttackRestrictionEffect(int maxAttackedBy) { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + this.maxAttackedBy = maxAttackedBy; + staticText = "No more than one creature can attack you each combat"; + } + + public MirriWeatherlightDuelistAttackRestrictionEffect(final MirriWeatherlightDuelistAttackRestrictionEffect effect) { + super(effect); + this.maxAttackedBy = effect.maxAttackedBy; + } + + @Override + public MirriWeatherlightDuelistAttackRestrictionEffect copy() { + return new MirriWeatherlightDuelistAttackRestrictionEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + switch (layer) { + case RulesEffects: + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + // Change the rule + if (controller.getMaxAttackedBy() > maxAttackedBy) { + controller.setMaxAttackedBy(maxAttackedBy); + } + } + break; + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.RulesEffects; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index f71af32ff02..47663bbc7ab 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -65,6 +65,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Hungry Lynx", 31, Rarity.RARE, mage.cards.h.HungryLynx.class)); cards.add(new SetCardInfo("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); cards.add(new SetCardInfo("Kindred Dominance", 18, Rarity.RARE, mage.cards.k.KindredDominance.class)); + cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 43, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); cards.add(new SetCardInfo("Mirror of the Forebears", 54, Rarity.UNCOMMON, mage.cards.m.MirrorOfTheForebears.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 636db342f29d0ea128d6a0fbf57c44d637881fff Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 17 Aug 2017 08:12:47 -0400 Subject: [PATCH 07/10] Implemented Galecaster Colossus --- .../src/mage/cards/g/GalecasterColossus.java | 88 +++++++++++++++++++ .../cards/m/MirriWeatherlightDuelist.java | 34 +------ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 3 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/g/GalecasterColossus.java diff --git a/Mage.Sets/src/mage/cards/g/GalecasterColossus.java b/Mage.Sets/src/mage/cards/g/GalecasterColossus.java new file mode 100644 index 00000000000..5bf40b9c85e --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GalecasterColossus.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author TheElk801 + */ +public class GalecasterColossus extends CardImpl { + + private static final FilterNonlandPermanent filter = new FilterNonlandPermanent("nonland permanent you don't control"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("untapped Wizard you control"); + + static { + filter.add(new ControllerPredicate(TargetController.NOT_YOU)); + filter2.add(new SubtypePredicate(SubType.WIZARD)); + filter2.add(Predicates.not(new TappedPredicate())); + } + + public GalecasterColossus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add("Giant"); + this.subtype.add("Wizard"); + this.power = new MageInt(5); + this.toughness = new MageInt(6); + + // Tap an untapped Wizard you control: Return target nonland permanent you don't control to its owner's hand. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new TapTargetCost(new TargetControlledPermanent(1, 1, filter2, true))); + ability.addTarget(new TargetNonlandPermanent(filter)); + this.addAbility(ability); + } + + public GalecasterColossus(final GalecasterColossus card) { + super(card); + } + + @Override + public GalecasterColossus copy() { + return new GalecasterColossus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java b/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java index 90d94d622d1..c9b9bf8bb8c 100644 --- a/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java +++ b/Mage.Sets/src/mage/cards/m/MirriWeatherlightDuelist.java @@ -71,7 +71,7 @@ public class MirriWeatherlightDuelist extends CardImpl { // Whenever Mirri, Weatherlight Duelist attacks, each opponent can't block with more than one creature this combat. this.addAbility(new AttacksTriggeredAbility(new AddContinuousEffectToGame(new MirriWeatherlightDuelistBlockRestrictionEffect()), false)); - + // As long as Mirri, Weatherlight Duelist is tapped, no more than one creature can attack you each combat. Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new MirriWeatherlightDuelistAttackRestrictionEffect(1), SourceTappedCondition.instance, @@ -121,38 +121,6 @@ class MirriWeatherlightDuelistBlockRestrictionEffect extends RestrictionEffect { } } -/*class MirriWeatherlightDuelistAttackRestrictionEffect extends RestrictionEffect { - - MirriWeatherlightDuelistAttackRestrictionEffect() { - super(Duration.WhileOnBattlefield); - staticText = "no more than one creature can attack you each combat"; - } - - MirriWeatherlightDuelistAttackRestrictionEffect(final MirriWeatherlightDuelistAttackRestrictionEffect effect) { - super(effect); - } - - @Override - public MirriWeatherlightDuelistAttackRestrictionEffect copy() { - return new MirriWeatherlightDuelistAttackRestrictionEffect(this); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - return true; - } - - @Override - public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { - for (UUID creature : game.getCombat().getAttackers()) { - if (game.getPermanent(creature).getControllerId().equals(attacker.getControllerId()) - && game.getCombat().getDefendingPlayerId(creature, game).equals(source.getControllerId())) { - return false; - } - } - return true; - } -}*/ class MirriWeatherlightDuelistAttackRestrictionEffect extends ContinuousEffectImpl { private final int maxAttackedBy; diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 47663bbc7ab..b2e4e92a2b5 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -61,6 +61,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Edgar Markov", 36, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class)); cards.add(new SetCardInfo("Fortunate Few", 4, Rarity.RARE, mage.cards.f.FortunateFew.class)); cards.add(new SetCardInfo("Fractured Identity", 37, Rarity.RARE, mage.cards.f.FracturedIdentity.class)); + cards.add(new SetCardInfo("Galecaster Colossus", 10, Rarity.RARE, mage.cards.g.GalecasterColossus.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("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); From d2d6c635ac835471b1acb0a2a1da8bbaeb502414 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 17 Aug 2017 09:44:30 -0400 Subject: [PATCH 08/10] Implemented Kheru Mind-Eater --- .../src/mage/cards/k/KheruMindEater.java | 194 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 195 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KheruMindEater.java diff --git a/Mage.Sets/src/mage/cards/k/KheruMindEater.java b/Mage.Sets/src/mage/cards/k/KheruMindEater.java new file mode 100644 index 00000000000..3717cc6216a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KheruMindEater.java @@ -0,0 +1,194 @@ +/* + * 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.k; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +/** + * + * @author TheElk801 + */ +public class KheruMindEater extends CardImpl { + + public KheruMindEater(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add("Vampire"); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Kheru Mind-Eater deals combat damage to a player, that player exiles a card from his or her hand face down. + this.addAbility(new DealsDamageToAPlayerTriggeredAbility(new KheruMindEaterExileEffect(), false, true)); + + // You may look at and play cards exiled with Kheru Mind-Eater. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new KheruMindEaterEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new KheruMindEaterLookAtCardEffect())); + } + + public KheruMindEater(final KheruMindEater card) { + super(card); + } + + @Override + public KheruMindEater copy() { + return new KheruMindEater(this); + } +} + +class KheruMindEaterExileEffect extends OneShotEffect { + + public KheruMindEaterExileEffect() { + super(Outcome.Discard); + staticText = "that player exiles a card of his or her hand face down"; + } + + public KheruMindEaterExileEffect(final KheruMindEaterExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + if (player != null && player.getHand().size() > 0) { + Target target = new TargetCardInHand(1, new FilterCard()); + target.chooseTarget(Outcome.Exile, player.getId(), source, game); + Card card = game.getCard(target.getFirstTarget()); + MageObject sourceObject = game.getObject(source.getSourceId()); + if (card != null && sourceObject != null) { + if (player.moveCardToExileWithInfo(card, CardUtil.getCardExileZoneId(game, source), sourceObject.getIdName(), source.getSourceId(), game, Zone.HAND, true)) { + card.setFaceDown(true, game); + } + return true; + } + } + return false; + } + + @Override + public KheruMindEaterExileEffect copy() { + return new KheruMindEaterExileEffect(this); + } +} + +class KheruMindEaterEffect extends AsThoughEffectImpl { + + public KheruMindEaterEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may play cards exiled with {this}"; + } + + public KheruMindEaterEffect(final KheruMindEaterEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public KheruMindEaterEffect copy() { + return new KheruMindEaterEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card card = game.getCard(objectId); + if (affectedControllerId.equals(source.getControllerId()) && card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { + ExileZone zone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); + return zone != null && zone.contains(card.getId()); + } + return false; + } +} + +class KheruMindEaterLookAtCardEffect extends AsThoughEffectImpl { + + public KheruMindEaterLookAtCardEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may look at cards exiled with {this}"; + } + + public KheruMindEaterLookAtCardEffect(final KheruMindEaterLookAtCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public KheruMindEaterLookAtCardEffect copy() { + return new KheruMindEaterLookAtCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + if (card != null) { + MageObject sourceObject = game.getObject(source.getSourceId()); + if (sourceObject == null) { + return false; + } + UUID exileId = CardUtil.getCardExileZoneId(game, source); + ExileZone exile = game.getExile().getExileZone(exileId); + return exile != null && exile.contains(objectId); + } + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index b2e4e92a2b5..f0954f7726e 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -65,6 +65,7 @@ public class Commander2017 extends ExpansionSet { 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("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); + cards.add(new SetCardInfo("Kheru Mind-Eater", 17, Rarity.RARE, mage.cards.k.KheruMindEater.class)); cards.add(new SetCardInfo("Kindred Dominance", 18, Rarity.RARE, mage.cards.k.KindredDominance.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 43, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); cards.add(new SetCardInfo("Mirror of the Forebears", 54, Rarity.UNCOMMON, mage.cards.m.MirrorOfTheForebears.class)); From f6a06fd5768e44bc69d45a25dfee44d06b1f635e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 17 Aug 2017 10:15:19 -0400 Subject: [PATCH 09/10] Implemented Magus of the Mind --- .../src/mage/cards/m/MagusOfTheMind.java | 169 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 170 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MagusOfTheMind.java diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheMind.java b/Mage.Sets/src/mage/cards/m/MagusOfTheMind.java new file mode 100644 index 00000000000..eff7793c91b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheMind.java @@ -0,0 +1,169 @@ +/* + * 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.m; + +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.CastSpellLastTurnWatcher; + +/** + * + * @author TheElk801 + */ +public class MagusOfTheMind extends CardImpl { + + public MagusOfTheMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add("Human"); + this.subtype.add("Wizard"); + this.power = new MageInt(4); + this.toughness = new MageInt(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. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MagusOfTheMindEffect(), new ManaCostsImpl("{U}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability, new CastSpellLastTurnWatcher()); + } + + public MagusOfTheMind(final MagusOfTheMind card) { + super(card); + } + + @Override + public MagusOfTheMind copy() { + return new MagusOfTheMind(this); + } +} + +class MagusOfTheMindEffect extends OneShotEffect { + + MagusOfTheMindEffect() { + super(Outcome.Benefit); + this.staticText = "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"; + } + + MagusOfTheMindEffect(final MagusOfTheMindEffect effect) { + super(effect); + } + + @Override + public MagusOfTheMindEffect copy() { + return new MagusOfTheMindEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getSimpleName()); + int stormCount = watcher.getAmountOfSpellsAllPlayersCastOnCurrentTurn() + 1; + System.out.println(stormCount); + if (controller != null && sourceObject != null) { + controller.shuffleLibrary(source, game); + if (controller.getLibrary().hasCards()) { + Set cards = controller.getLibrary().getTopCards(game, stormCount); + if (cards != null) { + for (Card card : cards) { + if (card != null) { + controller.moveCardToExileWithInfo(card, source.getSourceId(), sourceObject.getIdName(), source.getSourceId(), game, Zone.LIBRARY, true); + ContinuousEffect effect = new MagusOfTheMindCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId())); + game.addEffect(effect, source); + } + } + } + } + return true; + } + return false; + } +} + +class MagusOfTheMindCastFromExileEffect extends AsThoughEffectImpl { + + MagusOfTheMindCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "you may play that card without paying its mana cost"; + } + + MagusOfTheMindCastFromExileEffect(final MagusOfTheMindCastFromExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public MagusOfTheMindCastFromExileEffect copy() { + return new MagusOfTheMindCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (objectId != null && objectId.equals(getTargetPointer().getFirst(game, source))) { + if (affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + if (card != null && game.getState().getZone(objectId) == Zone.EXILED) { + if (!card.isLand() && card.getSpellAbility().getCosts() != null) { + Player player = game.getPlayer(affectedControllerId); + if (player != null) { + player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts()); + } + } + return true; + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index f0954f7726e..66eda5eba25 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -67,6 +67,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); cards.add(new SetCardInfo("Kheru Mind-Eater", 17, Rarity.RARE, mage.cards.k.KheruMindEater.class)); cards.add(new SetCardInfo("Kindred Dominance", 18, Rarity.RARE, mage.cards.k.KindredDominance.class)); + cards.add(new SetCardInfo("Magus of the Mind", 12, Rarity.RARE, mage.cards.m.MagusOfTheMind.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 43, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); cards.add(new SetCardInfo("Mirror of the Forebears", 54, Rarity.UNCOMMON, mage.cards.m.MirrorOfTheForebears.class)); cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); From 126e4561723963981b8abff3d2e11f3c566b3239 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 17 Aug 2017 11:02:14 -0400 Subject: [PATCH 10/10] Implemented Izzet Chemister --- .../src/mage/cards/i/IzzetChemister.java | 149 ++++++++++++++++++ Mage.Sets/src/mage/sets/Commander2017.java | 1 + 2 files changed, 150 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/IzzetChemister.java diff --git a/Mage.Sets/src/mage/cards/i/IzzetChemister.java b/Mage.Sets/src/mage/cards/i/IzzetChemister.java new file mode 100644 index 00000000000..b131d443221 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IzzetChemister.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.i; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.HasteAbility; +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.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterOwnedCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; + +/** + * + * @author TheElk801 + */ +public class IzzetChemister extends CardImpl { + + private static final FilterCard filter = new FilterOwnedCard("instant or sorcery card from your graveyard"); + + public IzzetChemister(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add("Goblin"); + this.subtype.add("Wizard"); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // R, T: Exile target instant or sorcery card from your graveyard. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(this.getId(), this.getIdName()), new ManaCostsImpl("{R}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCard(Zone.GRAVEYARD, filter)); + this.addAbility(ability); + + // 1R, T: Sacrifice Izzet Chemister: Cast any number of cards exiled with Izzet Chemister without paying their mana costs. + IzzetChemisterCastFromExileEffect returnFromExileEffect = new IzzetChemisterCastFromExileEffect(this.getId(), "Cast any number of cards exiled with {this} without paying their mana costs."); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, returnFromExileEffect, new ManaCostsImpl("{1}{R}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public IzzetChemister(final IzzetChemister card) { + super(card); + } + + @Override + public IzzetChemister copy() { + return new IzzetChemister(this); + } +} + +class IzzetChemisterCastFromExileEffect extends OneShotEffect { + + private UUID exileId; + + public IzzetChemisterCastFromExileEffect(UUID exileId, String description) { + super(Outcome.PlayForFree); + this.exileId = exileId; + this.setText(description); + } + + public IzzetChemisterCastFromExileEffect(final IzzetChemisterCastFromExileEffect effect) { + super(effect); + this.exileId = effect.exileId; + } + + @Override + public IzzetChemisterCastFromExileEffect copy() { + return new IzzetChemisterCastFromExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ExileZone exile = game.getExile().getExileZone(exileId); + Player controller = game.getPlayer(source.getControllerId()); + FilterCard filter = new FilterCard(); + if (controller != null && exile != null) { + Cards cardsToExile = new CardsImpl(); + cardsToExile.addAll(exile.getCards(game)); + OuterLoop: + while (cardsToExile.count(filter, game) > 0) { + if (!controller.canRespond()) { + return false; + } + TargetCardInExile target = new TargetCardInExile(0, 1, filter, exileId, false); + target.setNotTarget(true); + while (cardsToExile.count(filter, game) > 0 && controller.choose(Outcome.PlayForFree, cardsToExile, target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + controller.cast(card.getSpellAbility(), game, true); + cardsToExile.remove(card); + } else { + break OuterLoop; + } + target.clearChosen(); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 66eda5eba25..014843135d7 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -65,6 +65,7 @@ public class Commander2017 extends ExpansionSet { 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("Inalla, Archmage Ritualist", 38, Rarity.MYTHIC, mage.cards.i.InallaArchmageRitualist.class)); + cards.add(new SetCardInfo("Izzet Chemister", 26, Rarity.RARE, mage.cards.i.IzzetChemister.class)); cards.add(new SetCardInfo("Kheru Mind-Eater", 17, Rarity.RARE, mage.cards.k.KheruMindEater.class)); cards.add(new SetCardInfo("Kindred Dominance", 18, Rarity.RARE, mage.cards.k.KindredDominance.class)); cards.add(new SetCardInfo("Magus of the Mind", 12, Rarity.RARE, mage.cards.m.MagusOfTheMind.class));