From 81ef8da46e75dcf4dbe7e488d22dcf637ad9cb07 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sat, 1 Jun 2024 00:44:30 +0200 Subject: [PATCH] implement [MH3] Dog Umbra ; fix [BRO] Mishra's Domination --- Mage.Sets/src/mage/cards/c/CageOfHands.java | 43 +-------- Mage.Sets/src/mage/cards/d/DogUmbra.java | 92 +++++++++++++++++++ .../src/mage/cards/m/MishrasDomination.java | 11 ++- .../src/mage/cards/r/RecumbentBliss.java | 41 +-------- Mage.Sets/src/mage/sets/ModernHorizons3.java | 1 + .../single/bro/MishraDominationTest.java | 81 ++++++++++++++++ .../test/cards/single/mh3/DogUmbraTest.java | 64 +++++++++++++ 7 files changed, 251 insertions(+), 82 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/d/DogUmbra.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/bro/MishraDominationTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/DogUmbraTest.java diff --git a/Mage.Sets/src/mage/cards/c/CageOfHands.java b/Mage.Sets/src/mage/cards/c/CageOfHands.java index 864c18b3366..be4883ae37e 100644 --- a/Mage.Sets/src/mage/cards/c/CageOfHands.java +++ b/Mage.Sets/src/mage/cards/c/CageOfHands.java @@ -4,15 +4,13 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -36,10 +34,10 @@ public final class CageOfHands extends CardImpl { this.addAbility(ability); // Enchanted creature can't attack or block. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CageOfHandsEffect())); + this.addAbility(new SimpleStaticAbility(new CantAttackBlockAttachedEffect(AttachmentType.AURA))); // {1}{W}: Return Cage of Hands to its owner's hand. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(true), new ManaCostsImpl<>("{1}{W}"))); + this.addAbility(new SimpleActivatedAbility(new ReturnToHandSourceEffect(true), new ManaCostsImpl<>("{1}{W}"))); } private CageOfHands(final CageOfHands card) { @@ -50,37 +48,4 @@ public final class CageOfHands extends CardImpl { public CageOfHands copy() { return new CageOfHands(this); } -} - -class CageOfHandsEffect extends RestrictionEffect { - - CageOfHandsEffect() { - super(Duration.WhileOnBattlefield); - staticText = "Enchanted creature can't attack or block"; - } - - private CageOfHandsEffect(final CageOfHandsEffect effect) { - super(effect); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getAttachments().contains((source.getSourceId())); - } - - @Override - public boolean canAttack(Game game, boolean canUseChooseDialogs) { - return false; - } - - @Override - public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) { - return false; - } - - @Override - public CageOfHandsEffect copy() { - return new CageOfHandsEffect(this); - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DogUmbra.java b/Mage.Sets/src/mage/cards/d/DogUmbra.java new file mode 100644 index 00000000000..beb456caa5e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DogUmbra.java @@ -0,0 +1,92 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRestrictionEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.TotemArmorAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DogUmbra extends CardImpl { + + public DogUmbra(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // As long as another player controls enchanted creature, it can't attack or block. Otherwise, Dog Umbra has umbra armor. + Ability ability = new SimpleStaticAbility(new ConditionalRestrictionEffect( + new CantAttackBlockAttachedEffect(AttachmentType.AURA), + DogUmbraCondition.TRUE, + "As long as another player controls enchanted creature, it can't attack or block." + )); + ability.addEffect(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(new TotemArmorAbility()), + DogUmbraCondition.FALSE, + "Otherwise, Dog Umbra has umbra armor" + )); + this.addAbility(ability); + } + + private DogUmbra(final DogUmbra card) { + super(card); + } + + @Override + public DogUmbra copy() { + return new DogUmbra(this); + } +} + +enum DogUmbraCondition implements Condition { + TRUE(true), + FALSE(false); + private final boolean value; + + DogUmbraCondition(boolean value) { + this.value = value; + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanentOrLKIBattlefield) + .map(Controllable::getControllerId) + .map(source::isControlledBy) + .orElse(false) + .equals(!value); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MishrasDomination.java b/Mage.Sets/src/mage/cards/m/MishrasDomination.java index b57f53eb100..b46b1e7d1ca 100644 --- a/Mage.Sets/src/mage/cards/m/MishrasDomination.java +++ b/Mage.Sets/src/mage/cards/m/MishrasDomination.java @@ -6,21 +6,21 @@ import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.combat.CantBlockSourceEffect; +import mage.abilities.effects.common.combat.CantBlockAttachedEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; import mage.game.Controllable; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; -import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -46,7 +46,7 @@ public final class MishrasDomination extends CardImpl { "as long as you control enchanted creature, it gets +2/+2" )); ability.addEffect(new ConditionalRestrictionEffect( - new CantBlockSourceEffect(Duration.WhileOnBattlefield), + new CantBlockAttachedEffect(AttachmentType.AURA), MishrasDominationCondition.FALSE, "otherwise, it can't block" )); this.addAbility(ability); @@ -75,7 +75,8 @@ enum MishrasDominationCondition implements Condition { public boolean apply(Game game, Ability source) { return Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) + .map(Permanent::getAttachedTo) + .map(game::getPermanentOrLKIBattlefield) .map(Controllable::getControllerId) .map(source::isControlledBy) .orElse(false) diff --git a/Mage.Sets/src/mage/cards/r/RecumbentBliss.java b/Mage.Sets/src/mage/cards/r/RecumbentBliss.java index 62dfc643bfc..7bd942ce88d 100644 --- a/Mage.Sets/src/mage/cards/r/RecumbentBliss.java +++ b/Mage.Sets/src/mage/cards/r/RecumbentBliss.java @@ -3,15 +3,13 @@ package mage.cards.r; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -31,7 +29,7 @@ public final class RecumbentBliss extends CardImpl { this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); Ability ability = new EnchantAbility(auraTarget); this.addAbility(ability); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new RecumbentBlissEffect())); + this.addAbility(new SimpleStaticAbility(new CantAttackBlockAttachedEffect(AttachmentType.AURA))); this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GainLifeEffect(1), TargetController.YOU, true)); } @@ -43,37 +41,4 @@ public final class RecumbentBliss extends CardImpl { public RecumbentBliss copy() { return new RecumbentBliss(this); } -} - -class RecumbentBlissEffect extends RestrictionEffect { - - RecumbentBlissEffect() { - super(Duration.WhileOnBattlefield); - staticText = "Enchanted creature can't attack or block"; - } - - private RecumbentBlissEffect(final RecumbentBlissEffect effect) { - super(effect); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getAttachments().contains((source.getSourceId())); - } - - @Override - public boolean canAttack(Game game, boolean canUseChooseDialogs) { - return false; - } - - @Override - public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) { - return false; - } - - @Override - public RecumbentBlissEffect copy() { - return new RecumbentBlissEffect(this); - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index b61c69ba3ba..3540e3ad6d3 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -69,6 +69,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Devourer of Destiny", 2, Rarity.RARE, mage.cards.d.DevourerOfDestiny.class)); cards.add(new SetCardInfo("Disciple of Freyalise", 250, Rarity.UNCOMMON, mage.cards.d.DiscipleOfFreyalise.class)); cards.add(new SetCardInfo("Distinguished Conjurer", 264, Rarity.UNCOMMON, mage.cards.d.DistinguishedConjurer.class)); + cards.add(new SetCardInfo("Dog Umbra", 22, Rarity.COMMON, mage.cards.d.DogUmbra.class)); cards.add(new SetCardInfo("Dreadmobile", 87, Rarity.UNCOMMON, mage.cards.d.Dreadmobile.class)); cards.add(new SetCardInfo("Dreamdrinker Vampire", 88, Rarity.COMMON, mage.cards.d.DreamdrinkerVampire.class)); cards.add(new SetCardInfo("Dreamtide Whale", 59, Rarity.RARE, mage.cards.d.DreamtideWhale.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/MishraDominationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/MishraDominationTest.java new file mode 100644 index 00000000000..87e4d6e7ef5 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/MishraDominationTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.single.bro; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class MishraDominationTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.m.MishrasDomination Mishra's Domination} {1}{R} + * Enchantment — Aura + * Enchant creature + * As long as you control enchanted creature, it gets +2/+2. Otherwise, it can’t block. + */ + private static final String domination = "Mishra's Domination"; + + @Test + public void test_Boost() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + addCard(Zone.HAND, playerA, domination); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, domination, "Memnite"); + attack(2, playerB, "Grizzly Bears", playerA); + block(2, playerA, "Memnite", "Grizzly Bears"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPowerToughness(playerA, "Memnite", 3, 3); + assertGraveyardCount(playerB, "Grizzly Bears", 1); + } + + @Test + public void test_Threaten_Boosted() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + addCard(Zone.HAND, playerA, domination); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Threaten"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, domination, "Grizzly Bears", true); + checkPT("no change in PT", 1, PhaseStep.BEGIN_COMBAT, playerB, "Grizzly Bears", 2, 2); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Threaten", "Grizzly Bears", true); + checkPT("change in PT after control change", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 4, 4); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_CantBlock() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + addCard(Zone.HAND, playerA, domination); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, domination, "Grizzly Bears"); + + checkPT("no change in PT", 1, PhaseStep.BEGIN_COMBAT, playerB, "Grizzly Bears", 2, 2); + attack(1, playerA, "Memnite", playerB); + block(1, playerB, "Grizzly Bears", "Memnite"); // invalid block + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 1); + assertPowerToughness(playerA, "Memnite", 1, 1); + assertPowerToughness(playerB, "Grizzly Bears", 2, 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/DogUmbraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/DogUmbraTest.java new file mode 100644 index 00000000000..6c654c9da7f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/DogUmbraTest.java @@ -0,0 +1,64 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DogUmbraTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DogUmbra Dog Umbra} {1}{W} + * Flash + * Enchant creature + * As long as another player controls enchanted creature, it can’t attack or block. Otherwise, Dog Umbra has umbra armor. + */ + private static final String umbra = "Dog Umbra"; + + @Test + public void test_Umbra() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.HAND, playerA, umbra); + addCard(Zone.HAND, playerA, "Murder"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, umbra, "Memnite", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder", "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Murder", 1); + assertGraveyardCount(playerA, umbra, 1); + assertGraveyardCount(playerA, "Memnite", 0); + assertPermanentCount(playerA, "Memnite", 1); + } + + @Test + public void test_Not_Umbra() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.HAND, playerA, umbra); + addCard(Zone.HAND, playerA, "Murder"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, umbra, "Memnite", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder", "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Murder", 1); + assertGraveyardCount(playerA, umbra, 1); + assertGraveyardCount(playerB, "Memnite", 1); + assertPermanentCount(playerB, "Memnite", 0); + } +}