diff --git a/Mage.Sets/src/mage/cards/b/BarracksOfTheThousand.java b/Mage.Sets/src/mage/cards/b/BarracksOfTheThousand.java new file mode 100644 index 00000000000..5a462272b46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarracksOfTheThousand.java @@ -0,0 +1,56 @@ +package mage.cards.b; + +import mage.abilities.common.CastSpellPaidBySourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.GnomeSoldierStarStarToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BarracksOfTheThousand extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an artifact or creature spell"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + public BarracksOfTheThousand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.LAND}, ""); + + this.supertype.add(SuperType.LEGENDARY); + + // (Transforms from Thousand Moons Smithy.) + this.nightCard = true; + + // {T}: Add {W}. + this.addAbility(new WhiteManaAbility()); + + // Whenever you cast an artifact or creature spell using mana produced by Barracks of the Thousand, create a white Gnome Soldier artifact creature token with "This creature's power and toughness are each equal to the number of artifacts and/or creatures you control." + this.addAbility(new CastSpellPaidBySourceTriggeredAbility( + new CreateTokenEffect(new GnomeSoldierStarStarToken()), + filter, false + )); + } + + private BarracksOfTheThousand(final BarracksOfTheThousand card) { + super(card); + } + + @Override + public BarracksOfTheThousand copy() { + return new BarracksOfTheThousand(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BrasssTunnelGrinder.java b/Mage.Sets/src/mage/cards/b/BrasssTunnelGrinder.java new file mode 100644 index 00000000000..30463be4cb5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrasssTunnelGrinder.java @@ -0,0 +1,97 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.DescendedThisTurnCondition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.DescendedThisTurnCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.players.Player; +import mage.watchers.common.DescendedWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BrasssTunnelGrinder extends CardImpl { + + public BrasssTunnelGrinder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); + this.secondSideCardClazz = mage.cards.t.TecutlanTheSearingRift.class; + + this.supertype.add(SuperType.LEGENDARY); + + // When Brass's Tunnel-Grinder enters the battlefield, discard any number of cards, then draw that many cards plus one. + this.addAbility(new EntersBattlefieldTriggeredAbility(new BrasssTunnelGrinderEffect())); + + // At the beginning of your end step, if you descended this turn, put a bore counter on Brass's Tunnel-Grinder. Then if there are three or more bore counters on it, remove those counters and transform it. + this.addAbility(new TransformAbility()); + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.BORE.createInstance()), + TargetController.YOU, DescendedThisTurnCondition.instance, false + ); + + ConditionalOneShotEffect secondCheck = new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.BORE), + new SourceHasCounterCondition(CounterType.BORE, 3, Integer.MAX_VALUE), + "Then if there are three or more bore counters on it, remove those counters and transform it" + ); + secondCheck.addEffect(new TransformSourceEffect()); + ability.addEffect(secondCheck); + ability.addHint(DescendedThisTurnCount.getHint()); + this.addAbility(ability, new DescendedWatcher()); + } + + private BrasssTunnelGrinder(final BrasssTunnelGrinder card) { + super(card); + } + + @Override + public BrasssTunnelGrinder copy() { + return new BrasssTunnelGrinder(this); + } +} + +class BrasssTunnelGrinderEffect extends OneShotEffect { + + BrasssTunnelGrinderEffect() { + super(Outcome.DrawCard); + staticText = "discard any number of cards, then draw that many cards plus one"; + } + + private BrasssTunnelGrinderEffect(final BrasssTunnelGrinderEffect effect) { + super(effect); + } + + @Override + public BrasssTunnelGrinderEffect copy() { + return new BrasssTunnelGrinderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + + int dicarded = player.discard(0, Integer.MAX_VALUE, false, source, game).size(); + player.drawCards(1 + dicarded, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TecutlanTheSearingRift.java b/Mage.Sets/src/mage/cards/t/TecutlanTheSearingRift.java new file mode 100644 index 00000000000..f9874232e46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TecutlanTheSearingRift.java @@ -0,0 +1,92 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.CastSpellPaidBySourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.DiscoverEffect; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.PermanentPredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TecutlanTheSearingRift extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a permanent spell"); + + static { + filter.add(PermanentPredicate.instance); + } + + public TecutlanTheSearingRift(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAVE); + + // (Transforms from Brass's Tunnel-Grinder.) + this.nightCard = true; + + // {T}: Add {R}. + this.addAbility(new RedManaAbility()); + + // Whenever you cast a permanent spell using mana produced by Tecutlan, the Searing Rift, discover X, where X is that spell's mana value. + this.addAbility(new CastSpellPaidBySourceTriggeredAbility( + new TecutlanTheSearingRiftEffect(), + filter, true + )); + } + + private TecutlanTheSearingRift(final TecutlanTheSearingRift card) { + super(card); + } + + @Override + public TecutlanTheSearingRift copy() { + return new TecutlanTheSearingRift(this); + } +} + +class TecutlanTheSearingRiftEffect extends OneShotEffect { + + TecutlanTheSearingRiftEffect() { + super(Outcome.Benefit); + staticText = "discover X, where X is that spell's mana value"; + } + + private TecutlanTheSearingRiftEffect(final TecutlanTheSearingRiftEffect effect) { + super(effect); + } + + @Override + public TecutlanTheSearingRiftEffect copy() { + return new TecutlanTheSearingRiftEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Spell spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source)); + int mv = spell == null ? 0 : Math.max(0, spell.getManaValue()); + + DiscoverEffect.doDiscover(controller, mv, game, source); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/ThousandMoonsSmithy.java b/Mage.Sets/src/mage/cards/t/ThousandMoonsSmithy.java new file mode 100644 index 00000000000..112ba0a0071 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThousandMoonsSmithy.java @@ -0,0 +1,68 @@ +package mage.cards.t; + +import mage.abilities.common.BeginningOfPreCombatMainTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.permanent.token.GnomeSoldierStarStarToken; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ThousandMoonsSmithy extends CardImpl { + + public static final FilterControlledPermanent filter = + new FilterControlledPermanent("untapped artifacts and/or creatures you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.ARTIFACT.getPredicate() + )); + } + + public ThousandMoonsSmithy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}{W}"); + this.secondSideCardClazz = mage.cards.b.BarracksOfTheThousand.class; + + this.supertype.add(SuperType.LEGENDARY); + + // When Thousand Moons Smithy enters the battlefield, create a white Gnome Soldier artifact creature token with "This creature's power and toughness are each equal to the number of artifacts and/or creatures you control." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new GnomeSoldierStarStarToken()))); + + // At the beginning of your precombat main phase, you may tap five untapped artifacts and/or creatures you control. If you do, transform Thousand Moons Smithy. + this.addAbility(new TransformAbility()); + this.addAbility(new BeginningOfPreCombatMainTriggeredAbility( + new DoIfCostPaid( + new TransformSourceEffect(), + new TapTargetCost(new TargetControlledPermanent(5, filter)) + ), + TargetController.YOU, + false + )); + } + + private ThousandMoonsSmithy(final ThousandMoonsSmithy card) { + super(card); + } + + @Override + public ThousandMoonsSmithy copy() { + return new ThousandMoonsSmithy(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index e5d0e73e0ca..ea734550b29 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -54,6 +54,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Another Chance", 90, Rarity.COMMON, mage.cards.a.AnotherChance.class)); cards.add(new SetCardInfo("Armored Kincaller", 174, Rarity.COMMON, mage.cards.a.ArmoredKincaller.class)); cards.add(new SetCardInfo("Attentive Sunscribe", 4, Rarity.COMMON, mage.cards.a.AttentiveSunscribe.class)); + cards.add(new SetCardInfo("Barracks of the Thousand", 39, Rarity.RARE, mage.cards.b.BarracksOfTheThousand.class)); cards.add(new SetCardInfo("Bartolome del Presidio", 224, Rarity.UNCOMMON, mage.cards.b.BartolomeDelPresidio.class)); cards.add(new SetCardInfo("Basking Capybara", 175, Rarity.COMMON, mage.cards.b.BaskingCapybara.class)); cards.add(new SetCardInfo("Bat Colony", 5, Rarity.UNCOMMON, mage.cards.b.BatColony.class)); @@ -67,6 +68,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Brackish Blunder", 46, Rarity.COMMON, mage.cards.b.BrackishBlunder.class)); cards.add(new SetCardInfo("Braided Net", 47, Rarity.RARE, mage.cards.b.BraidedNet.class)); cards.add(new SetCardInfo("Braided Quipu", 47, Rarity.RARE, mage.cards.b.BraidedQuipu.class)); + cards.add(new SetCardInfo("Brass's Tunnel-Grinder", 135, Rarity.RARE, mage.cards.b.BrasssTunnelGrinder.class)); cards.add(new SetCardInfo("Brazen Blademaster", 136, Rarity.COMMON, mage.cards.b.BrazenBlademaster.class)); cards.add(new SetCardInfo("Breeches, Eager Pillager", 137, Rarity.RARE, mage.cards.b.BreechesEagerPillager.class)); cards.add(new SetCardInfo("Bringer of the Last Gift", 94, Rarity.RARE, mage.cards.b.BringerOfTheLastGift.class)); @@ -310,6 +312,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Synapse Necromage", 125, Rarity.UNCOMMON, mage.cards.s.SynapseNecromage.class)); cards.add(new SetCardInfo("Tarrian's Soulcleaver", 264, Rarity.RARE, mage.cards.t.TarriansSoulcleaver.class)); cards.add(new SetCardInfo("Tectonic Hazard", 169, Rarity.COMMON, mage.cards.t.TectonicHazard.class)); + cards.add(new SetCardInfo("Tecutlan, the Searing Rift", 135, Rarity.RARE, mage.cards.t.TecutlanTheSearingRift.class)); cards.add(new SetCardInfo("Temple of Civilization", 26, Rarity.MYTHIC, mage.cards.t.TempleOfCivilization.class)); cards.add(new SetCardInfo("Temple of Cultivation", 204, Rarity.MYTHIC, mage.cards.t.TempleOfCultivation.class)); cards.add(new SetCardInfo("Temple of Cyclical Time", 67, Rarity.MYTHIC, mage.cards.t.TempleOfCyclicalTime.class)); @@ -327,6 +330,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("The Skullspore Nexus", 212, Rarity.MYTHIC, mage.cards.t.TheSkullsporeNexus.class)); cards.add(new SetCardInfo("Thousand Moons Crackshot", 37, Rarity.COMMON, mage.cards.t.ThousandMoonsCrackshot.class)); cards.add(new SetCardInfo("Thousand Moons Infantry", 38, Rarity.COMMON, mage.cards.t.ThousandMoonsInfantry.class)); + cards.add(new SetCardInfo("Thousand Moons Smithy", 39, Rarity.RARE, mage.cards.t.ThousandMoonsSmithy.class)); cards.add(new SetCardInfo("Thrashing Brontodon", 216, Rarity.UNCOMMON, mage.cards.t.ThrashingBrontodon.class)); cards.add(new SetCardInfo("Threefold Thunderhulk", 265, Rarity.RARE, mage.cards.t.ThreefoldThunderhulk.class)); cards.add(new SetCardInfo("Throne of the Grim Captain", 266, Rarity.RARE, mage.cards.t.ThroneOfTheGrimCaptain.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java new file mode 100644 index 00000000000..e8f4545b1e4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java @@ -0,0 +1,141 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BarracksOfTheThousandTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.ThousandMoonsSmithy} {2}{W}{W}
+ * Legendary Artifact
+ * When Thousand Moons Smithy enters the battlefield, create a white Gnome Soldier artifact creature token with “This creature’s power and toughness are each equal to the number of artifacts and/or creatures you control.”
+ * At the beginning of your precombat main phase, you may tap five untapped artifacts and/or creatures you control. If you do, transform Thousand Moons Smithy. + */ + private static final String smithy = "Thousand Moons Smithy"; + /** + * {@link mage.cards.b.BarracksOfTheThousand}
+ * Legendary Artifact Land
+ * {T}: Add {W}.
+ * Whenever you cast an artifact or creature spell using mana produced by Barracks of the Thousand, create a white Gnome Soldier artifact creature token with “This creature’s power and toughness are each equal to the number of artifacts and/or creatures you control.”
+ */ + private static final String barracks = "Barracks of the Thousand"; + + private void initToTransform() { + addCard(Zone.BATTLEFIELD, playerA, smithy); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Bear Cub"); + addCard(Zone.BATTLEFIELD, playerA, "Forest Bear"); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Runeclaw Bear"); + + // First mainphase, transform the smithy tapping all the bears + setChoice(playerA, true); // yes to "you may tap" + setChoice(playerA, "Balduvian Bears^Bear Cub^Forest Bear^Grizzly Bears^Runeclaw Bear"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + } + + @Test + public void trigger_simple() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, "Arcbound Worker", 1); + initToTransform(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Worker"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Gnome Soldier Token", 1); + assertPermanentCount(playerA, "Arcbound Worker", 1); + } + + @Test + public void trigger_onlyonce_doublemana() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Heartbeat of Spring"); + addCard(Zone.HAND, playerA, "Armored Warhorse"); + initToTransform(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Armored Warhorse"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Gnome Soldier Token", 1); + assertPermanentCount(playerA, "Armored Warhorse", 1); + } + + + @Test + public void noTrigger_NotPaidWithBarrack() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, "Memnite", 1); + initToTransform(); + + // Memnite cost 0 so no mana is spend from Barracks. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Gnome Soldier Token", 0); + assertPermanentCount(playerA, "Memnite", 1); + } + + @Test + public void noTrigger_notCheck() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.HAND, playerA, "Divination", 1); + initToTransform(); + + // Sorcery, doesn't trigger Barrack + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Divination"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Gnome Soldier Token", 0); + assertGraveyardCount(playerA, "Divination", 1); + } + + @Test + public void noTrigger_afterRemand() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerB, "Remand"); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + addCard(Zone.HAND, playerA, "Arcbound Worker"); + addCard(Zone.HAND, playerA, "Plains", 1); // to cast the second time + initToTransform(); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Worker"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", "Arcbound Worker", "Arcbound Worker"); + + checkGraveyardCount("Remand in graveyard", 3, PhaseStep.BEGIN_COMBAT, playerB, "Remand", 1); + checkHandCardCount("Worker in hand", 3, PhaseStep.BEGIN_COMBAT, playerA, "Arcbound Worker", 1); + checkPermanentCount("Gnome Soldier Token on battlefield", 3, PhaseStep.BEGIN_COMBAT, playerA, "Gnome Soldier Token", 1); + + playLand(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Plains"); + waitStackResolved(3, PhaseStep.POSTCOMBAT_MAIN); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Arcbound Worker"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Gnome Soldier Token", 1); + assertPermanentCount(playerA, "Arcbound Worker", 1); + assertGraveyardCount(playerB, "Remand", 1); + } + +} diff --git a/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java new file mode 100644 index 00000000000..501f68e9c74 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java @@ -0,0 +1,77 @@ +package mage.abilities.common; + + +import mage.MageObjectReference; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.ManaPaidObjectSourceWatcher; + +/** + * @author Susucr + */ +public class CastSpellPaidBySourceTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterSpell filter; + private final boolean setTargetPointer; + + public CastSpellPaidBySourceTriggeredAbility(Effect effect, FilterSpell filter, boolean setTargetPointer) { + super(Zone.BATTLEFIELD, effect); + setTriggerPhrase("Whenever you cast " + filter.getMessage() + " using mana produced by {this}, "); + addWatcher(new ManaPaidObjectSourceWatcher()); + + this.filter = filter; + this.setTargetPointer = setTargetPointer; + } + + protected CastSpellPaidBySourceTriggeredAbility(final CastSpellPaidBySourceTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public CastSpellPaidBySourceTriggeredAbility copy() { + return new CastSpellPaidBySourceTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!controllerId.equals(event.getPlayerId())) { + return false; + } + + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null || !this.filter.match(spell, controllerId, this, game)) { + return false; + } + + ManaPaidObjectSourceWatcher watcher = game.getState().getWatcher(ManaPaidObjectSourceWatcher.class); + if (watcher == null) { + return false; + } + + if (!watcher.checkManaFromSourceWasUsedToPay( + new MageObjectReference(sourceId, game), + new MageObjectReference(spell.getSourceId(), game) + )) { + return false; + } + + if (setTargetPointer) { + this.getAllEffects().setTargetPointer(new FixedTarget(spell.getId(), game)); + } + + return true; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 12ffa77128c..b96ae1e0c2b 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -27,6 +27,7 @@ public enum CounterType { BLOOD("blood"), BLOODLINE("bloodline"), BOOK("book"), + BORE("bore"), BOUNTY("bounty"), BRIBERY("bribery"), BRICK("brick"), diff --git a/Mage/src/main/java/mage/game/permanent/token/GnomeSoldierStarStarToken.java b/Mage/src/main/java/mage/game/permanent/token/GnomeSoldierStarStarToken.java new file mode 100644 index 00000000000..26ce636116f --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/GnomeSoldierStarStarToken.java @@ -0,0 +1,49 @@ +package mage.game.permanent.token; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; + +/** + * @author Susucr + */ +public final class GnomeSoldierStarStarToken extends TokenImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + + public GnomeSoldierStarStarToken() { + super("Gnome Soldier Token", "white Gnome Soldier artifact creature token with " + + "\"this creature's power and toughness are each equal to the number of artifacts and/or creatures you control.\""); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + subtype.add(SubType.GNOME); + subtype.add(SubType.SOLDIER); + color.setWhite(true); + + this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessSourceEffect( + xValue + ).setText("this creature's power and toughness are each equal to the number of artifacts and/or creatures you control"))); + } + + protected GnomeSoldierStarStarToken(final GnomeSoldierStarStarToken token) { + super(token); + } + + public GnomeSoldierStarStarToken copy() { + return new GnomeSoldierStarStarToken(this); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/ManaPaidObjectSourceWatcher.java b/Mage/src/main/java/mage/watchers/common/ManaPaidObjectSourceWatcher.java new file mode 100644 index 00000000000..70e3717f976 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/ManaPaidObjectSourceWatcher.java @@ -0,0 +1,54 @@ +package mage.watchers.common; + +import mage.MageObject; +import mage.MageObjectReference; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ManaPaidEvent; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author Susucr + */ +public class ManaPaidObjectSourceWatcher extends Watcher { + + // what is paid -> set of all MageObject sources used to pay for the mana. + private final Map> payMap = new HashMap<>(); + + public ManaPaidObjectSourceWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.MANA_PAID) { + return; + } + + ManaPaidEvent manaEvent = (ManaPaidEvent) event; + UUID paid = manaEvent.getSourcePaidId(); + MageObject sourceObject = manaEvent.getSourceObject(); + if (paid == null || sourceObject == null) { + return; + } + + payMap + .computeIfAbsent(new MageObjectReference(paid, game), x -> new HashSet<>()) + .add(new MageObjectReference(sourceObject, game)); + } + + public boolean checkManaFromSourceWasUsedToPay(MageObjectReference sourceOfMana, MageObjectReference paidObject) { + return payMap + .getOrDefault(paidObject, Collections.emptySet()) + .contains(sourceOfMana); + } + + @Override + public void reset() { + payMap.clear(); + super.reset(); + } +}