diff --git a/Mage.Sets/src/mage/cards/c/CleansingBeam.java b/Mage.Sets/src/mage/cards/c/CleansingBeam.java
index 4d8cd2f9b06..0693f56b247 100644
--- a/Mage.Sets/src/mage/cards/c/CleansingBeam.java
+++ b/Mage.Sets/src/mage/cards/c/CleansingBeam.java
@@ -8,7 +8,7 @@ import mage.cards.CardSetInfo;
import mage.constants.AbilityWord;
import mage.constants.CardType;
import mage.constants.Outcome;
-import mage.filter.FilterPermanent;
+import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@@ -41,12 +41,6 @@ public final class CleansingBeam extends CardImpl {
class CleansingBeamEffect extends OneShotEffect {
- static final FilterPermanent filter = new FilterPermanent("creature");
-
- static {
- filter.add(CardType.CREATURE.getPredicate());
- }
-
CleansingBeamEffect() {
super(Outcome.Damage);
staticText = "{this} deals 2 damage to target creature and each other creature that shares a color with it";
@@ -61,10 +55,10 @@ class CleansingBeamEffect extends OneShotEffect {
Permanent target = game.getPermanent(targetPointer.getFirst(game, source));
if (target != null) {
ObjectColor color = target.getColor(game);
- target.damage(2, source.getSourceId(), source, game);
- for (Permanent p : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
+ target.damage(2, source, game);
+ for (Permanent p : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game)) {
if (!target.getId().equals(p.getId()) && p.getColor(game).shares(color)) {
- p.damage(2, source.getSourceId(), source, game, false, true);
+ p.damage(2, source, game);
}
}
return true;
diff --git a/Mage.Sets/src/mage/cards/c/CowardKiller.java b/Mage.Sets/src/mage/cards/c/CowardKiller.java
new file mode 100644
index 00000000000..e7717532831
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/c/CowardKiller.java
@@ -0,0 +1,79 @@
+package mage.cards.c;
+
+import mage.abilities.Ability;
+import mage.abilities.effects.OneShotEffect;
+import mage.abilities.effects.common.combat.CantBlockTargetEffect;
+import mage.abilities.effects.common.continuous.BecomesCreatureTypeTargetEffect;
+import mage.abilities.effects.common.counter.TimeTravelEffect;
+import mage.cards.CardSetInfo;
+import mage.cards.SplitCard;
+import mage.constants.*;
+import mage.filter.StaticFilters;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+import mage.target.common.TargetCreaturePermanent;
+
+import java.util.UUID;
+
+public final class CowardKiller extends SplitCard {
+
+ public CowardKiller(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}", "{2}{R}{R}", SpellAbilityType.SPLIT);
+
+ // Coward
+ // Target creature can't block this turn and becomes a Coward in addition to its other types until end of turn.
+ // Time travel.
+
+ getLeftHalfCard().getSpellAbility().addEffect(new CantBlockTargetEffect(Duration.EndOfTurn));
+ getLeftHalfCard().getSpellAbility().addEffect(new BecomesCreatureTypeTargetEffect(Duration.EndOfTurn, SubType.COWARD, false).setText(" and becomes a Coward in addition to its other types until end of turn"));
+ getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
+ getLeftHalfCard().getSpellAbility().addEffect(new TimeTravelEffect().concatBy("
"));
+
+ // Killer
+ // Killer deals 3 damage to target creature and each other creature that shares a creature type with it.
+ getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
+ getRightHalfCard().getSpellAbility().addEffect(new KillerEffect());
+
+ }
+
+ private CowardKiller(final CowardKiller card) {
+ super(card);
+ }
+
+ @Override
+ public CowardKiller copy() {
+ return new CowardKiller(this);
+ }
+}
+
+class KillerEffect extends OneShotEffect {
+
+ KillerEffect() {
+ super(Outcome.Damage);
+ staticText = "{this} deals 3 damage to target creature and each other creature that shares a creature type with it";
+ }
+
+ private KillerEffect(final KillerEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Permanent target = game.getPermanent(targetPointer.getFirst(game, source));
+ if (target != null) {
+ target.damage(3, source, game);
+ for (Permanent p : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game)) {
+ if (!target.getId().equals(p.getId()) && p.shareCreatureTypes(game,target)) {
+ p.damage(3, source, game);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public KillerEffect copy() {
+ return new KillerEffect(this);
+ }
+}
diff --git a/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java b/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java
new file mode 100644
index 00000000000..9d8f6968abd
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java
@@ -0,0 +1,110 @@
+package mage.cards.n;
+
+import mage.MageInt;
+import mage.Mana;
+import mage.abilities.TriggeredAbilityImpl;
+import mage.abilities.condition.Condition;
+import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
+import mage.abilities.effects.Effect;
+import mage.abilities.effects.common.RollDieWithResultTableEffect;
+import mage.abilities.effects.mana.BasicManaEffect;
+import mage.cards.CardImpl;
+import mage.cards.CardSetInfo;
+import mage.constants.CardType;
+import mage.constants.ComparisonType;
+import mage.constants.SubType;
+import mage.constants.Zone;
+import mage.filter.common.FilterControlledCreaturePermanent;
+import mage.filter.predicate.mageobject.NamePredicate;
+import mage.game.Game;
+import mage.game.events.EntersTheBattlefieldEvent;
+import mage.game.events.GameEvent;
+import mage.game.permanent.Permanent;
+
+import java.util.UUID;
+
+/**
+ * This is the MTGO variant of the card
+ * Original announcement
+ * Scryfall link
+ * @author notgreat
+ */
+public final class NameStickerGoblin extends CardImpl {
+
+ public NameStickerGoblin(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
+
+ this.subtype.add(SubType.GOBLIN);
+ this.subtype.add(SubType.GUEST);
+ this.power = new MageInt(2);
+ this.toughness = new MageInt(2);
+
+
+ RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect();
+ // When this creature enters the battlefield from anywhere other than a graveyard or exile, if it’s
+ // on the battlefield and you control 9 or fewer creatures named “Name Sticker” Goblin, roll a 20-sided die.
+ // 1-6 | Add {R}{R}{R}{R}.
+ effect.addTableEntry(1, 6, new BasicManaEffect(Mana.RedMana(4)));
+ // 7-14 | Add {R}{R}{R}{R}{R}.
+ effect.addTableEntry(7, 14, new BasicManaEffect(Mana.RedMana(5)));
+ // 15-20 | Add {R}{R}{R}{R}{R}{R}.
+ effect.addTableEntry(15, 20, new BasicManaEffect(Mana.RedMana(6)));
+ this.addAbility(new NameStickerGoblinTriggeredAbility(effect));
+ }
+
+ private NameStickerGoblin(final NameStickerGoblin card) {
+ super(card);
+ }
+
+ @Override
+ public NameStickerGoblin copy() {
+ return new NameStickerGoblin(this);
+ }
+}
+
+class NameStickerGoblinTriggeredAbility extends TriggeredAbilityImpl {
+ private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent();
+ static{
+ filter.add(new NamePredicate("\"Name Sticker\" Goblin"));
+ }
+ private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.OR_LESS, 9);
+ NameStickerGoblinTriggeredAbility(Effect effect) {
+ super(Zone.BATTLEFIELD, effect);
+ setTriggerPhrase("When this creature enters the battlefield from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named \"Name Sticker\" Goblin, ");
+ }
+
+ private NameStickerGoblinTriggeredAbility(final NameStickerGoblinTriggeredAbility ability) {
+ super(ability);
+ }
+
+ @Override
+ public NameStickerGoblinTriggeredAbility copy() {
+ return new NameStickerGoblinTriggeredAbility(this);
+ }
+
+ @Override
+ public boolean checkEventType(GameEvent event, Game game) {
+ return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
+ }
+
+ @Override
+ public boolean checkTrigger(GameEvent event, Game game) {
+ EntersTheBattlefieldEvent zEvent = (EntersTheBattlefieldEvent) event;
+ if (zEvent == null) {
+ return false;
+ }
+ Permanent permanent = zEvent.getTarget();
+ if (permanent == null) {
+ return false;
+ }
+ Zone zone = zEvent.getFromZone();
+ return zone != Zone.GRAVEYARD && zone != Zone.EXILED
+ && permanent.getId().equals(getSourceId());
+ }
+
+ @Override
+ public boolean checkInterveningIfClause(Game game) {
+ Permanent permanent = getSourcePermanentIfItStillExists(game);
+ return permanent != null && condition.apply(game, this);
+ }
+}
diff --git a/Mage.Sets/src/mage/cards/t/ThijarianWitness.java b/Mage.Sets/src/mage/cards/t/ThijarianWitness.java
new file mode 100644
index 00000000000..28079712758
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/t/ThijarianWitness.java
@@ -0,0 +1,84 @@
+package mage.cards.t;
+
+import mage.MageInt;
+import mage.abilities.common.DiesCreatureTriggeredAbility;
+import mage.abilities.effects.common.ExileTargetEffect;
+import mage.abilities.effects.keyword.InvestigateEffect;
+import mage.abilities.keyword.FlashAbility;
+import mage.cards.CardImpl;
+import mage.cards.CardSetInfo;
+import mage.constants.CardType;
+import mage.constants.SubType;
+import mage.filter.FilterPermanent;
+import mage.filter.common.FilterCreaturePermanent;
+import mage.filter.predicate.Predicate;
+import mage.filter.predicate.mageobject.AnotherPredicate;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+import mage.watchers.common.AttackingBlockingDelayedWatcher;
+
+import java.util.UUID;
+
+/**
+ * @author notgreat
+ */
+public final class ThijarianWitness extends CardImpl {
+
+ private static final FilterPermanent filter
+ = new FilterCreaturePermanent("another creature");
+
+ static {
+ filter.add(AnotherPredicate.instance);
+ filter.add(WasAttackBlockAlonePredicate.instance);
+ }
+
+ public ThijarianWitness(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
+
+ this.subtype.add(SubType.ALIEN);
+ this.subtype.add(SubType.CLERIC);
+ this.power = new MageInt(0);
+ this.toughness = new MageInt(4);
+
+ // Flash
+ this.addAbility(FlashAbility.getInstance());
+
+ // Bear Witness -- Whenever another creature dies, if it was attacking or blocking alone, exile it and investigate.
+ DiesCreatureTriggeredAbility ability = new DiesCreatureTriggeredAbility(
+ new ExileTargetEffect().setText("if it was attacking or blocking alone, exile it"),
+ false, filter, true
+ );
+ ability.addEffect(new InvestigateEffect().concatBy("and"));
+ ability.withFlavorWord("Bear Witness");
+ ability.addWatcher(new AttackingBlockingDelayedWatcher());
+
+ this.addAbility(ability);
+ }
+
+ private ThijarianWitness(final ThijarianWitness card) {
+ super(card);
+ }
+
+ @Override
+ public ThijarianWitness copy() {
+ return new ThijarianWitness(this);
+ }
+}
+enum WasAttackBlockAlonePredicate implements Predicate {
+ instance;
+
+ @Override
+ public boolean apply(Permanent input, Game game) {
+ AttackingBlockingDelayedWatcher watcher = AttackingBlockingDelayedWatcher.getWatcher(game);
+ if (watcher == null) {
+ return false;
+ }
+ return (watcher.checkAttacker(input.getId()) && watcher.countAttackers() == 1)
+ || (watcher.checkBlocker(input.getId()) && watcher.countBlockers() == 1);
+ }
+
+ @Override
+ public String toString() {
+ return "attacking or blocking alone";
+ }
+}
diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java
index d448dae20df..8c8b624045d 100644
--- a/Mage.Sets/src/mage/sets/DoctorWho.java
+++ b/Mage.Sets/src/mage/sets/DoctorWho.java
@@ -48,6 +48,7 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("Clockwork Droid", 172, Rarity.UNCOMMON, mage.cards.c.ClockworkDroid.class));
cards.add(new SetCardInfo("Command Tower", 263, Rarity.COMMON, mage.cards.c.CommandTower.class));
cards.add(new SetCardInfo("Commander's Sphere", 240, Rarity.COMMON, mage.cards.c.CommandersSphere.class));
+ cards.add(new SetCardInfo("Coward // Killer", 77, Rarity.RARE, mage.cards.c.CowardKiller.class));
cards.add(new SetCardInfo("Crack in Time", 16, Rarity.RARE, mage.cards.c.CrackInTime.class));
cards.add(new SetCardInfo("Creeping Tar Pit", 267, Rarity.RARE, mage.cards.c.CreepingTarPit.class));
cards.add(new SetCardInfo("Crisis of Conscience", 17, Rarity.RARE, mage.cards.c.CrisisOfConscience.class));
@@ -230,6 +231,7 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("The Tenth Doctor", 446, Rarity.MYTHIC, mage.cards.t.TheTenthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Tenth Doctor", 561, Rarity.MYTHIC, mage.cards.t.TheTenthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Twelfth Doctor", 164, Rarity.RARE, mage.cards.t.TheTwelfthDoctor.class));
+ cards.add(new SetCardInfo("Thijarian Witness", 111, Rarity.UNCOMMON, mage.cards.t.ThijarianWitness.class));
cards.add(new SetCardInfo("Think Twice", 220, Rarity.COMMON, mage.cards.t.ThinkTwice.class));
cards.add(new SetCardInfo("Thought Vessel", 255, Rarity.UNCOMMON, mage.cards.t.ThoughtVessel.class));
cards.add(new SetCardInfo("Three Visits", 235, Rarity.UNCOMMON, mage.cards.t.ThreeVisits.class));
diff --git a/Mage.Sets/src/mage/sets/Unfinity.java b/Mage.Sets/src/mage/sets/Unfinity.java
index 4fa05a25004..cd97818af28 100644
--- a/Mage.Sets/src/mage/sets/Unfinity.java
+++ b/Mage.Sets/src/mage/sets/Unfinity.java
@@ -20,6 +20,7 @@ public final class Unfinity extends ExpansionSet {
this.hasBasicLands = true;
this.hasBoosters = false; // not likely to be able to drafts at any point
+ cards.add(new SetCardInfo("\"Name Sticker\" Goblin", "107m", Rarity.COMMON, mage.cards.n.NameStickerGoblin.class));
cards.add(new SetCardInfo("Attempted Murder", 66, Rarity.UNCOMMON, mage.cards.a.AttemptedMurder.class));
cards.add(new SetCardInfo("Blood Crypt", 279, Rarity.RARE, mage.cards.b.BloodCrypt.class));
cards.add(new SetCardInfo("Boing!", 40, Rarity.COMMON, mage.cards.b.Boing.class));
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/unf/NameStickerGoblinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/unf/NameStickerGoblinTest.java
new file mode 100644
index 00000000000..3e8383d888d
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/unf/NameStickerGoblinTest.java
@@ -0,0 +1,89 @@
+package org.mage.test.cards.single.unf;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import mage.counters.CounterType;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author notgreat
+ */
+public class NameStickerGoblinTest extends CardTestPlayerBase {
+
+ /**
+ * "Name Sticker" Goblin {2}{R}
+ * When this creature enters the battlefield from anywhere other than a graveyard or exile, if it’s on the battlefield and you control 9 or fewer creatures named “Name Sticker” Goblin, roll a 20-sided die.
+ * 1-6 | Add {R}{R}{R}{R}.
+ * 7-14 | Add {R}{R}{R}{R}{R}.
+ * 15-20 | Add {R}{R}{R}{R}{R}{R}.
+ */
+ private final static String nsgoblin = "\"Name Sticker\" Goblin";
+
+ @Test
+ public void testBasicETB() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.HAND, playerA, nsgoblin);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain",3);
+
+ setDieRollResult(playerA, 1);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nsgoblin, true);
+ checkManaPool("Mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 4);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ }
+ @Test
+ public void testGraveyardETB() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.GRAVEYARD, playerA, nsgoblin);
+ addCard(Zone.HAND, playerA, "Unearth");
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp");
+
+ //No dice roll since it's from graveyard
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unearth", nsgoblin, true);
+ checkManaPool("No Mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 0);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ }
+ @Test
+ public void testExileETB() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, nsgoblin);
+ addCard(Zone.HAND, playerA, "Cloudshift");
+ addCard(Zone.BATTLEFIELD, playerA, "Plains");
+
+ //No dice roll since it's from exile
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", nsgoblin, true);
+ checkManaPool("No mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 0);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ }
+ @Test
+ public void testNineETB() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, nsgoblin, 8);
+ addCard(Zone.HAND, playerA, nsgoblin, 3);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain",3);
+
+ setDieRollResult(playerA, 15);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nsgoblin, true);
+ checkManaPool("Mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 6);
+
+ //No dice roll since count > 9
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nsgoblin, true);
+ checkManaPool("No extra mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 3);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nsgoblin, true);
+ checkManaPool("No extra mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 0);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java
new file mode 100644
index 00000000000..3e86680602a
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java
@@ -0,0 +1,172 @@
+package org.mage.test.cards.single.who;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author notgreat
+ */
+public class ThijarianWitnessTest extends CardTestPlayerBase {
+
+ /**
+ * Thijarian Witness {1}{G}
+ * Creature - Alien Cleric
+ * Flash
+ * Bear Witness - Whenever another creature dies, if it was attacking or blocking alone, exile it and investigate.
+ * 0/4
+ */
+ private static final String witness = "Thijarian Witness";
+ private static final String clue = "Clue Token";
+ private static final String tiny = "Raging Goblin"; // 1/1
+ private static final String tiny2 = "Memnite"; // 1/1
+ private static final String big = "Alpine Grizzly"; // 4/2
+ private static final String kill = "Infernal Grasp"; //Also can test with Lightning Bolt
+
+ @Test
+ public void test_AttackingAloneAfterKill() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, tiny);
+ addCard(Zone.BATTLEFIELD, playerA, tiny2);
+ addCard(Zone.BATTLEFIELD, playerB, "Badlands", 4);
+ addCard(Zone.HAND, playerB, kill, 2);
+
+ attack(1, playerA, tiny);
+ attack(1, playerA, tiny2);
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, kill, tiny, true);
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, kill, tiny2, true);
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 1);
+ assertExileCount(playerA, 1);
+ }
+ @Test
+ public void test_BlockingAloneAfterKill() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, big);
+ addCard(Zone.BATTLEFIELD, playerA, "Badlands", 4);
+ addCard(Zone.HAND, playerA, kill);
+ addCard(Zone.BATTLEFIELD, playerB, tiny);
+ addCard(Zone.BATTLEFIELD, playerB, tiny2);
+
+ attack(1, playerA, big);
+ block(1, playerB, tiny, big);
+ block(1, playerB, tiny2, big);
+ castSpell(1, PhaseStep.DECLARE_BLOCKERS, playerA, kill, tiny2);
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 1);
+ assertPermanentCount(playerB, tiny, 0);
+ assertPermanentCount(playerA, big, 1);
+ assertExileCount(playerB, 1);
+ }
+ @Test
+ public void test_DoubleBlocked() {
+ //Auto-assign damage
+ //setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, big);
+ addCard(Zone.BATTLEFIELD, playerB, tiny);
+ addCard(Zone.BATTLEFIELD, playerB, tiny2);
+
+ attack(1, playerA, big);
+ block(1, playerB, tiny, big);
+ block(1, playerB, tiny2, big);
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 1);
+ assertPermanentCount(playerB, tiny, 0);
+ assertPermanentCount(playerA, big, 0);
+ assertExileCount(playerA, 1);
+ assertExileCount(playerB, 0);
+ }
+ @Test
+ public void test_DoubleBlocker() {
+ //Auto-assign damage
+ //setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, tiny);
+ addCard(Zone.BATTLEFIELD, playerA, tiny2);
+ addCard(Zone.BATTLEFIELD, playerB, "Night Market Guard");
+
+ attack(1, playerA, tiny);
+ attack(1, playerA, tiny2);
+ block(1, playerB, "Night Market Guard", tiny);
+ block(1, playerB, "Night Market Guard", tiny2);
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 1);
+ assertExileCount(playerA, 0);
+ assertExileCount(playerB, 1);
+ }
+ @Test
+ public void test_AttackAndBlock() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, tiny);
+ addCard(Zone.BATTLEFIELD, playerB, tiny);
+
+ attack(1, playerA, tiny);
+ block(1, playerB, tiny, tiny);
+ setChoice(playerA, ""); //stack triggers
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 2);
+ assertExileCount(playerA, 1);
+ assertExileCount(playerB, 1);
+ }
+ @Test
+ public void test_AttackMakesToken() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerA, "Skyknight Vanguard");
+ addCard(Zone.HAND, playerB, kill);
+ addCard(Zone.HAND, playerB, kill);
+ addCard(Zone.BATTLEFIELD, playerB, "Badlands", 4);
+
+ attack(1, playerA, "Skyknight Vanguard");
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, kill, "Skyknight Vanguard");
+ waitStackResolved(1, PhaseStep.DECLARE_ATTACKERS);
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, kill, "Soldier Token");
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 2);
+ assertExileCount(playerA, 1);
+ }
+ @Test
+ public void test_multipleWitness() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, witness);
+ addCard(Zone.BATTLEFIELD, playerB, witness);
+ addCard(Zone.BATTLEFIELD, playerA, tiny);
+ addCard(Zone.BATTLEFIELD, playerB, tiny);
+
+ attack(1, playerA, tiny);
+ block(1, playerB, tiny, tiny);
+ setChoice(playerA, ""); //stack triggers
+ setChoice(playerB, "");
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, clue, 2);
+ assertPermanentCount(playerB, clue, 2);
+ assertExileCount(playerA, 1);
+ assertExileCount(playerB, 1);
+ }
+}
diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java
index b0cdc055a78..33583eaa57a 100644
--- a/Mage/src/main/java/mage/MageObjectReference.java
+++ b/Mage/src/main/java/mage/MageObjectReference.java
@@ -100,6 +100,10 @@ public class MageObjectReference implements Comparable, Ser
}
}
+ @Override
+ public String toString(){
+ return "("+zoneChangeCounter+"|"+sourceId.toString().substring(0,3)+")";
+ }
public UUID getSourceId() {
return sourceId;
}
diff --git a/Mage/src/main/java/mage/watchers/common/AttackingBlockingDelayedWatcher.java b/Mage/src/main/java/mage/watchers/common/AttackingBlockingDelayedWatcher.java
new file mode 100644
index 00000000000..e5c2264ffab
--- /dev/null
+++ b/Mage/src/main/java/mage/watchers/common/AttackingBlockingDelayedWatcher.java
@@ -0,0 +1,70 @@
+package mage.watchers.common;
+
+import mage.constants.WatcherScope;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+import mage.watchers.Watcher;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Stores attacking/blocking combat information but is only updated
+ * 1) as damage is dealt (combat damage or otherwise)
+ * 2) as a spell or ability starts resolving
+ * 3) Land playing or special action taken (just in case that then causes a static effect to kill)
+ * Thus the information is available after any involved creatures die
+ * and all dying creatures can see all other creatures that were in combat at that time
+ * WARNING: This information is NOT to be used for static effects since the information will always be outdated.
+ * Use game.getCombat() directly or one of the other combat watchers instead
+ * @author notgreat
+ */
+public class AttackingBlockingDelayedWatcher extends Watcher {
+
+ private Set attackers = new HashSet<>();
+ private Set blockers = new HashSet<>();
+
+ public AttackingBlockingDelayedWatcher() {
+ super(WatcherScope.GAME);
+ }
+ @Override
+ public void watch(GameEvent event, Game game) {
+ switch (event.getType()) {
+ case LAND_PLAYED:
+ case TAKEN_SPECIAL_ACTION:
+ case RESOLVING_ABILITY:
+ case DAMAGED_BATCH_FOR_PERMANENTS:
+ //Note: getAttackers and getBlockers make a new Set, so this is safe to do
+ attackers = game.getCombat().getAttackers();
+ blockers = game.getCombat().getBlockers();
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ attackers.clear();
+ blockers.clear();
+ }
+
+ public boolean checkAttacker(UUID attacker) {
+ return attackers.contains(attacker);
+ }
+
+ public boolean checkBlocker(UUID blocker) {
+ return blockers.contains(blocker);
+ }
+
+ public long countBlockers() {
+ return blockers.size();
+ }
+
+ public long countAttackers() {
+ return attackers.size();
+ }
+
+ public static AttackingBlockingDelayedWatcher getWatcher(Game game) {
+ return game.getState().getWatcher(AttackingBlockingDelayedWatcher.class);
+ }
+}