Implementing "collect evidence" mechanic (#11671)

* [MKM] Implement Axebane Ferox

* add exile to cost, fix text

* add targeting message copied from crew ability

* [MKM] Implement Forensic Researcher

* [MKM] Implement Izoni, Center of the Web

* implement requested changes

* merge fix

* [MKM] Implement Sample Collector

* [MKM] Implement Evidence Examiner

* [MKM] Implement Surveillance Monitor

* [MKM] Implement Vitu-Ghazi Inspector

* [MKM] Implement Crimestopper Sprite

* [MKM] Implement Urgent Necropsy

* [MKM] Implement Analyze the Pollen

* implement requested changes

* add can pay cost check to counter unless pays effect

* fix test failure

* add tests

* fix prompt message
This commit is contained in:
Evan Kranzler 2024-01-27 20:26:14 -05:00 committed by GitHub
parent 322c49e37f
commit 99c2ffa231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1162 additions and 4 deletions

View file

@ -0,0 +1,44 @@
package mage.cards.a;
import mage.abilities.condition.common.CollectedEvidenceCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.keyword.CollectEvidenceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AnalyzeThePollen extends CardImpl {
public AnalyzeThePollen(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}");
// As an additional cost to cast this spell, you may collect evidence 8.
this.addAbility(new CollectEvidenceAbility(8));
// Search your library for a basic land card. If evidence was collected, instead search your library for a creature or land card. Reveal that card, put it into your hand, then shuffle.
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_CREATURE_OR_LAND), true),
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true),
CollectedEvidenceCondition.instance, "search your library for a basic land card. " +
"If evidence was collected, instead search your library for a creature or land card. " +
"Reveal that card, put it into your hand, then shuffle"
));
}
private AnalyzeThePollen(final AnalyzeThePollen card) {
super(card);
}
@Override
public AnalyzeThePollen copy() {
return new AnalyzeThePollen(this);
}
}

View file

@ -0,0 +1,45 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.WardAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AxebaneFerox extends CardImpl {
public AxebaneFerox(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}");
this.subtype.add(SubType.BEAST);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// Haste
this.addAbility(HasteAbility.getInstance());
// Ward--Collect evidence 4.
this.addAbility(new WardAbility(new CollectEvidenceCost(4)));
}
private AxebaneFerox(final AxebaneFerox card) {
super(card);
}
@Override
public AxebaneFerox copy() {
return new AxebaneFerox(this);
}
}

View file

@ -0,0 +1,58 @@
package mage.cards.c;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.CollectedEvidenceCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.TapTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.CollectEvidenceAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class CrimestopperSprite extends CardImpl {
public CrimestopperSprite(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
this.subtype.add(SubType.FAERIE);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// As an additional cost to cast this spell, you may collect evidence 6.
this.addAbility(new CollectEvidenceAbility(6));
// Flying
this.addAbility(FlyingAbility.getInstance());
// When Crimestopper Sprite enters the battlefield, tap target creature. If evidence was collected, put a stun counter on it.
Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect());
ability.addTarget(new TargetCreaturePermanent());
ability.addEffect(new ConditionalOneShotEffect(
new AddCountersTargetEffect(CounterType.STUN.createInstance()),
CollectedEvidenceCondition.instance, "If evidence was collected, put a stun counter on it"
));
this.addAbility(ability);
}
private CrimestopperSprite(final CrimestopperSprite card) {
super(card);
}
@Override
public CrimestopperSprite copy() {
return new CrimestopperSprite(this);
}
}

View file

@ -0,0 +1,48 @@
package mage.cards.e;
import mage.MageInt;
import mage.abilities.common.BeginningOfCombatTriggeredAbility;
import mage.abilities.common.CollectEvidenceTriggeredAbility;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.keyword.InvestigateEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TargetController;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class EvidenceExaminer extends CardImpl {
public EvidenceExaminer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}");
this.subtype.add(SubType.MERFOLK);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// At the beginning of combat on your turn, you may collect evidence 4.
this.addAbility(new BeginningOfCombatTriggeredAbility(
new DoIfCostPaid(null, new CollectEvidenceCost(4)),
TargetController.YOU, false
));
// Whenever you collect evidence, investigate.
this.addAbility(new CollectEvidenceTriggeredAbility(new InvestigateEffect(), false));
}
private EvidenceExaminer(final EvidenceExaminer card) {
super(card);
}
@Override
public EvidenceExaminer copy() {
return new EvidenceExaminer(this);
}
}

View file

@ -0,0 +1,52 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.TapTargetEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ForensicResearcher extends CardImpl {
public ForensicResearcher(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
this.subtype.add(SubType.MERFOLK);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// {T}: Untap another target permanent you control.
Ability ability = new SimpleActivatedAbility(new UntapTargetEffect(), new TapSourceCost());
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_ANOTHER_TARGET_PERMANENT));
this.addAbility(ability);
// {T}, Collect evidence 3: Tap target creature you don't control.
ability = new SimpleActivatedAbility(new TapTargetEffect(), new TapSourceCost());
ability.addCost(new CollectEvidenceCost(3));
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL));
this.addAbility(ability);
}
private ForensicResearcher(final ForensicResearcher card) {
super(card);
}
@Override
public ForensicResearcher copy() {
return new ForensicResearcher(this);
}
}

View file

@ -0,0 +1,66 @@
package mage.cards.i;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.game.permanent.token.IzoniSpiderToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class IzoniCenterOfTheWeb extends CardImpl {
public IzoniCenterOfTheWeb(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.ELF);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(5);
this.toughness = new MageInt(4);
// Menace
this.addAbility(new MenaceAbility());
// Whenever Izoni, Center of the Web enters the battlefield or attacks, you may collect evidence 4. If you do, create two 2/1 black and green Spider creature tokens with menace and reach.
this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DoIfCostPaid(
new CreateTokenEffect(new IzoniSpiderToken(), 2),
new CollectEvidenceCost(4)
)));
// Sacrifice four tokens: Surveil 2, then draw two cards. You gain 2 life.
Ability ability = new SimpleActivatedAbility(
new SurveilEffect(2, false),
new SacrificeTargetCost(4, StaticFilters.FILTER_PERMANENT_TOKEN)
);
ability.addEffect(new DrawCardSourceControllerEffect(2).concatBy(", then"));
ability.addEffect(new GainLifeEffect(2));
this.addAbility(ability);
}
private IzoniCenterOfTheWeb(final IzoniCenterOfTheWeb card) {
super(card);
}
@Override
public IzoniCenterOfTheWeb copy() {
return new IzoniCenterOfTheWeb(this);
}
}

View file

@ -0,0 +1,49 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.effects.common.DoWhenCostPaid;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class SampleCollector extends CardImpl {
public SampleCollector(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.TROLL);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Whenever Sample Collector attacks, you may collect evidence 3. When you do, put a +1/+1 counter on target creature you control.
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false
);
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(new AttacksTriggeredAbility(new DoWhenCostPaid(
ability, new CollectEvidenceCost(3), "Collect evidence 3?"
)));
}
private SampleCollector(final SampleCollector card) {
super(card);
}
@Override
public SampleCollector copy() {
return new SampleCollector(this);
}
}

View file

@ -0,0 +1,49 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.common.CollectEvidenceTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.game.permanent.token.ThopterColorlessToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class SurveillanceMonitor extends CardImpl {
public SurveillanceMonitor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}");
this.subtype.add(SubType.VEDALKEN);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// When Surveillance Monitor enters the battlefield, you may collect evidence 4.
this.addAbility(new EntersBattlefieldTriggeredAbility(
new DoIfCostPaid(null, new CollectEvidenceCost(4))
));
// Whenever you collect evidence, create a 1/1 colorless Thopter artifact creature token with flying.
this.addAbility(new CollectEvidenceTriggeredAbility(
new CreateTokenEffect(new ThopterColorlessToken()), false
));
}
private SurveillanceMonitor(final SurveillanceMonitor card) {
super(card);
}
@Override
public SurveillanceMonitor copy() {
return new SurveillanceMonitor(this);
}
}

View file

@ -0,0 +1,76 @@
package mage.cards.u;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.game.Game;
import mage.target.Target;
import mage.target.common.TargetArtifactPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetEnchantmentPermanent;
import mage.target.common.TargetPlaneswalkerPermanent;
import mage.target.targetpointer.EachTargetPointer;
import java.util.Collection;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class UrgentNecropsy extends CardImpl {
public UrgentNecropsy(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}{G}");
// As an additional cost to cast this spell, collect evidence X, where X is the total mana value of the permanents this spell targets.
this.getSpellAbility().addEffect(new InfoEffect(
"As an additional cost to cast this spell, collect evidence X, " +
"where X is the total mana value of the permanents this spell targets."
));
this.getSpellAbility().setCostAdjuster(UrgentNecropsyAdjuster.instance);
// Destroy up to one target artifact, up to one target creature, up to one target enchantment, and up to one target planeswalker.
this.getSpellAbility().addEffect(new DestroyTargetEffect(
"<br>Destroy up to one target artifact, up to one target creature, " +
"up to one target enchantment, and up to one target planeswalker"
).setTargetPointer(new EachTargetPointer()));
this.getSpellAbility().addTarget(new TargetArtifactPermanent(0, 1));
this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1));
this.getSpellAbility().addTarget(new TargetEnchantmentPermanent(0, 1));
this.getSpellAbility().addTarget(new TargetPlaneswalkerPermanent(0, 1));
}
private UrgentNecropsy(final UrgentNecropsy card) {
super(card);
}
@Override
public UrgentNecropsy copy() {
return new UrgentNecropsy(this);
}
}
enum UrgentNecropsyAdjuster implements CostAdjuster {
instance;
@Override
public void adjustCosts(Ability ability, Game game) {
int xValue = ability
.getTargets()
.stream()
.map(Target::getTargets)
.flatMap(Collection::stream)
.map(game::getPermanent)
.filter(Objects::nonNull)
.mapToInt(MageObject::getManaValue)
.sum();
ability.addCost(new CollectEvidenceCost(xValue));
}
}

View file

@ -0,0 +1,59 @@
package mage.cards.v;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.CollectedEvidenceCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.CollectEvidenceAbility;
import mage.abilities.keyword.ReachAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class VituGhaziInspector extends CardImpl {
public VituGhaziInspector(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
this.subtype.add(SubType.ELF);
this.subtype.add(SubType.DETECTIVE);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// As an additional cost to cast this spell, you may collect evidence 6.
this.addAbility(new CollectEvidenceAbility(6));
// Reach
this.addAbility(ReachAbility.getInstance());
// When Vitu-Ghazi Inspector enters the battlefield, if evidence was collected, put a +1/+1 counter on target creature and you gain 2 life.
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())),
CollectedEvidenceCondition.instance, "When {this} enters the battlefield, if evidence was " +
"collected, put a +1/+1 counter on target creature and you gain 2 life."
);
ability.addEffect(new GainLifeEffect(2));
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
}
private VituGhaziInspector(final VituGhaziInspector card) {
super(card);
}
@Override
public VituGhaziInspector copy() {
return new VituGhaziInspector(this);
}
}

View file

@ -33,11 +33,13 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Airtight Alibi", 149, Rarity.COMMON, mage.cards.a.AirtightAlibi.class));
cards.add(new SetCardInfo("Alley Assailant", 76, Rarity.COMMON, mage.cards.a.AlleyAssailant.class));
cards.add(new SetCardInfo("Alquist Proft, Master Sleuth", 185, Rarity.MYTHIC, mage.cards.a.AlquistProftMasterSleuth.class));
cards.add(new SetCardInfo("Analyze the Pollen", 150, Rarity.RARE, mage.cards.a.AnalyzeThePollen.class));
cards.add(new SetCardInfo("Anzrag, the Quake-Mole", 186, Rarity.MYTHIC, mage.cards.a.AnzragTheQuakeMole.class));
cards.add(new SetCardInfo("Assassin's Trophy", 187, Rarity.RARE, mage.cards.a.AssassinsTrophy.class));
cards.add(new SetCardInfo("Audience with Trostani", 152, Rarity.RARE, mage.cards.a.AudienceWithTrostani.class));
cards.add(new SetCardInfo("Aurelia, the Law Above", 188, Rarity.RARE, mage.cards.a.AureliaTheLawAbove.class));
cards.add(new SetCardInfo("Auspicious Arrival", 5, Rarity.COMMON, mage.cards.a.AuspiciousArrival.class));
cards.add(new SetCardInfo("Axebane Ferox", 153, Rarity.RARE, mage.cards.a.AxebaneFerox.class));
cards.add(new SetCardInfo("Barbed Servitor", 77, Rarity.RARE, mage.cards.b.BarbedServitor.class));
cards.add(new SetCardInfo("Basilica Stalker", 78, Rarity.COMMON, mage.cards.b.BasilicaStalker.class));
cards.add(new SetCardInfo("Benthic Criminologists", 40, Rarity.COMMON, mage.cards.b.BenthicCriminologists.class));
@ -54,6 +56,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Concealed Weapon", 117, Rarity.UNCOMMON, mage.cards.c.ConcealedWeapon.class));
cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class));
cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class));
cards.add(new SetCardInfo("Crimestopper Sprite", 49, Rarity.COMMON, mage.cards.c.CrimestopperSprite.class));
cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class));
cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class));
cards.add(new SetCardInfo("Curious Inquiry", 51, Rarity.UNCOMMON, mage.cards.c.CuriousInquiry.class));
@ -71,12 +74,14 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Eliminate the Impossible", 54, Rarity.UNCOMMON, mage.cards.e.EliminateTheImpossible.class));
cards.add(new SetCardInfo("Escape Tunnel", 261, Rarity.COMMON, mage.cards.e.EscapeTunnel.class));
cards.add(new SetCardInfo("Essence of Antiquity", 15, Rarity.UNCOMMON, mage.cards.e.EssenceOfAntiquity.class));
cards.add(new SetCardInfo("Evidence Examiner", 201, Rarity.UNCOMMON, mage.cards.e.EvidenceExaminer.class));
cards.add(new SetCardInfo("Exit Specialist", 55, Rarity.UNCOMMON, mage.cards.e.ExitSpecialist.class));
cards.add(new SetCardInfo("Ezrim, Agency Chief", 202, Rarity.RARE, mage.cards.e.EzrimAgencyChief.class));
cards.add(new SetCardInfo("Fae Flight", 56, Rarity.UNCOMMON, mage.cards.f.FaeFlight.class));
cards.add(new SetCardInfo("Faerie Snoop", 203, Rarity.COMMON, mage.cards.f.FaerieSnoop.class));
cards.add(new SetCardInfo("Fanatical Strength", 159, Rarity.COMMON, mage.cards.f.FanaticalStrength.class));
cards.add(new SetCardInfo("Festerleech", 85, Rarity.UNCOMMON, mage.cards.f.Festerleech.class));
cards.add(new SetCardInfo("Forensic Researcher", 58, Rarity.UNCOMMON, mage.cards.f.ForensicResearcher.class));
cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Forum Familiar", 16, Rarity.UNCOMMON, mage.cards.f.ForumFamiliar.class));
cards.add(new SetCardInfo("Furtive Courier", 59, Rarity.UNCOMMON, mage.cards.f.FurtiveCourier.class));
@ -103,6 +108,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Inside Source", 19, Rarity.COMMON, mage.cards.i.InsideSource.class));
cards.add(new SetCardInfo("Insidious Roots", 208, Rarity.UNCOMMON, mage.cards.i.InsidiousRoots.class));
cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Izoni, Center of the Web", 209, Rarity.RARE, mage.cards.i.IzoniCenterOfTheWeb.class));
cards.add(new SetCardInfo("Jaded Analyst", 62, Rarity.COMMON, mage.cards.j.JadedAnalyst.class));
cards.add(new SetCardInfo("Knife", 134, Rarity.UNCOMMON, mage.cards.k.Knife.class));
cards.add(new SetCardInfo("Kraul Whipcracker", 213, Rarity.UNCOMMON, mage.cards.k.KraulWhipcracker.class));
@ -159,6 +165,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Rubblebelt Braggart", 143, Rarity.COMMON, mage.cards.r.RubblebeltBraggart.class));
cards.add(new SetCardInfo("Rubblebelt Maverick", 174, Rarity.COMMON, mage.cards.r.RubblebeltMaverick.class));
cards.add(new SetCardInfo("Rune-Brand Juggler", 229, Rarity.UNCOMMON, mage.cards.r.RuneBrandJuggler.class));
cards.add(new SetCardInfo("Sample Collector", 175, Rarity.UNCOMMON, mage.cards.s.SampleCollector.class));
cards.add(new SetCardInfo("Sanctuary Wall", 32, Rarity.UNCOMMON, mage.cards.s.SanctuaryWall.class));
cards.add(new SetCardInfo("Sanguine Savior", 230, Rarity.COMMON, mage.cards.s.SanguineSavior.class));
cards.add(new SetCardInfo("Sanitation Automaton", 256, Rarity.COMMON, mage.cards.s.SanitationAutomaton.class));
@ -176,6 +183,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Steamcore Scholar", 71, Rarity.RARE, mage.cards.s.SteamcoreScholar.class));
cards.add(new SetCardInfo("Sudden Setback", 72, Rarity.UNCOMMON, mage.cards.s.SuddenSetback.class));
cards.add(new SetCardInfo("Sumala Sentry", 233, Rarity.UNCOMMON, mage.cards.s.SumalaSentry.class));
cards.add(new SetCardInfo("Surveillance Monitor", 73, Rarity.UNCOMMON, mage.cards.s.SurveillanceMonitor.class));
cards.add(new SetCardInfo("Suspicious Detonation", 145, Rarity.COMMON, mage.cards.s.SuspiciousDetonation.class));
cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("The Chase Is On", 116, Rarity.COMMON, mage.cards.t.TheChaseIsOn.class));
@ -192,9 +200,11 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Undercover Crocodelf", 239, Rarity.COMMON, mage.cards.u.UndercoverCrocodelf.class));
cards.add(new SetCardInfo("Underground Mortuary", 271, Rarity.RARE, mage.cards.u.UndergroundMortuary.class));
cards.add(new SetCardInfo("Undergrowth Recon", 181, Rarity.MYTHIC, mage.cards.u.UndergrowthRecon.class));
cards.add(new SetCardInfo("Urgent Necropsy", 240, Rarity.MYTHIC, mage.cards.u.UrgentNecropsy.class));
cards.add(new SetCardInfo("Vein Ripper", 110, Rarity.MYTHIC, mage.cards.v.VeinRipper.class));
cards.add(new SetCardInfo("Vengeful Creeper", 182, Rarity.COMMON, mage.cards.v.VengefulCreeper.class));
cards.add(new SetCardInfo("Vengeful Tracker", 147, Rarity.UNCOMMON, mage.cards.v.VengefulTracker.class));
cards.add(new SetCardInfo("Vitu-Ghazi Inspector", 183, Rarity.COMMON, mage.cards.v.VituGhaziInspector.class));
cards.add(new SetCardInfo("Voja, Jaws of the Conclave", 432, Rarity.MYTHIC, mage.cards.v.VojaJawsOfTheConclave.class));
cards.add(new SetCardInfo("Warleader's Call", 242, Rarity.RARE, mage.cards.w.WarleadersCall.class));
cards.add(new SetCardInfo("Wispdrinker Vampire", 243, Rarity.UNCOMMON, mage.cards.w.WispdrinkerVampire.class));

View file

@ -0,0 +1,294 @@
package org.mage.test.cards.cost.additional;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class CollectEvidenceTest extends CardTestPlayerBase {
private static final String ferox = "Axebane Ferox";
private static final String murder = "Murder";
private static final String elemental = "Earth Elemental";
private static final String giant = "Hill Giant";
private static final String ogre = "Gray Ogre";
private static final String piker = "Goblin Piker";
private static final String raiders = "Mons's Goblin Raiders";
private static final String effigy = "Fuming Effigy";
private static final String sprite = "Crimestopper Sprite";
private static final String monitor = "Surveillance Monitor";
@Test
public void testNoPay() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertPermanentCount(playerB, ferox, 1);
}
@Test
public void testPayWith5() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, elemental);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, elemental);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, elemental, 1);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith411() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, ogre);
addCard(Zone.GRAVEYARD, playerA, raiders);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, ogre);
setChoice(playerA, raiders);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, ogre, 1);
assertExileCount(playerA, raiders, 1);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith4() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, giant);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, giant);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, giant, 1);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith31() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, ogre);
addCard(Zone.GRAVEYARD, playerA, raiders);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, ogre);
setChoice(playerA, raiders);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, ogre, 1);
assertExileCount(playerA, raiders, 1);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith22() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, piker, 2);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, piker, 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, piker, 2);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith211() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, piker);
addCard(Zone.GRAVEYARD, playerA, raiders, 2);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, piker);
setChoice(playerA, raiders, 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, piker, 1);
assertExileCount(playerA, raiders, 2);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testPayWith1111() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, raiders, 4);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, raiders, 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, raiders, 4);
assertPermanentCount(playerB, ferox, 0);
}
@Test
public void testFumingEffigy() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, effigy);
addCard(Zone.HAND, playerA, murder);
addCard(Zone.GRAVEYARD, playerA, raiders, 4);
addCard(Zone.BATTLEFIELD, playerB, ferox);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, ferox);
setChoice(playerA, true);
setChoice(playerA, raiders, 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, murder, 1);
assertExileCount(playerA, raiders, 4);
assertPermanentCount(playerB, ferox, 0);
assertLife(playerB, 20 - 1);
}
@Test
public void testSpriteNoPay() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.HAND, playerA, sprite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sprite);
addTarget(playerA, sprite);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertTapped(sprite, true);
assertCounterCount(sprite, CounterType.STUN, 0);
}
@Test
public void testSpritePay() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.HAND, playerA, sprite);
addCard(Zone.GRAVEYARD, playerA, ogre, 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sprite);
setChoice(playerA, true);
setChoice(playerA, ogre, 2);
addTarget(playerA, sprite);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertTapped(sprite, true);
assertCounterCount(sprite, CounterType.STUN, 1);
}
@Test
public void testMonitorTrigger() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
addCard(Zone.HAND, playerA, monitor);
addCard(Zone.GRAVEYARD, playerA, giant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, monitor);
setChoice(playerA, true);
setChoice(playerA, giant);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Thopter Token", 1);
}
@Test
public void testMonitorTriggerTwice() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 4 + 3);
addCard(Zone.HAND, playerA, monitor);
addCard(Zone.HAND, playerA, sprite);
addCard(Zone.GRAVEYARD, playerA, giant);
addCard(Zone.GRAVEYARD, playerA, ogre, 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, monitor);
setChoice(playerA, true);
setChoice(playerA, giant);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Thopter Token", 1);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, sprite);
setChoice(playerA, true);
setChoice(playerA, ogre, 2);
addTarget(playerA, sprite);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Thopter Token", 2);
assertTapped(sprite, true);
assertCounterCount(sprite, CounterType.STUN, 1);
}
}

View file

@ -47,7 +47,6 @@ public class RealitySmasherTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade");
addTarget(playerB, "Reality Smasher");
setChoice(playerB, false); // no discard
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();

View file

@ -0,0 +1,37 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author TheElk801
*/
public class CollectEvidenceTriggeredAbility extends TriggeredAbilityImpl {
public CollectEvidenceTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
setTriggerPhrase("Whenever you collect evidence, ");
}
private CollectEvidenceTriggeredAbility(final CollectEvidenceTriggeredAbility ability) {
super(ability);
}
@Override
public CollectEvidenceTriggeredAbility copy() {
return new CollectEvidenceTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.EVIDENCE_COLLECTED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId());
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.CollectEvidenceAbility;
import mage.game.Game;
import mage.util.CardUtil;
/**
* Checks if the spell was cast with the alternate collect evidence cost
*
* @author TheElk801
*/
public enum CollectedEvidenceCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return CardUtil.checkSourceCostsTagExists(game, source, CollectEvidenceAbility.COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY);
}
@Override
public String toString() {
return "Evidence was collected";
}
}

View file

@ -0,0 +1,99 @@
package mage.abilities.costs.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.hint.HintUtils;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInYourGraveyard;
import java.awt.*;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
public class CollectEvidenceCost extends CostImpl {
private final int amount;
public CollectEvidenceCost(int amount) {
super();
this.amount = amount;
this.text = "collect evidence " + amount;
}
private CollectEvidenceCost(final CollectEvidenceCost cost) {
super(cost);
this.amount = cost.amount;
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Player player = game.getPlayer(controllerId);
return player != null && player
.getGraveyard()
.getCards(game)
.stream()
.filter(Objects::nonNull)
.mapToInt(MageObject::getManaValue)
.sum() >= amount;
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player == null) {
paid = false;
return paid;
}
// TODO: require target to have minimum selected total mana value (requires refactor)
Target target = new TargetCardInYourGraveyard(1, Integer.MAX_VALUE) {
@Override
public String getMessage() {
// shows selected mana value
int totalMV = this
.getTargets()
.stream()
.map(game::getCard)
.filter(Objects::nonNull)
.mapToInt(MageObject::getManaValue)
.sum();
return super.getMessage() + HintUtils.prepareText(
" (selected mana value " + totalMV + " of " + amount + ")",
totalMV >= amount ? Color.GREEN : Color.RED
);
}
}.withNotTarget(true);
player.choose(Outcome.Exile, target, source, game);
Cards cards = new CardsImpl(target.getTargets());
paid = cards
.getCards(game)
.stream()
.filter(Objects::nonNull)
.mapToInt(MageObject::getManaValue)
.sum() >= amount;
if (paid) {
player.moveCards(cards, Zone.EXILED, source, game);
game.fireEvent(GameEvent.getEvent(
GameEvent.EventType.EVIDENCE_COLLECTED,
source.getSourceId(), source, source.getControllerId(), amount
));
}
return paid;
}
@Override
public CollectEvidenceCost copy() {
return new CollectEvidenceCost(this);
}
}

View file

@ -84,7 +84,8 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
message += costValueMessage + '?';
costToPay.clearPaid();
if (!(player.chooseUse(Outcome.Benefit, message, source, game)
if (!(costToPay.canPay(source, source, player.getId(), game)
&& player.chooseUse(Outcome.Benefit, message, source, game)
&& costToPay.pay(source, game, source, spell.getControllerId(), false, null))) {
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the counter effect");
game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD);

View file

@ -0,0 +1,103 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.condition.common.CollectedEvidenceCondition;
import mage.abilities.costs.*;
import mage.abilities.costs.common.CollectEvidenceCost;
import mage.abilities.hint.ConditionTrueHint;
import mage.abilities.hint.Hint;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
/**
* @author TheElk801
*/
public class CollectEvidenceAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
private static final String promptString = "Collect evidence ";
private static final String keywordText = "As an additional cost to cast this spell, you may collect evidence ";
private static final String reminderText = "Exile cards with total mana value $$$ or greater from your graveyard";
private final String rule;
private final int amount;
public static final String COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY = "collectEvidenceActivation";
protected OptionalAdditionalCost additionalCost;
private static final Hint hint = new ConditionTrueHint(CollectedEvidenceCondition.instance, "evidence was collected");
public static OptionalAdditionalCost makeCost(int amount) {
OptionalAdditionalCost cost = new OptionalAdditionalCostImpl(
keywordText + amount,
reminderText.replace("$$$", "" + amount),
new CollectEvidenceCost(amount)
);
cost.setRepeatable(false);
return cost;
}
public CollectEvidenceAbility(int amount) {
super(Zone.STACK, null);
this.additionalCost = makeCost(amount);
this.rule = additionalCost.getName() + ' ' + additionalCost.getReminderText();
this.setRuleAtTheTop(true);
this.addHint(hint);
this.amount = amount;
}
private CollectEvidenceAbility(final CollectEvidenceAbility ability) {
super(ability);
this.rule = ability.rule;
this.additionalCost = ability.additionalCost.copy();
this.amount = ability.amount;
}
@Override
public CollectEvidenceAbility copy() {
return new CollectEvidenceAbility(this);
}
public void resetCost() {
if (additionalCost != null) {
additionalCost.reset();
}
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (!(ability instanceof SpellAbility)) {
return;
}
Player player = game.getPlayer(ability.getControllerId());
if (player == null) {
return;
}
this.resetCost();
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
if (!canPay || !player.chooseUse(Outcome.Exile, promptString + amount + '?', ability, game)) {
return;
}
additionalCost.activate();
for (Cost cost : ((Costs<Cost>) additionalCost)) {
ability.getCosts().add(cost.copy());
}
ability.setCostsTag(COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY, null);
}
@Override
public String getCastMessageSuffix() {
return additionalCost.getCastSuffixMessage(0);
}
@Override
public String getRule() {
return rule;
}
}

View file

@ -573,6 +573,12 @@ public class GameEvent implements Serializable {
playerId the player suspecting
*/
BECOME_SUSPECTED,
/* Evidence collected
targetId same as sourceId
sourceId of the ability for the cost
playerId the player paying the cost
*/
EVIDENCE_COLLECTED,
//custom events
CUSTOM_EVENT
}

View file

@ -0,0 +1,37 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.keyword.MenaceAbility;
import mage.abilities.keyword.ReachAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class IzoniSpiderToken extends TokenImpl {
public IzoniSpiderToken() {
super("Spider Token", "2/1 black and green Spider creature token with menace and reach");
cardType.add(CardType.CREATURE);
color.setBlack(true);
color.setGreen(true);
subtype.add(SubType.SPIDER);
power = new MageInt(2);
toughness = new MageInt(1);
// Menace
this.addAbility(new MenaceAbility());
// Reach
this.addAbility(ReachAbility.getInstance());
}
private IzoniSpiderToken(final IzoniSpiderToken token) {
super(token);
}
public IzoniSpiderToken copy() {
return new IzoniSpiderToken(this);
}
}

View file

@ -74,7 +74,7 @@ public final class CardUtil {
public static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
private static final List<String> costWords = Arrays.asList(
"put", "return", "exile", "discard", "sacrifice", "remove", "tap", "reveal", "pay"
"put", "return", "exile", "discard", "sacrifice", "remove", "tap", "reveal", "pay", "collect"
);
public static final int TESTS_SET_CODE_LOOKUP_LENGTH = 6; // search set code in commands like "set_code-card_name"
@ -1764,8 +1764,8 @@ public final class CardUtil {
* Warning, don't use self reference objects because it will raise StackOverflowError
*
* @param value
* @return
* @param <T>
* @return
*/
public static <T> T deepCopyObject(T value) {
if (isImmutableObject(value)) {