diff --git a/Mage.Sets/src/mage/cards/w/WitchKingOfAngmar.java b/Mage.Sets/src/mage/cards/w/WitchKingOfAngmar.java new file mode 100644 index 00000000000..1c743820966 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WitchKingOfAngmar.java @@ -0,0 +1,73 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CombatDamageDealtToYouTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.effects.common.TapSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.keyword.TheRingTemptsYouEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; + +import java.util.UUID; + +/** + * + * @author bobby-mccann + */ +public final class WitchKingOfAngmar extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creature that dealt combat damage to this ability's controller this turn"); + + static { + filter.add(new DamagedPlayerThisTurnPredicate(TargetController.SOURCE_CONTROLLER, true)); + } + + public WitchKingOfAngmar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WRAITH); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever one or more creatures deal combat damage to you, each opponent sacrifices a creature that dealt combat damage to you this turn. The Ring tempts you. + { + Ability ability = new CombatDamageDealtToYouTriggeredAbility(new SacrificeOpponentsEffect(filter)); + ability.addEffect(new TheRingTemptsYouEffect()); + this.addAbility(ability); + } + + // Discard a card: Witch-king of Angmar gains indestructible until end of turn. Tap it. + { + Ability ability = new SimpleActivatedAbility( + new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), + new DiscardCardCost() + ); + ability.addEffect(new TapSourceEffect().setText("tap it")); + this.addAbility(ability); + } + } + + private WitchKingOfAngmar(final WitchKingOfAngmar card) { + super(card); + } + + @Override + public WitchKingOfAngmar copy() { + return new WitchKingOfAngmar(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index 487861015a1..39242da3ca9 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -280,6 +280,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Warbeast of Gorgoroth", 152, Rarity.COMMON, mage.cards.w.WarbeastOfGorgoroth.class)); cards.add(new SetCardInfo("Westfold Rider", 37, Rarity.COMMON, mage.cards.w.WestfoldRider.class)); cards.add(new SetCardInfo("Willow-Wind", 76, Rarity.COMMON, mage.cards.w.WillowWind.class)); + cards.add(new SetCardInfo("Witch-king of Angmar", 114, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class)); cards.add(new SetCardInfo("Witch-king, Bringer of Ruin", 293, Rarity.RARE, mage.cards.w.WitchKingBringerOfRuin.class)); cards.add(new SetCardInfo("Wizard's Rockets", 252, Rarity.COMMON, mage.cards.w.WizardsRockets.class)); cards.add(new SetCardInfo("Wose Pathfinder", 190, Rarity.COMMON, mage.cards.w.WosePathfinder.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/WitchKingOfAngmarTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/WitchKingOfAngmarTest.java new file mode 100644 index 00000000000..2a1d4f79b6d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/WitchKingOfAngmarTest.java @@ -0,0 +1,62 @@ +package org.mage.test.cards.single.ltr; + +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class WitchKingOfAngmarTest extends CardTestPlayerBase { + static final String witchKing = "Witch-king of Angmar"; + @Test + public void testSacrifice() { + setStrictChooseMode(true); + + String watchwolf = "Watchwolf"; + String swallower = "Simic Sky Swallower"; // Has shroud - should still be a choice + + addCard(Zone.BATTLEFIELD, playerA, witchKing, 1, true); + addCard(Zone.HAND, playerA, "Swamp", 5); + + addCard(Zone.BATTLEFIELD, playerB, watchwolf, 1); + addCard(Zone.BATTLEFIELD, playerB, swallower, 1); + addCard(Zone.BATTLEFIELD, playerB, "Nivix Cyclops", 1); + + attack(2, playerB, watchwolf); + attack(2, playerB, swallower); + checkStackObject("Sacrifice trigger check", 2, PhaseStep.COMBAT_DAMAGE, playerB, "Whenever one or more creatures deal combat damage to you", 1); + + // Choose which creature to sacrifice + addTarget(playerB, watchwolf); + + // The ring tempts you choice: + setChoice(playerA, witchKing); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 - 3 - 6); + // Player B had to sacrifice one permanent: + assertPermanentCount(playerB, 2); + } + + @Test + public void testIndestructible() { + addCard(Zone.BATTLEFIELD, playerA, witchKing, 1); + addCard(Zone.HAND, playerA, "Swamp", 5); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard a card:"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAbility(playerA, witchKing, IndestructibleAbility.getInstance(), true); + assertTapped(witchKing, true); + assertHandCount(playerA, 4); + + setStopAt(2, PhaseStep.UNTAP); + execute(); + + assertAbility(playerA, witchKing, IndestructibleAbility.getInstance(), false); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/constants/TargetController.java b/Mage/src/main/java/mage/constants/TargetController.java index 2d3e103e391..bfd0e04255b 100644 --- a/Mage/src/main/java/mage/constants/TargetController.java +++ b/Mage/src/main/java/mage/constants/TargetController.java @@ -27,7 +27,8 @@ public enum TargetController { EACH_PLAYER, ENCHANTED, SOURCE_TARGETS, - MONARCH; + MONARCH, + SOURCE_CONTROLLER; private final OwnerPredicate ownerPredicate; private final PlayerPredicate playerPredicate; @@ -78,6 +79,8 @@ public enum TargetController { case ENCHANTED: Permanent permanent = input.getSource().getSourcePermanentIfItStillExists(game); return permanent != null && input.getObject().isOwnedBy(permanent.getAttachedTo()); + case SOURCE_CONTROLLER: + return card.isOwnedBy(input.getSource().getControllerId()); case SOURCE_TARGETS: return card.isOwnedBy(input.getSource().getFirstTarget()); case MONARCH: @@ -119,8 +122,10 @@ public enum TargetController { game.getPlayer(playerId).hasOpponent(player.getId(), game); case NOT_YOU: return !player.getId().equals(playerId); + case SOURCE_CONTROLLER: + return player.getId().equals(input.getSource().getControllerId()); case SOURCE_TARGETS: - return player.equals(input.getSource().getFirstTarget()); + return player.getId().equals(input.getSource().getFirstTarget()); case MONARCH: return player.getId().equals(game.getMonarchId()); default: @@ -162,6 +167,8 @@ public enum TargetController { case ENCHANTED: Permanent permanent = input.getSource().getSourcePermanentIfItStillExists(game); return permanent != null && input.getObject().isControlledBy(permanent.getAttachedTo()); + case SOURCE_CONTROLLER: + return object.isControlledBy(input.getSource().getControllerId()); case SOURCE_TARGETS: return object.isControlledBy(input.getSource().getFirstTarget()); case MONARCH: diff --git a/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java index 4f3392da66b..6d814cba451 100644 --- a/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java @@ -10,61 +10,83 @@ import mage.watchers.common.PlayerDamagedBySourceWatcher; import java.util.UUID; /** + * For use in abilities with this predicate: + * "_ that dealt (combat) damage to _ this turn" + * * @author LevelX2 */ public class DamagedPlayerThisTurnPredicate implements ObjectSourcePlayerPredicate { - private final TargetController controller; + private final TargetController playerDamaged; - public DamagedPlayerThisTurnPredicate(TargetController controller) { - this.controller = controller; + private final boolean combatDamageOnly; + + public DamagedPlayerThisTurnPredicate(TargetController playerDamaged) { + this(playerDamaged, false); + } + + public DamagedPlayerThisTurnPredicate(TargetController playerDamaged, boolean combatDamageOnly) { + this.playerDamaged = playerDamaged; + this.combatDamageOnly = combatDamageOnly; } @Override public boolean apply(ObjectSourcePlayer input, Game game) { - Controllable object = input.getObject(); + UUID objectId = input.getObject().getId(); UUID playerId = input.getPlayerId(); - switch (controller) { + switch (playerDamaged) { case YOU: - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, playerId); - if (watcher != null) { - return watcher.hasSourceDoneDamage(object.getId(), game); - } - break; + // that dealt damage to you this turn + return playerDealtDamageBy(playerId, objectId, game); + case SOURCE_CONTROLLER: + // that dealt damage to this spell/ability's controller this turn + UUID controllerId = input.getSource().getControllerId(); + return playerDealtDamageBy(controllerId, objectId, game); case OPPONENT: + // that dealt damage to an opponent this turn for (UUID opponentId : game.getOpponents(playerId)) { - watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, opponentId); - if (watcher != null) { - return watcher.hasSourceDoneDamage(object.getId(), game); + if (playerDealtDamageBy(opponentId, objectId, game)) { + return true; } } - break; + return false; case NOT_YOU: + // that dealt damage to another player this turn for (UUID notYouId : game.getState().getPlayersInRange(playerId, game)) { if (!notYouId.equals(playerId)) { - watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, notYouId); - if (watcher != null) { - return watcher.hasSourceDoneDamage(object.getId(), game); + if (playerDealtDamageBy(notYouId, objectId, game)) { + return true; } } } - break; + return false; case ANY: + // that dealt damage to a player this turn for (UUID anyId : game.getState().getPlayersInRange(playerId, game)) { - watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, anyId); - if (watcher != null) { - return watcher.hasSourceDoneDamage(object.getId(), game); + if (playerDealtDamageBy(anyId, objectId, game)) { + return true; } } - return true; + return false; + default: + throw new UnsupportedOperationException("TargetController not supported"); } + } - return false; + private boolean playerDealtDamageBy(UUID playerId, UUID objectId, Game game) { + PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, playerId); + if (watcher == null) { + return false; + } + if (combatDamageOnly) { + return watcher.hasSourceDoneCombatDamage(objectId, game); + } + return watcher.hasSourceDoneDamage(objectId, game); } @Override public String toString() { - return "Damaged player (" + controller.toString() + ')'; + return "Damaged player (" + playerDamaged.toString() + ')'; } } diff --git a/Mage/src/main/java/mage/watchers/common/PlayerDamagedBySourceWatcher.java b/Mage/src/main/java/mage/watchers/common/PlayerDamagedBySourceWatcher.java index fc9e2d99c95..c0a7f926ee9 100644 --- a/Mage/src/main/java/mage/watchers/common/PlayerDamagedBySourceWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PlayerDamagedBySourceWatcher.java @@ -6,8 +6,8 @@ import java.util.Set; import java.util.UUID; import mage.constants.WatcherScope; import mage.game.Game; +import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -19,6 +19,7 @@ import mage.watchers.Watcher; public class PlayerDamagedBySourceWatcher extends Watcher { private final Set damageSourceIds = new HashSet<>(); + private final Set combatDamageSourceIds = new HashSet<>(); public PlayerDamagedBySourceWatcher() { super(WatcherScope.PLAYER); @@ -28,7 +29,11 @@ public class PlayerDamagedBySourceWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { if (event.getTargetId().equals(controllerId)) { - damageSourceIds.add(CardUtil.getCardZoneString(null, event.getSourceId(), game)); + String sourceId = CardUtil.getCardZoneString(null, event.getSourceId(), game); + damageSourceIds.add(sourceId); + if (((DamagedEvent) event).isCombatDamage()) { + combatDamageSourceIds.add(sourceId); + } } } } @@ -45,6 +50,10 @@ public class PlayerDamagedBySourceWatcher extends Watcher { return damageSourceIds.contains(CardUtil.getCardZoneString(null, sourceId, game)); } + public boolean hasSourceDoneCombatDamage(UUID sourceId, Game game) { + return combatDamageSourceIds.contains(CardUtil.getCardZoneString(null, sourceId, game)); + } + @Override public void reset() { super.reset();