mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[UNF] "Name Sticker" Goblin, [WHO] Coward // Killer, [WHO] Thijarian Witness (#11392)
* Add [WHO] Coward // Killer * Add MTGO version of [UNF] "Name Sticker" Goblin * Implement [WHO] Thijarian Witness * Add NameStickerGoblinTest * Fix Thijarian Witness, add tests (may need additional tests). Also adds a simple toString for MageObjectReference * Don't spam the java garbage collector, add another test * Replace non-ASCII characters in card text * improve MOR toString * Thijarian Witness fixed better, add AttackingBlockingWatcher in common * cleanup from xenohedron's review * Fix test, add warning not to use AttackingBlockingWatcher for static effects * rename AttackingBlockingWatcher to AttackingBlockingDelayedWatcher to make it more obvious how it should be used, minor documentation changes Simplify and rename Thijarian Witness Predicate * add null checks
This commit is contained in:
parent
50d5b7ce9b
commit
38adbb4ae5
10 changed files with 615 additions and 10 deletions
|
|
@ -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;
|
||||
|
|
|
|||
79
Mage.Sets/src/mage/cards/c/CowardKiller.java
Normal file
79
Mage.Sets/src/mage/cards/c/CowardKiller.java
Normal file
|
|
@ -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("<br>"));
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
110
Mage.Sets/src/mage/cards/n/NameStickerGoblin.java
Normal file
110
Mage.Sets/src/mage/cards/n/NameStickerGoblin.java
Normal file
|
|
@ -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
|
||||
* <a href="https://www.mtgo.com/news/mtgo-woe-8292023#unfinity">Original announcement</a>
|
||||
* <a href="https://scryfall.com/card/unf/107m/name-sticker-goblin">Scryfall link</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
84
Mage.Sets/src/mage/cards/t/ThijarianWitness.java
Normal file
84
Mage.Sets/src/mage/cards/t/ThijarianWitness.java
Normal file
|
|
@ -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<Permanent> {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +100,10 @@ public class MageObjectReference implements Comparable<MageObjectReference>, Ser
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "("+zoneChangeCounter+"|"+sourceId.toString().substring(0,3)+")";
|
||||
}
|
||||
public UUID getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID> attackers = new HashSet<>();
|
||||
private Set<UUID> 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue