diff --git a/Mage.Sets/src/mage/cards/a/AnalyzeThePollen.java b/Mage.Sets/src/mage/cards/a/AnalyzeThePollen.java new file mode 100644 index 00000000000..bf651e6d329 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnalyzeThePollen.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AxebaneFerox.java b/Mage.Sets/src/mage/cards/a/AxebaneFerox.java new file mode 100644 index 00000000000..8af2f813505 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AxebaneFerox.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrimestopperSprite.java b/Mage.Sets/src/mage/cards/c/CrimestopperSprite.java new file mode 100644 index 00000000000..b43455b924a --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrimestopperSprite.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EvidenceExaminer.java b/Mage.Sets/src/mage/cards/e/EvidenceExaminer.java new file mode 100644 index 00000000000..218b53802fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvidenceExaminer.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForensicResearcher.java b/Mage.Sets/src/mage/cards/f/ForensicResearcher.java new file mode 100644 index 00000000000..6f5be4559b9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForensicResearcher.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java b/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java new file mode 100644 index 00000000000..87bf630d97c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SampleCollector.java b/Mage.Sets/src/mage/cards/s/SampleCollector.java new file mode 100644 index 00000000000..6c84497632e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SampleCollector.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurveillanceMonitor.java b/Mage.Sets/src/mage/cards/s/SurveillanceMonitor.java new file mode 100644 index 00000000000..28ee7e57891 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SurveillanceMonitor.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java b/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java new file mode 100644 index 00000000000..7358884883b --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java @@ -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( + "
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)); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VituGhaziInspector.java b/Mage.Sets/src/mage/cards/v/VituGhaziInspector.java new file mode 100644 index 00000000000..b24c87c26d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VituGhaziInspector.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java index 175ba3c7492..3cf00e2bbc2 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java @@ -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)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java new file mode 100644 index 00000000000..659c4bdb6be --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java @@ -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); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/RealitySmasherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/RealitySmasherTest.java index 826af8c88e3..bf68ce301a8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/RealitySmasherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/RealitySmasherTest.java @@ -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(); diff --git a/Mage/src/main/java/mage/abilities/common/CollectEvidenceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CollectEvidenceTriggeredAbility.java new file mode 100644 index 00000000000..4474fb8a6ca --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CollectEvidenceTriggeredAbility.java @@ -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()); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java new file mode 100644 index 00000000000..9ff596d1e75 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java @@ -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"; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java new file mode 100644 index 00000000000..83242ad81a5 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java index c020c65bd18..f45ac670e47 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java @@ -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); diff --git a/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java b/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java new file mode 100644 index 00000000000..13e9d5a29a2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java @@ -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) 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; + } +} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 9e43083a098..a1640d142b6 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -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 } diff --git a/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java b/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java new file mode 100644 index 00000000000..5837ce32ebb --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 9cc6767be7a..af9fb494478 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -74,7 +74,7 @@ public final class CardUtil { public static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); private static final List 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 + * @return */ public static T deepCopyObject(T value) { if (isImmutableObject(value)) {