From ff11727596e640489c7adde277bcb3259e8a0db3 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 27 Dec 2018 18:01:37 -0600 Subject: [PATCH] - Added Krovikan Vampire. Fixed Duplicity. --- Mage.Sets/src/mage/cards/d/Duplicity.java | 68 ++++- .../src/mage/cards/k/KrovikanVampire.java | 264 ++++++++++++++++++ Mage.Sets/src/mage/sets/IceAge.java | 1 + Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 4 files changed, 322 insertions(+), 12 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/k/KrovikanVampire.java diff --git a/Mage.Sets/src/mage/cards/d/Duplicity.java b/Mage.Sets/src/mage/cards/d/Duplicity.java index 42fd7d75ab4..24b5f48d783 100644 --- a/Mage.Sets/src/mage/cards/d/Duplicity.java +++ b/Mage.Sets/src/mage/cards/d/Duplicity.java @@ -6,10 +6,15 @@ import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.StaticAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.EntersBattlefieldEffect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.discard.DiscardControllerEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -21,8 +26,8 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; import mage.players.Player; -import mage.util.CardUtil; /** * @@ -43,7 +48,7 @@ public final class Duplicity extends CardImpl { this.addAbility(new BeginningOfYourEndStepTriggeredAbility(new DiscardControllerEffect(1), false)); // When you lose control of Duplicity, put all cards exiled with Duplicity into their owner's graveyard. - this.addAbility(new LoseControlDuplicity()); + this.addAbility(new DuplicityEntersBattlefieldAbility(new CreateDelayedTriggeredAbilityEffect(new LoseControlDuplicity()))); } @@ -75,7 +80,7 @@ class DuplicityEffect extends OneShotEffect { if (controller != null && sourceObject != null) { if (controller.getLibrary().hasCards()) { - UUID exileId = CardUtil.getCardExileZoneId(game, source); + UUID exileId = source.getSourceId(); Set cardsToExile = controller.getLibrary().getTopCards(game, 5); for (Card card : cardsToExile) { controller.moveCardsToExile(card, source, game, true, exileId, sourceObject.getName()); @@ -111,7 +116,7 @@ class DuplicityExileHandEffect extends OneShotEffect { if (controller != null && sourceObject != null) { if (!controller.getHand().isEmpty()) { - UUID exileId = CardUtil.getCardExileZoneId(game, source); + UUID exileId = source.getSourceId(); Set cardsFromHandToExile = controller.getHand().getCards(game); for (Card card : cardsFromHandToExile) { controller.moveCardsToExile(card, source, game, true, exileId, sourceObject.getName()); @@ -154,12 +159,23 @@ class LoseControlDuplicity extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_CONTROL; + return event.getType() == GameEvent.EventType.LOST_CONTROL + || event.getType() == GameEvent.EventType.ZONE_CHANGE; } @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(controllerId); + if (event.getType() == GameEvent.EventType.LOST_CONTROL + && event.getPlayerId().equals(controllerId) + && event.getSourceId().equals(getSourceId())) { + return true; + } + if (event.getType() == GameEvent.EventType.ZONE_CHANGE + && event.getTargetId().equals(getSourceId()) + && ((ZoneChangeEvent) event).getToZone() != Zone.BATTLEFIELD) { + return true; + } + return false; } @Override @@ -182,13 +198,13 @@ class PutExiledCardsInOwnersGraveyard extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source.getSourceId()); - if (controller != null - && sourceObject != null) { - UUID exileId = CardUtil.getCardExileZoneId(game, source); + if (controller != null) { + UUID exileId = source.getSourceId(); Set cardsInExile = game.getExile().getExileZone(exileId).getCards(game); - controller.moveCardsToGraveyardWithInfo(cardsInExile, source, game, Zone.EXILED); - return true; + if (cardsInExile != null) { + controller.moveCards(cardsInExile, Zone.GRAVEYARD, source, game); + return true; + } } return false; } @@ -198,3 +214,31 @@ class PutExiledCardsInOwnersGraveyard extends OneShotEffect { return new PutExiledCardsInOwnersGraveyard(this); } } + +class DuplicityEntersBattlefieldAbility extends StaticAbility { + + public DuplicityEntersBattlefieldAbility(Effect effect) { + super(Zone.ALL, new EntersBattlefieldEffect(effect, null, null, true, false)); + } + + public DuplicityEntersBattlefieldAbility(final DuplicityEntersBattlefieldAbility ability) { + super(ability); + } + + @Override + public void addEffect(Effect effect) { + if (!getEffects().isEmpty()) { + Effect entersBattlefieldEffect = this.getEffects().get(0); + if (entersBattlefieldEffect instanceof EntersBattlefieldEffect) { + ((EntersBattlefieldEffect) entersBattlefieldEffect).addEffect(effect); + return; + } + } + super.addEffect(effect); + } + + @Override + public DuplicityEntersBattlefieldAbility copy() { + return new DuplicityEntersBattlefieldAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KrovikanVampire.java b/Mage.Sets/src/mage/cards/k/KrovikanVampire.java new file mode 100644 index 00000000000..9b1eb757a40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KrovikanVampire.java @@ -0,0 +1,264 @@ +package mage.cards.k; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; + +/** + * + * @author jeffwadsworth + */ +public final class KrovikanVampire extends CardImpl { + + public KrovikanVampire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of each end step, if a creature dealt damage by Krovikan Vampire this turn died, put that card onto the battlefield under your control. Sacrifice it when you lose control of Krovikan Vampire. + Ability ability = new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, + new KrovikanVampireEffect(), + TargetController.ANY, + KrovikanVampireInterveningIfCondition.instance, + false); + ability.addWatcher(new KrovikanVampireCreaturesDamagedWatcher()); + ability.addWatcher(new KrovikanVampireCreaturesDiedWatcher()); + this.addAbility(ability); + } + + public KrovikanVampire(final KrovikanVampire card) { + super(card); + } + + @Override + public KrovikanVampire copy() { + return new KrovikanVampire(this); + } +} + +class KrovikanVampireEffect extends OneShotEffect { + + Set creaturesAffected = new HashSet<>(); + + KrovikanVampireEffect() { + super(Outcome.Neutral); + staticText = "put that card onto the battlefield under your control. Sacrifice it when you lose control of {this}"; + } + + KrovikanVampireEffect(KrovikanVampireEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent krovikanVampire = game.getPermanent(source.getSourceId()); + creaturesAffected = (Set) game.getState().getValue(source.getSourceId() + "creatureToGainControl"); + if (creaturesAffected != null + && controller != null + && krovikanVampire != null) { + for (UUID creatureId : creaturesAffected) { + controller.moveCards(game.getCard(creatureId), Zone.BATTLEFIELD, source, game, false, false, false, null); + OneShotEffect effect = new SacrificeTargetEffect(); + effect.setText("Sacrifice this if Krovikan Vampire leaves the battlefield or its current controller loses control of it."); + effect.setTargetPointer(new FixedTarget(creatureId)); + KrovikanVampireDelayedTriggeredAbility dTA = new KrovikanVampireDelayedTriggeredAbility(effect, krovikanVampire.getId()); + game.addDelayedTriggeredAbility(dTA, source); + } + creaturesAffected.clear(); + return true; + } + return false; + } + + @Override + public KrovikanVampireEffect copy() { + return new KrovikanVampireEffect(this); + } +} + +enum KrovikanVampireInterveningIfCondition implements Condition { + + instance; + Set creaturesAffected = new HashSet<>(); + + @Override + public boolean apply(Game game, Ability source) { + KrovikanVampireCreaturesDiedWatcher watcherDied = (KrovikanVampireCreaturesDiedWatcher) game.getState().getWatchers().get(KrovikanVampireCreaturesDiedWatcher.class.getSimpleName()); + KrovikanVampireCreaturesDamagedWatcher watcherDamaged = (KrovikanVampireCreaturesDamagedWatcher) game.getState().getWatchers().get(KrovikanVampireCreaturesDamagedWatcher.class.getSimpleName()); + if (watcherDied != null) { + Set creaturesThatDiedThisTurn = watcherDied.diedThisTurn; + if (creaturesThatDiedThisTurn != null) { + for (UUID mor : creaturesThatDiedThisTurn) { + if (watcherDamaged != null) { + for (UUID mor2 : watcherDamaged.getDamagedBySource()) { + if (mor2 != null + && mor == mor2) { + creaturesAffected.add(mor); + } + } + } + } + if (creaturesAffected != null + && creaturesAffected.size() > 0) { + game.getState().setValue(source.getSourceId() + "creatureToGainControl", creaturesAffected); + return true; + } + } + } + return false; + } + + @Override + public String toString() { + return "if a creature dealt damage by Krovikan Vampire this turn died"; + } +} + +class KrovikanVampireCreaturesDamagedWatcher extends Watcher { + + public final Set damagedBySource = new HashSet<>(); + + public KrovikanVampireCreaturesDamagedWatcher() { + super(KrovikanVampireCreaturesDamagedWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public KrovikanVampireCreaturesDamagedWatcher(final KrovikanVampireCreaturesDamagedWatcher watcher) { + super(watcher); + this.damagedBySource.addAll(watcher.damagedBySource); + } + + @Override + public KrovikanVampireCreaturesDamagedWatcher copy() { + return new KrovikanVampireCreaturesDamagedWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == EventType.DAMAGED_CREATURE + && sourceId.equals(event.getSourceId())) { + damagedBySource.add(event.getTargetId()); + } + } + + public Set getDamagedBySource() { + return this.damagedBySource; + } + + @Override + public void reset() { + damagedBySource.clear(); + } +} + +class KrovikanVampireCreaturesDiedWatcher extends Watcher { + + public final Set diedThisTurn = new HashSet<>(); + + public KrovikanVampireCreaturesDiedWatcher() { + super(KrovikanVampireCreaturesDiedWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public KrovikanVampireCreaturesDiedWatcher(final KrovikanVampireCreaturesDiedWatcher watcher) { + super(watcher); + this.diedThisTurn.addAll(watcher.diedThisTurn); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.isDiesEvent() + && zEvent.getTarget() != null + && zEvent.getTarget().isCreature()) { + diedThisTurn.add(zEvent.getTargetId()); + } + } + } + + @Override + public void reset() { + diedThisTurn.clear(); + } + + public Set getDiedThisTurn() { + return this.diedThisTurn; + } + + @Override + public KrovikanVampireCreaturesDiedWatcher copy() { + return new KrovikanVampireCreaturesDiedWatcher(this); + } +} + +class KrovikanVampireDelayedTriggeredAbility extends DelayedTriggeredAbility { + + UUID krovikanVampire; + + KrovikanVampireDelayedTriggeredAbility(Effect effect, UUID krovikanVampire) { + super(effect, Duration.EndOfGame, true); + this.krovikanVampire = krovikanVampire; + } + + KrovikanVampireDelayedTriggeredAbility(KrovikanVampireDelayedTriggeredAbility ability) { + super(ability); + this.krovikanVampire = ability.krovikanVampire; + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.LOST_CONTROL + || event.getType() == EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == EventType.LOST_CONTROL + && event.getSourceId().equals(krovikanVampire)) { + return true; + } + if (event.getType() == EventType.ZONE_CHANGE + && event.getTargetId().equals(krovikanVampire)) { + return true; + } + return false; + } + + @Override + public KrovikanVampireDelayedTriggeredAbility copy() { + return new KrovikanVampireDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "LOSE CONTROL DUDE WORKS"; + } +} diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index a56bfb0d143..f20ae15e229 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -195,6 +195,7 @@ public final class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Krovikan Elementalist", 139, Rarity.UNCOMMON, mage.cards.k.KrovikanElementalist.class)); cards.add(new SetCardInfo("Krovikan Fetish", 140, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); cards.add(new SetCardInfo("Krovikan Sorcerer", 81, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); + cards.add(new SetCardInfo("Krovikan Vampire", 29, Rarity.UNCOMMON, mage.cards.k.KrovikanVampire.class)); cards.add(new SetCardInfo("Land Cap", 357, Rarity.RARE, mage.cards.l.LandCap.class)); cards.add(new SetCardInfo("Lapis Lazuli Talisman", 327, Rarity.UNCOMMON, mage.cards.l.LapisLazuliTalisman.class)); cards.add(new SetCardInfo("Lava Tubes", 358, Rarity.RARE, mage.cards.l.LavaTubes.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index 00bafb192f1..932a442fc1a 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -147,6 +147,7 @@ public final class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Krovikan Fetish", 100, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); cards.add(new SetCardInfo("Krovikan Horror", 101, Rarity.RARE, mage.cards.k.KrovikanHorror.class)); cards.add(new SetCardInfo("Krovikan Sorcerer", 51, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); + cards.add(new SetCardInfo("Krovikan Vampire", 102, Rarity.UNCOMMON, mage.cards.k.KrovikanVampire.class)); cards.add(new SetCardInfo("Lat-Nam's Legacy", 52, Rarity.COMMON, mage.cards.l.LatNamsLegacy.class)); cards.add(new SetCardInfo("Leaping Lizard", 171, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); cards.add(new SetCardInfo("Lim-Dul's High Guard", 103, Rarity.UNCOMMON, mage.cards.l.LimDulsHighGuard.class));