From 2d8be6663b10f08aca8cf79396cd84a449d61a24 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 15 Jul 2021 23:46:19 +0400 Subject: [PATCH 01/48] [AFR] added card icons with class level info (#7808) --- .../card/icons/prepared/hexagon-fill.svg | 7 +++++ .../src/main/java/mage/view/CardView.java | 2 +- .../src/main/java/mage/abilities/Ability.java | 13 ++++++++ .../main/java/mage/abilities/AbilityImpl.java | 7 ++++- .../mage/abilities/icon/CardIconImpl.java | 4 ++- .../mage/abilities/icon/CardIconType.java | 1 + .../keyword/ClassReminderAbility.java | 31 +++++++++++++++++++ .../java/mage/game/stack/StackAbility.java | 5 +++ 8 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 Mage.Client/src/main/resources/card/icons/prepared/hexagon-fill.svg diff --git a/Mage.Client/src/main/resources/card/icons/prepared/hexagon-fill.svg b/Mage.Client/src/main/resources/card/icons/prepared/hexagon-fill.svg new file mode 100644 index 00000000000..2f310a22d62 --- /dev/null +++ b/Mage.Client/src/main/resources/card/icons/prepared/hexagon-fill.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 79afbb0a8af..c76b68a24db 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -416,7 +416,7 @@ public class CardView extends SimpleCardView { // card icons for permanents on battlefield permanent.getAbilities(game).forEach(ability -> { - this.cardIcons.addAll(ability.getIcons()); + this.cardIcons.addAll(ability.getIcons(game)); }); } else { if (card.isCopy()) { diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 610d8785130..c4f6cda3509 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -552,8 +552,21 @@ public interface Ability extends Controllable, Serializable { Ability addHint(Hint hint); + /** + * For abilities with static icons + * + * @return + */ List getIcons(); + /** + * For abilities with dynamic icons + * + * @param game can be null for static calls like copies + * @return + */ + List getIcons(Game game); + Ability addIcon(CardIcon cardIcon); Ability addCustomOutcome(Outcome customOutcome); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 1fc78638a48..a3546704389 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1342,7 +1342,12 @@ public abstract class AbilityImpl implements Ability { } @Override - public List getIcons() { + final public List getIcons() { + return getIcons(null); + } + + @Override + public List getIcons(Game game) { return this.icons; } diff --git a/Mage/src/main/java/mage/abilities/icon/CardIconImpl.java b/Mage/src/main/java/mage/abilities/icon/CardIconImpl.java index 89318cb15a2..4ea64fc84a9 100644 --- a/Mage/src/main/java/mage/abilities/icon/CardIconImpl.java +++ b/Mage/src/main/java/mage/abilities/icon/CardIconImpl.java @@ -1,9 +1,11 @@ package mage.abilities.icon; +import java.io.Serializable; + /** * @author JayDi85 */ -public class CardIconImpl implements CardIcon { +public class CardIconImpl implements CardIcon, Serializable { private final CardIconType cardIconType; private final String text; diff --git a/Mage/src/main/java/mage/abilities/icon/CardIconType.java b/Mage/src/main/java/mage/abilities/icon/CardIconType.java index c4e8c1111ab..42e3b0160ea 100644 --- a/Mage/src/main/java/mage/abilities/icon/CardIconType.java +++ b/Mage/src/main/java/mage/abilities/icon/CardIconType.java @@ -29,6 +29,7 @@ public enum CardIconType { ABILITY_INFECT("prepared/flask.svg", CardIconCategory.ABILITY, 100), ABILITY_INDESTRUCTIBLE("prepared/ankh.svg", CardIconCategory.ABILITY, 100), ABILITY_VIGILANCE("prepared/eye.svg", CardIconCategory.ABILITY, 100), + ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100), // SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog diff --git a/Mage/src/main/java/mage/abilities/keyword/ClassReminderAbility.java b/Mage/src/main/java/mage/abilities/keyword/ClassReminderAbility.java index b9964bf564a..b5fd4b85b9f 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ClassReminderAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ClassReminderAbility.java @@ -2,7 +2,15 @@ package mage.abilities.keyword; import mage.abilities.StaticAbility; import mage.abilities.hint.common.ClassLevelHint; +import mage.abilities.icon.CardIcon; +import mage.abilities.icon.CardIconImpl; +import mage.abilities.icon.CardIconType; import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.ArrayList; +import java.util.List; /** * @author TheElk801 @@ -27,4 +35,27 @@ public class ClassReminderAbility extends StaticAbility { public String getRule() { return "(Gain the next level as a sorcery to add its ability.)"; } + + @Override + public List getIcons(Game game) { + if (game == null) { + return this.icons; + } + + // dynamic GUI icon with current level + List res = new ArrayList<>(); + Permanent permanent = this.getSourcePermanentOrLKI(game); + if (permanent == null) { + return res; + } + + CardIcon levelIcon = new CardIconImpl( + CardIconType.ABILITY_CLASS_LEVEL, + "Current class level: " + permanent.getClassLevel(), + String.valueOf(permanent.getClassLevel()) + ); + res.add(levelIcon); + + return res; + } } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 61a3969b13f..3a6585c0bd9 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -692,6 +692,11 @@ public class StackAbility extends StackObjectImpl implements Ability { return this.ability.getIcons(); } + @Override + public List getIcons(Game game) { + return this.ability.getIcons(game); + } + @Override public Ability addIcon(CardIcon cardIcon) { throw new IllegalArgumentException("Stack ability is not supports icon adding"); From ad7eb55460e19fc13bc570adbabd3141cc616635 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 16 Jul 2021 00:13:27 +0400 Subject: [PATCH 02/48] [AFR] fixed empty class level up logs, added levelled logs (#7808) --- .../mage/abilities/keyword/ClassLevelAbility.java | 11 ++++++++++- Mage/src/main/java/mage/game/permanent/Permanent.java | 6 ++++++ .../main/java/mage/game/permanent/PermanentImpl.java | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/keyword/ClassLevelAbility.java b/Mage/src/main/java/mage/abilities/keyword/ClassLevelAbility.java index 3061e6d8fd2..b317a9be51a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ClassLevelAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ClassLevelAbility.java @@ -61,6 +61,7 @@ class SetClassLevelEffect extends OneShotEffect { SetClassLevelEffect(int level) { super(Outcome.Benefit); this.level = level; + staticText = "level up to " + level; } private SetClassLevelEffect(final SetClassLevelEffect effect) { @@ -76,9 +77,17 @@ class SetClassLevelEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent == null || !permanent.setClassLevel(level)) { + if (permanent == null) { return false; } + + int oldLevel = permanent.getClassLevel(); + if (!permanent.setClassLevel(level)) { + return false; + } + + game.informPlayers(permanent.getLogName() + " levelled up from " + oldLevel + " to " + permanent.getClassLevel()); + game.fireEvent(GameEvent.getEvent( GameEvent.EventType.GAINS_CLASS_LEVEL, source.getSourceId(), source, source.getControllerId(), level diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index afb3725c4b8..ded11c011ba 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -75,6 +75,12 @@ public interface Permanent extends Card, Controllable { int getClassLevel(); + /** + * Level up to next level. + * + * @param classLevel + * @return false on wrong settings (e.g. level up to multiple levels) + */ boolean setClassLevel(int classLevel); void setCardNumber(String cid); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 355c92e4880..b903450cc1a 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1528,6 +1528,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public boolean setClassLevel(int classLevel) { + // can level up to next (+1) level only if (this.classLevel == classLevel - 1) { this.classLevel = classLevel; return true; From d2f2578cc4da4e9506f9e7b7fe72368fa1e30ecd Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Thu, 15 Jul 2021 16:15:58 -0500 Subject: [PATCH 03/48] - Added test unit for Ranar the Ever-Watchful --- .../triggers/RanarTheEverWatchfulTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/RanarTheEverWatchfulTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RanarTheEverWatchfulTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RanarTheEverWatchfulTest.java new file mode 100644 index 00000000000..10c4525c411 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RanarTheEverWatchfulTest.java @@ -0,0 +1,90 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.triggers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author jeffwadsworth + */ +public class RanarTheEverWatchfulTest extends CardTestPlayerBase { + + @Test + public void testRanarWithCurseOfTheSwine() { + setStrictChooseMode(true); + // Curse of the Swine targets 4 creatures; 2 non-tokens and 2 tokens with Ranar, the Ever-Watchful on the battlefield + // Result: 2 boar tokens and 1 spirit token for the playerA. 2 boar tokens for playerB + + addCard(Zone.BATTLEFIELD, playerA, "Ranar the Ever-Watchful", 1); + + /* + Whenever one or more cards are put into exile from your hand or a spell or ability you control exiles one or + more permanents from the battlefield, create a 1/1 white Spirit creature token with flying. + */ + + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); // Creature 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Serra Angel", 1); // Creature 4/4 Flyer + addCard(Zone.BATTLEFIELD, playerA, "Bog Imp", 1); // Creature 2/1 Flyer + addCard(Zone.BATTLEFIELD, playerB, "Sengir Vampire", 1); // Creature 4/4 Flyer + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); // handle mana for Curse of the Swine + addCard(Zone.BATTLEFIELD, playerB, "Plains", 10); // handle mana for Call the Calvary + addCard(Zone.HAND, playerB, "Call the Cavalry", 1); // generate 2 tokens for playerB + addCard(Zone.HAND, playerA, "Curse of the Swine", 1); // exile target cards and create a boar token for each one exiled + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Call the Cavalry"); // create 2 Knight tokens + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of the Swine"); + setChoice(playerA, "X=6"); + addTarget(playerA, "Memnite"); + addTarget(playerA, "Serra Angel"); + addTarget(playerA, "Bog Imp"); + addTarget(playerA, "Sengir Vampire"); + addTarget(playerA, "Knight"); + addTarget(playerA, "Knight"); + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Boar", 3); // created token by Curse + assertPermanentCount(playerA, "Spirit", 1); // created token by Ranar + assertPermanentCount(playerB, "Boar", 3); // created token by Curse + assertPermanentCount(playerB, "Spirit", 0); // no Spirit token for playerB + } + + @Test + public void testRanarExiledCardsFromHand() { + setStrictChooseMode(true); + // A card is exiled from playerA's hand + // Result: Due to the card being exiled from playerA's hand, a Spirit token is created for playerA + + addCard(Zone.BATTLEFIELD, playerA, "Ranar the Ever-Watchful", 1); + + /* + Whenever one or more cards are put into exile from your hand or a spell or ability you control exiles one or + more permanents from the battlefield, create a 1/1 white Spirit creature token with flying. + */ + + addCard(Zone.HAND, playerA, "Psychic Theft", 1); // exile instant/sorcery card from target player's hand + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); // handle mana for Psychic Theft + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); // instant spell card + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Theft", playerA); + setChoice(playerA, "Lightning Bolt"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spirit", 1); // created token by Ranar + assertPermanentCount(playerB, "Spirit", 0); // no Spirit tokens for the other player + } + +} From 3299641ad4a87503b5edbc53e820b0cbbee0a155 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 15 Jul 2021 18:39:46 -0400 Subject: [PATCH 04/48] [AFR] various text fixes --- .../src/mage/cards/a/AirCultElemental.java | 2 +- Mage.Sets/src/mage/cards/b/BardClass.java | 2 +- .../src/mage/cards/c/ContactOtherPlane.java | 2 +- .../src/mage/cards/d/DawnbringerCleric.java | 2 +- .../src/mage/cards/d/DeathPriestOfMyrkul.java | 9 ++--- Mage.Sets/src/mage/cards/f/FighterClass.java | 4 +-- Mage.Sets/src/mage/cards/f/FindThePath.java | 2 +- .../src/mage/cards/g/GoblinMorningstar.java | 4 ++- Mage.Sets/src/mage/cards/g/GreenDragon.java | 2 +- Mage.Sets/src/mage/cards/g/GuildThief.java | 8 ++--- Mage.Sets/src/mage/cards/i/IronGolem.java | 2 +- .../src/mage/cards/l/LightfootRogue.java | 2 +- .../src/mage/cards/l/LoathsomeTroll.java | 2 +- .../src/mage/cards/m/MercadianAtlas.java | 2 +- .../src/mage/cards/o/OswaldFiddlebender.java | 2 +- .../src/mage/cards/p/PowerOfPersuasion.java | 2 +- Mage.Sets/src/mage/cards/p/PurpleWorm.java | 2 +- .../mage/cards/s/ShessraDeathsWhisper.java | 4 +-- Mage.Sets/src/mage/cards/s/SpikedPitTrap.java | 2 +- Mage.Sets/src/mage/cards/t/TreasureChest.java | 2 +- Mage.Sets/src/mage/cards/w/WarlockClass.java | 16 +++++---- Mage.Sets/src/mage/cards/w/WildShape.java | 2 +- .../mage/cards/y/YouFindTheVillainsLair.java | 2 +- .../java/mage/verify/VerifyCardDataTest.java | 2 +- .../BeginningOfEndStepTriggeredAbility.java | 33 ++++++++++--------- .../CastOnlyIfConditionIsTrueEffect.java | 3 +- .../common/EquippedSourceCondition.java | 2 +- .../BecomesCreatureTargetEffect.java | 7 ++-- ...SearchLibraryGraveyardPutInHandEffect.java | 2 +- .../mage/abilities/keyword/EquipAbility.java | 16 +++++---- 30 files changed, 78 insertions(+), 66 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AirCultElemental.java b/Mage.Sets/src/mage/cards/a/AirCultElemental.java index 210a6fa390b..173b62f090d 100644 --- a/Mage.Sets/src/mage/cards/a/AirCultElemental.java +++ b/Mage.Sets/src/mage/cards/a/AirCultElemental.java @@ -20,7 +20,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class AirCultElemental extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other creature"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other target creature"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/b/BardClass.java b/Mage.Sets/src/mage/cards/b/BardClass.java index 1ec0ae71e2c..12329fc085c 100644 --- a/Mage.Sets/src/mage/cards/b/BardClass.java +++ b/Mage.Sets/src/mage/cards/b/BardClass.java @@ -52,7 +52,7 @@ public final class BardClass extends CardImpl { // Legendary spells you cast cost {R}{G} less to cast. This effect reduces only the amount of colored mana you pay. this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( - new SpellsCostReductionControllerEffect(filter, new ManaCostsImpl<>("{W}{B}")), 2 + new SpellsCostReductionControllerEffect(filter, new ManaCostsImpl<>("{R}{G}")), 2 ))); // {3}{R}{G}: Level 3 diff --git a/Mage.Sets/src/mage/cards/c/ContactOtherPlane.java b/Mage.Sets/src/mage/cards/c/ContactOtherPlane.java index 3a8ceee6d4a..c172cb58e1b 100644 --- a/Mage.Sets/src/mage/cards/c/ContactOtherPlane.java +++ b/Mage.Sets/src/mage/cards/c/ContactOtherPlane.java @@ -33,7 +33,7 @@ public final class ContactOtherPlane extends CardImpl { // 20 | Scry 3, then draw three cards. effect.addTableEntry( 20, 20, new ScryEffect(3, false), - new DrawCardSourceControllerEffect(2).concatBy(", then") + new DrawCardSourceControllerEffect(3).concatBy(", then") ); } diff --git a/Mage.Sets/src/mage/cards/d/DawnbringerCleric.java b/Mage.Sets/src/mage/cards/d/DawnbringerCleric.java index 3999e94d0d0..746cef4f2e5 100644 --- a/Mage.Sets/src/mage/cards/d/DawnbringerCleric.java +++ b/Mage.Sets/src/mage/cards/d/DawnbringerCleric.java @@ -31,7 +31,7 @@ public final class DawnbringerCleric extends CardImpl { // When Dawnbringer Cleric enters the battlefield, choose one — // • Cure Wounds — You gain 2 life. - Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(1)); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2)); ability.getModes().getMode().withFlavorWord("Cure Wounds"); // • Dispel Magic — Destroy target enchantment. diff --git a/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java b/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java index 11222e13aef..fcbdae45f3f 100644 --- a/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java +++ b/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java @@ -3,18 +3,19 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CompletedDungeonCondition; +import mage.abilities.condition.common.MorbidCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.permanent.token.SkeletonToken; -import mage.watchers.common.CompletedDungeonWatcher; +import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -51,8 +52,8 @@ public final class DeathPriestOfMyrkul extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( Zone.BATTLEFIELD, new DoIfCostPaid(new CreateTokenEffect(new SkeletonToken()), new GenericManaCost(1)), - TargetController.YOU, CompletedDungeonCondition.instance, false - ).addHint(CompletedDungeonCondition.getHint()), new CompletedDungeonWatcher()); + TargetController.YOU, MorbidCondition.instance, false + ).addHint(MorbidHint.instance), new MorbidWatcher()); } private DeathPriestOfMyrkul(final DeathPriestOfMyrkul card) { diff --git a/Mage.Sets/src/mage/cards/f/FighterClass.java b/Mage.Sets/src/mage/cards/f/FighterClass.java index 29e81e8eeaa..183f4156934 100644 --- a/Mage.Sets/src/mage/cards/f/FighterClass.java +++ b/Mage.Sets/src/mage/cards/f/FighterClass.java @@ -49,7 +49,7 @@ public final class FighterClass extends CardImpl { // When Fighter Class enters the battlefield, search your library for an Equipment card, reveal it, put it into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter)) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) )); // {1}{R}{W}: Level 2 @@ -58,7 +58,7 @@ public final class FighterClass extends CardImpl { // Equip abilities you activate cost {2} less to activate. this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( new AbilitiesCostReductionControllerEffect(EquipAbility.class, "Equip") - .setText("\"equip abilities you activate cost {2} less to activate\""), + .setText("equip abilities you activate cost {2} less to activate"), 2 ))); diff --git a/Mage.Sets/src/mage/cards/f/FindThePath.java b/Mage.Sets/src/mage/cards/f/FindThePath.java index b3d68b92d64..647f735b845 100644 --- a/Mage.Sets/src/mage/cards/f/FindThePath.java +++ b/Mage.Sets/src/mage/cards/f/FindThePath.java @@ -43,7 +43,7 @@ public final class FindThePath extends CardImpl { new SimpleManaAbility( Zone.BATTLEFIELD, new Mana(ManaType.GREEN, 2), new TapSourceCost() ), AttachmentType.AURA - ))); + ).setText("enchanted land has \"{T}: Add {G}{G}.\""))); } private FindThePath(final FindThePath card) { diff --git a/Mage.Sets/src/mage/cards/g/GoblinMorningstar.java b/Mage.Sets/src/mage/cards/g/GoblinMorningstar.java index 7d1364b6ec0..fd14b0a3946 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinMorningstar.java +++ b/Mage.Sets/src/mage/cards/g/GoblinMorningstar.java @@ -33,7 +33,9 @@ public final class GoblinMorningstar extends CardImpl { // Equipped creature gets +1/+0 and has trample. Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); - ability.addEffect(new GainAbilityAttachedEffect(TrampleAbility.getInstance(), AttachmentType.EQUIPMENT)); + ability.addEffect(new GainAbilityAttachedEffect( + TrampleAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has trample")); this.addAbility(ability); // Equip {1}{R} diff --git a/Mage.Sets/src/mage/cards/g/GreenDragon.java b/Mage.Sets/src/mage/cards/g/GreenDragon.java index 620d99f2651..c05ff73f200 100644 --- a/Mage.Sets/src/mage/cards/g/GreenDragon.java +++ b/Mage.Sets/src/mage/cards/g/GreenDragon.java @@ -36,7 +36,7 @@ public final class GreenDragon extends CardImpl { // Poison Breath — When Green Dragon enters the battlefield, until end of turn, whenever a creature an opponent controls is dealt damage, destroy it. this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( new GreenDragonDelayedTriggeredAbility(), false - ))); + )).withFlavorWord("Poison Breath")); } private GreenDragon(final GreenDragon card) { diff --git a/Mage.Sets/src/mage/cards/g/GuildThief.java b/Mage.Sets/src/mage/cards/g/GuildThief.java index b66a7e2b43a..c0b4636cc75 100644 --- a/Mage.Sets/src/mage/cards/g/GuildThief.java +++ b/Mage.Sets/src/mage/cards/g/GuildThief.java @@ -1,12 +1,9 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; @@ -15,7 +12,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.counters.CounterType; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -34,7 +30,9 @@ public final class GuildThief extends CardImpl { // Whenever Guild Thief deals combat damage to a player, put a +1/+1 counter on it. this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), false + new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)) + .setText("put a +1/+1 counter on it"), + false )); // Cunning Action — {3}{U}: Guild Thief can't be blocked this turn. diff --git a/Mage.Sets/src/mage/cards/i/IronGolem.java b/Mage.Sets/src/mage/cards/i/IronGolem.java index e9c5921272d..76bf1480d01 100644 --- a/Mage.Sets/src/mage/cards/i/IronGolem.java +++ b/Mage.Sets/src/mage/cards/i/IronGolem.java @@ -31,7 +31,7 @@ public final class IronGolem extends CardImpl { // Iron Golem attacks or blocks each combat if able. Ability ability = new SimpleStaticAbility(new AttacksIfAbleSourceEffect(Duration.WhileOnBattlefield).setText("{this} attacks")); - ability.addEffect(new BlocksIfAbleSourceEffect(Duration.WhileOnBattlefield).setText("or blocks each combat if able")); + ability.addEffect(new BlocksIfAbleSourceEffect(Duration.WhileOnBattlefield).setText("blocks each combat if able").concatBy("or")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/l/LightfootRogue.java b/Mage.Sets/src/mage/cards/l/LightfootRogue.java index f646f20ac4d..a88196de0d7 100644 --- a/Mage.Sets/src/mage/cards/l/LightfootRogue.java +++ b/Mage.Sets/src/mage/cards/l/LightfootRogue.java @@ -56,7 +56,7 @@ public final class LightfootRogue extends CardImpl { 20, 20, new BoostSourceEffect( 3, 0, Duration.EndOfTurn - ).setText("it gets +1/+0"), + ).setText("it gets +3/+0"), new GainAbilitySourceEffect( FirstStrikeAbility.getInstance(), Duration.EndOfTurn ).setText("and gains first strike"), diff --git a/Mage.Sets/src/mage/cards/l/LoathsomeTroll.java b/Mage.Sets/src/mage/cards/l/LoathsomeTroll.java index 6c24781cd0e..11d7b68c407 100644 --- a/Mage.Sets/src/mage/cards/l/LoathsomeTroll.java +++ b/Mage.Sets/src/mage/cards/l/LoathsomeTroll.java @@ -39,7 +39,7 @@ public final class LoathsomeTroll extends CardImpl { )); // 10-19 | Return Loathsome Troll to your hand. - effect.addTableEntry(10, 19, new ReturnToHandSourceEffect().setText("retun {this} to your hand")); + effect.addTableEntry(10, 19, new ReturnToHandSourceEffect().setText("return {this} to your hand")); // 20 | Return Loathsome Troll to the battlefield tapped. effect.addTableEntry(20, 20, new ReturnSourceFromGraveyardToBattlefieldEffect(true) diff --git a/Mage.Sets/src/mage/cards/m/MercadianAtlas.java b/Mage.Sets/src/mage/cards/m/MercadianAtlas.java index 113c9a3bf37..a8c2b1cfa32 100644 --- a/Mage.Sets/src/mage/cards/m/MercadianAtlas.java +++ b/Mage.Sets/src/mage/cards/m/MercadianAtlas.java @@ -58,7 +58,7 @@ enum MercadianAtlasCondition implements Condition { @Override public String toString() { - return "{this} is attacking"; + return "you didn't play a land this turn"; } } diff --git a/Mage.Sets/src/mage/cards/o/OswaldFiddlebender.java b/Mage.Sets/src/mage/cards/o/OswaldFiddlebender.java index 9580b0370e0..2aee9ae25a1 100644 --- a/Mage.Sets/src/mage/cards/o/OswaldFiddlebender.java +++ b/Mage.Sets/src/mage/cards/o/OswaldFiddlebender.java @@ -46,7 +46,7 @@ public final class OswaldFiddlebender extends CardImpl { ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent( StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN ))); - this.addAbility(ability); + this.addAbility(ability.withFlavorWord("Magical Tinkering")); } private OswaldFiddlebender(final OswaldFiddlebender card) { diff --git a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java index e707e31a309..3e112cb30f1 100644 --- a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java +++ b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java @@ -58,7 +58,7 @@ class PowerOfPersuasionEffect extends OneShotEffect { PowerOfPersuasionEffect() { super(Outcome.Benefit); - staticText = "its owner puts it on the top of bottom of their library"; + staticText = "its owner puts it on the top or bottom of their library"; } private PowerOfPersuasionEffect(final PowerOfPersuasionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/p/PurpleWorm.java b/Mage.Sets/src/mage/cards/p/PurpleWorm.java index 60005a96d1e..441072a9c92 100644 --- a/Mage.Sets/src/mage/cards/p/PurpleWorm.java +++ b/Mage.Sets/src/mage/cards/p/PurpleWorm.java @@ -31,7 +31,7 @@ public final class PurpleWorm extends CardImpl { // This spell costs {2} less to cast if a creature died this turn. Ability ability = new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionSourceEffect(1, MorbidCondition.instance) + Zone.ALL, new SpellCostReductionSourceEffect(2, MorbidCondition.instance) ); ability.setRuleAtTheTop(true); this.addAbility(ability.addHint(MorbidHint.instance), new MorbidWatcher()); diff --git a/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java b/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java index d54a362bad0..e7d9f9724b8 100644 --- a/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java +++ b/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java @@ -40,9 +40,9 @@ public final class ShessraDeathsWhisper extends CardImpl { // Whispers of the Grave — At the beginning of your end step, if a creature died this turn, you may pay 2 life. If you do, draw a card. this.addAbility(new BeginningOfEndStepTriggeredAbility( - Zone.BATTLEFIELD, new DoIfCostPaid(new DrawCardSourceControllerEffect(2), + Zone.BATTLEFIELD, new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new PayLifeCost(2)), TargetController.YOU, MorbidCondition.instance, false - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance).withFlavorWord("Whispers of the Grave"), new MorbidWatcher()); } private ShessraDeathsWhisper(final ShessraDeathsWhisper card) { diff --git a/Mage.Sets/src/mage/cards/s/SpikedPitTrap.java b/Mage.Sets/src/mage/cards/s/SpikedPitTrap.java index 0eb4037889d..16d39d3d93d 100644 --- a/Mage.Sets/src/mage/cards/s/SpikedPitTrap.java +++ b/Mage.Sets/src/mage/cards/s/SpikedPitTrap.java @@ -44,7 +44,7 @@ public final class SpikedPitTrap extends CardImpl { )); // 10-20 | Spike Pit Trap deals 5 damage to that creature. Create a Treasure token. - effect.addTableEntry(1, 9, new DamageTargetEffect( + effect.addTableEntry(10, 20, new DamageTargetEffect( 5, true, "that creature." ), new CreateTokenEffect(new TreasureToken())); } diff --git a/Mage.Sets/src/mage/cards/t/TreasureChest.java b/Mage.Sets/src/mage/cards/t/TreasureChest.java index b49171fc7e0..8796421f190 100644 --- a/Mage.Sets/src/mage/cards/t/TreasureChest.java +++ b/Mage.Sets/src/mage/cards/t/TreasureChest.java @@ -65,7 +65,7 @@ class TreasureChestEffect extends OneShotEffect { TreasureChestEffect() { super(Outcome.Benefit); - staticText = "search your library for a card. If it's an artifact card you may " + + staticText = "search your library for a card. If it's an artifact card, you may " + "put it onto the battlefield. Otherwise, put that card into your hand. Then shuffle"; } diff --git a/Mage.Sets/src/mage/cards/w/WarlockClass.java b/Mage.Sets/src/mage/cards/w/WarlockClass.java index ae4bda5dc88..d094f88a3a7 100644 --- a/Mage.Sets/src/mage/cards/w/WarlockClass.java +++ b/Mage.Sets/src/mage/cards/w/WarlockClass.java @@ -3,12 +3,14 @@ package mage.cards.w; import mage.abilities.Ability; import mage.abilities.common.BecomesClassLevelTriggeredAbility; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.ClassLevelAbility; import mage.abilities.keyword.ClassReminderAbility; @@ -39,7 +41,7 @@ public final class WarlockClass extends CardImpl { // At the beginning of your end step, if a creature died this turn, each opponent loses 1 life. this.addAbility(new ConditionalInterveningIfTriggeredAbility( new BeginningOfEndStepTriggeredAbility( - new LoseLifeOpponentsEffect(2), TargetController.YOU, false + new LoseLifeOpponentsEffect(1), TargetController.YOU, false ), MorbidCondition.instance, "At the beginning of your end step, " + "if a creature died this turn, each opponent loses 1 life." ).addHint(MorbidHint.instance), new MorbidWatcher()); @@ -49,7 +51,7 @@ public final class WarlockClass extends CardImpl { // When this Class becomes level 2, look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard. this.addAbility(new BecomesClassLevelTriggeredAbility(new LookLibraryAndPickControllerEffect( - StaticValue.get(1), false, StaticValue.get(1), StaticFilters.FILTER_CARD, + StaticValue.get(3), false, StaticValue.get(1), StaticFilters.FILTER_CARD, Zone.GRAVEYARD, false, false, false, Zone.HAND, false ), 2)); @@ -57,9 +59,11 @@ public final class WarlockClass extends CardImpl { this.addAbility(new ClassLevelAbility(3, "{6}{B}")); // At the beginning of your end step, each opponent loses life equal to the life they lost this turn. - this.addAbility(new BeginningOfEndStepTriggeredAbility( - new WarlockClassEffect(), TargetController.YOU, false - )); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( + new BeginningOfEndStepTriggeredAbility( + new WarlockClassEffect(), TargetController.YOU, false + ), 3 + ))); } private WarlockClass(final WarlockClass card) { @@ -76,7 +80,7 @@ class WarlockClassEffect extends OneShotEffect { WarlockClassEffect() { super(Outcome.Benefit); - staticText = "At the beginning of your end step, each opponent loses life equal to the life they lost this turn."; + staticText = "each opponent loses life equal to the life they lost this turn."; } private WarlockClassEffect(final WarlockClassEffect effect) { diff --git a/Mage.Sets/src/mage/cards/w/WildShape.java b/Mage.Sets/src/mage/cards/w/WildShape.java index fd11ef1a42b..cf62caaa074 100644 --- a/Mage.Sets/src/mage/cards/w/WildShape.java +++ b/Mage.Sets/src/mage/cards/w/WildShape.java @@ -25,7 +25,7 @@ public final class WildShape extends CardImpl { // Choose one. Until end of turn, target creature you control has that base power and toughness, becomes that creature type, and gains that ability. this.getSpellAbility().getModes().setChooseText( - ". Until end of turn, target creature you control has that base power and toughness, " + + "Choose one. Until end of turn, target creature you control has that base power and toughness, " + "becomes that creature type, and gains that ability." ); diff --git a/Mage.Sets/src/mage/cards/y/YouFindTheVillainsLair.java b/Mage.Sets/src/mage/cards/y/YouFindTheVillainsLair.java index e6c63acf11a..72aa4229d97 100644 --- a/Mage.Sets/src/mage/cards/y/YouFindTheVillainsLair.java +++ b/Mage.Sets/src/mage/cards/y/YouFindTheVillainsLair.java @@ -27,7 +27,7 @@ public final class YouFindTheVillainsLair extends CardImpl { // • Learn Their Secrets — Draw two cards, then discard two cards. this.getSpellAbility().addMode(new Mode( new DrawDiscardControllerEffect(2, 2) - ).withFlavorWord("Learn Their Secrts")); + ).withFlavorWord("Learn Their Secrets")); } private YouFindTheVillainsLair(final YouFindTheVillainsLair card) { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 0edcd4fa92d..68e4551cc9f 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -56,7 +56,7 @@ public class VerifyCardDataTest { private static final Logger logger = Logger.getLogger(VerifyCardDataTest.class); - private static final String FULL_ABILITIES_CHECK_SET_CODE = "MH2"; // check all abilities and output cards with wrong abilities texts; + private static final String FULL_ABILITIES_CHECK_SET_CODE = "AFR"; // check all abilities and output cards with wrong abilities texts; private static final boolean AUTO_FIX_SAMPLE_DECKS = false; // debug only: auto-fix sample decks by test_checkSampleDecks test run private static final boolean ONLY_TEXT = false; // use when checking text locally, suppresses unnecessary checks and output messages diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java index 61266799aa0..e11b5b6b29c 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java @@ -118,24 +118,25 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { } private String generateConditionString() { - if (interveningIfClauseCondition != null) { - if (interveningIfClauseCondition.toString().startsWith("if")) { - - //Fixes punctuation on multiple sentence if-then construction - // see -- Colfenor's Urn - if (interveningIfClauseCondition.toString().endsWith(".")) { - return interveningIfClauseCondition.toString() + " "; - } - - return interveningIfClauseCondition.toString() + ", "; - } else { - return "if {this} is " + interveningIfClauseCondition.toString() + ", "; + if (interveningIfClauseCondition == null) { + switch (getZone()) { + case GRAVEYARD: + return "if {this} is in your graveyard, "; } + return ""; } - switch (getZone()) { - case GRAVEYARD: - return "if {this} is in your graveyard, "; + String clauseText = interveningIfClauseCondition.toString(); + if (clauseText.startsWith("if")) { + //Fixes punctuation on multiple sentence if-then construction + // see -- Colfenor's Urn + if (clauseText.endsWith(".")) { + return clauseText + " "; + } + return clauseText + ", "; } - return ""; + System.out.println("=================="); + System.out.println(clauseText); + System.out.println("=================="); + return "if " + clauseText + ", "; } } diff --git a/Mage/src/main/java/mage/abilities/common/CastOnlyIfConditionIsTrueEffect.java b/Mage/src/main/java/mage/abilities/common/CastOnlyIfConditionIsTrueEffect.java index 3c32c37aace..1b280f1493c 100644 --- a/Mage/src/main/java/mage/abilities/common/CastOnlyIfConditionIsTrueEffect.java +++ b/Mage/src/main/java/mage/abilities/common/CastOnlyIfConditionIsTrueEffect.java @@ -10,7 +10,6 @@ import mage.game.Game; import mage.game.events.GameEvent; /** - * * @author LevelX2 */ public class CastOnlyIfConditionIsTrueEffect extends ContinuousRuleModifyingEffectImpl { @@ -52,7 +51,7 @@ public class CastOnlyIfConditionIsTrueEffect extends ContinuousRuleModifyingEffe private String setText() { StringBuilder sb = new StringBuilder("cast this spell only "); if (condition != null) { - sb.append(' ').append(condition.toString()); + sb.append(condition); } return sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java index cdcd889f8fd..18e0adf0ba1 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java @@ -34,7 +34,7 @@ public enum EquippedSourceCondition implements Condition { @Override public String toString() { - return "equipped"; + return "{this} is equipped"; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java index 8a1eb66b163..0e200bb74cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java @@ -182,10 +182,13 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { sb.append(token.getDescription()); sb.append(' ').append(duration.toString()); if (addStillALandText) { + if (!sb.toString().endsWith("\" ")) { + sb.append(". "); + } if (target.getMaxNumberOfTargets() > 1) { - sb.append(". They're still lands"); + sb.append("They're still lands"); } else { - sb.append(". It's still a land"); + sb.append("It's still a land"); } } return sb.toString().replace(" .", "."); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java index 21cd0a54688..c44cb611f34 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java @@ -35,7 +35,7 @@ public class SearchLibraryGraveyardPutInHandEffect extends OneShotEffect { this.filter = filter; this.forceToSearchBoth = forceToSearchBoth; staticText = (youMay ? "you may " : "") + "search your library and" + (forceToSearchBoth ? "" : "/or") + " graveyard for a card named " + filter.getMessage() - + ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle" : "If you search your library this way, shuffle"); + + ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle" : "If you search your library this way, shuffle it"); } public SearchLibraryGraveyardPutInHandEffect(final SearchLibraryGraveyardPutInHandEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java index 7b0dc758f80..1bc91d6cfcc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java @@ -28,7 +28,7 @@ public class EquipAbility extends ActivatedAbilityImpl { public EquipAbility(Outcome outcome, Cost cost, Target target) { super(Zone.BATTLEFIELD, new EquipEffect(outcome), cost); this.addTarget(target); - this.timing = TimingRule.SORCERY; + this.timing = TimingRule.SORCERY; } public EquipAbility(final EquipAbility ability) { @@ -50,19 +50,23 @@ public class EquipAbility extends ActivatedAbilityImpl { String targetText = getTargets().get(0) != null ? getTargets().get(0).getFilter().getMessage() : "creature"; String reminderText = " (" + manaCosts.getText() + ": Attach to target " + targetText + ". Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.)"; - StringBuilder sb = new StringBuilder("Equip "); + StringBuilder sb = new StringBuilder("Equip"); if (!targetText.equals("creature you control")) { - sb.append(targetText); + sb.append(' ').append(targetText); + } + String costText = costs.getText(); + if (costText != null && !costText.isEmpty()) { + sb.append("—").append(costText).append('.'); + } else { sb.append(' '); } - sb.append(costs.getText()); sb.append(manaCosts.getText()); if (costReduceText != null && !costReduceText.isEmpty()) { - sb.append(' '); + sb.append(". "); sb.append(costReduceText); } if (maxActivationsPerTurn == 1) { - sb.append(" Activate only once each turn."); + sb.append(". Activate only once each turn."); } sb.append(reminderText); return sb.toString(); From f9bf84e6f61fff5073f1a9112907d7c619ed6e1f Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 15 Jul 2021 19:28:40 -0400 Subject: [PATCH 05/48] [AFR] Implemented Zariel, Archduke of Avernus --- .../mage/cards/z/ZarielArchdukeOfAvernus.java | 60 +++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + .../src/main/java/mage/constants/SubType.java | 3 +- .../ZarielArchdukeOfAvernusEmblem.java | 60 +++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/z/ZarielArchdukeOfAvernus.java create mode 100644 Mage/src/main/java/mage/game/command/emblems/ZarielArchdukeOfAvernusEmblem.java diff --git a/Mage.Sets/src/mage/cards/z/ZarielArchdukeOfAvernus.java b/Mage.Sets/src/mage/cards/z/ZarielArchdukeOfAvernus.java new file mode 100644 index 00000000000..8a36922cf41 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZarielArchdukeOfAvernus.java @@ -0,0 +1,60 @@ +package mage.cards.z; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.command.emblems.ZarielArchdukeOfAvernusEmblem; +import mage.game.permanent.token.DevilToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZarielArchdukeOfAvernus extends CardImpl { + + public ZarielArchdukeOfAvernus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZARIEL); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // +1: Creatures you control get +1/+0 and gain haste until end of turn. + Ability ability = new LoyaltyAbility(new BoostControlledEffect( + 1, 0, Duration.EndOfTurn + ).setText("creatures you control get +1/+0"), 1); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and gain haste until end of turn")); + this.addAbility(ability); + + // 0: Create a 1/1 red Devil creature token with "When this creature dies, it deals 1 damage to any target." + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DevilToken()), 0)); + + // −6: You get an emblem with "At the end of the first combat phase on your turn, untap target creature you control. After this phase, there is an additional combat phase." + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new ZarielArchdukeOfAvernusEmblem()), -6)); + } + + private ZarielArchdukeOfAvernus(final ZarielArchdukeOfAvernus card) { + super(card); + } + + @Override + public ZarielArchdukeOfAvernus copy() { + return new ZarielArchdukeOfAvernus(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 15e688cfe87..94d4825a6aa 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -269,6 +269,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Yuan-Ti Fang-Blade", 128, Rarity.COMMON, mage.cards.y.YuanTiFangBlade.class)); cards.add(new SetCardInfo("Yuan-Ti Malison", 86, Rarity.RARE, mage.cards.y.YuanTiMalison.class)); cards.add(new SetCardInfo("Zalto, Fire Giant Duke", 171, Rarity.RARE, mage.cards.z.ZaltoFireGiantDuke.class)); + cards.add(new SetCardInfo("Zariel, Archduke of Avernus", 172, Rarity.MYTHIC, mage.cards.z.ZarielArchdukeOfAvernus.class)); cards.add(new SetCardInfo("Zombie Ogre", 129, Rarity.COMMON, mage.cards.z.ZombieOgre.class)); } } diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 961900b6956..70374d6f864 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -467,7 +467,8 @@ public enum SubType { XENAGOS("Xenagos", SubTypeSet.PlaneswalkerType), YANGGU("Yanggu", SubTypeSet.PlaneswalkerType), YANLING("Yanling", SubTypeSet.PlaneswalkerType), - YODA("Yoda", SubTypeSet.PlaneswalkerType, true); // Star Wars + YODA("Yoda", SubTypeSet.PlaneswalkerType, true), // Star Wars, + ZARIEL("Zariel", SubTypeSet.PlaneswalkerType); public static class SubTypePredicate implements Predicate { diff --git a/Mage/src/main/java/mage/game/command/emblems/ZarielArchdukeOfAvernusEmblem.java b/Mage/src/main/java/mage/game/command/emblems/ZarielArchdukeOfAvernusEmblem.java new file mode 100644 index 00000000000..efceb70f38f --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/ZarielArchdukeOfAvernusEmblem.java @@ -0,0 +1,60 @@ +package mage.game.command.emblems; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.AdditionalCombatPhaseEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.constants.TurnPhase; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.command.Emblem; +import mage.game.events.GameEvent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * @author TheElk801 + */ +public final class ZarielArchdukeOfAvernusEmblem extends Emblem { + + // −6: You get an emblem with "At the end of the first combat phase on your turn, untap target creature you control. After this phase, there is an additional combat phase." + public ZarielArchdukeOfAvernusEmblem() { + this.setName("Emblem Zariel"); + this.setExpansionSetCodeForImage("AFR"); + this.getAbilities().add(new ZarielArchdukeOfAvernusEmblemAbility()); + } +} + +class ZarielArchdukeOfAvernusEmblemAbility extends TriggeredAbilityImpl { + + ZarielArchdukeOfAvernusEmblemAbility() { + super(Zone.COMMAND, new UntapTargetEffect()); + this.addEffect(new AdditionalCombatPhaseEffect()); + this.addTarget(new TargetControlledCreaturePermanent()); + } + + private ZarielArchdukeOfAvernusEmblemAbility(final ZarielArchdukeOfAvernusEmblemAbility ability) { + super(ability); + } + + @Override + public ZarielArchdukeOfAvernusEmblemAbility copy() { + return new ZarielArchdukeOfAvernusEmblemAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_COMBAT_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.isActivePlayer(getControllerId()) + && game.getTurn().getPhase(TurnPhase.COMBAT).getCount() == 0; + } + + @Override + public String getRule() { + return "At the end of the first combat phase on your turn, untap target creature you control. " + + "After this phase, there is an additional combat phase."; + } + +} \ No newline at end of file From d42fd2e50595b292e930499514e6f43f46977efe Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 15 Jul 2021 20:24:46 -0400 Subject: [PATCH 06/48] [AFR] Implemented Ochre Jelly --- Mage.Sets/src/mage/cards/o/OchreJelly.java | 107 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + .../CreateDelayedTriggeredAbilityEffect.java | 5 + .../common/CreateTokenCopyTargetEffect.java | 9 +- 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/o/OchreJelly.java diff --git a/Mage.Sets/src/mage/cards/o/OchreJelly.java b/Mage.Sets/src/mage/cards/o/OchreJelly.java new file mode 100644 index 00000000000..131240fe556 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OchreJelly.java @@ -0,0 +1,107 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OchreJelly extends CardImpl { + + public OchreJelly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}"); + + this.subtype.add(SubType.OOZE); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Ochre Jelly enters the battlefield with X +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))); + + // Split — When Ochre Jelly dies, if it had two or more +1/+1 counters on it, create a token that's a copy of it at the beginning of the next end step. That token enters the battlefield with half that many +1/+1 counters on it, rounded down. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new DiesSourceTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( + new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new OchreJellyEffect()) + )), OchreJellyCondition.instance, CardUtil.italicizeWithEmDash("Split") + + "When {this} dies, if it had two or more +1/+1 counters on it, " + + "create a token that's a copy of it at the beginning of the next end step. " + + "That token enters the battlefield with half that many +1/+1 counters on it, rounded down." + )); + } + + private OchreJelly(final OchreJelly card) { + super(card); + } + + @Override + public OchreJelly copy() { + return new OchreJelly(this); + } +} + +enum OchreJellyCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) source.getEffects().get(0).getValue("permanentLeftBattlefield"); + return permanent != null && permanent.getCounters(game).getCount(CounterType.P1P1) >= 2; + } +} + +class OchreJellyEffect extends OneShotEffect { + + OchreJellyEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of {this}"; + } + + private OchreJellyEffect(final OchreJellyEffect effect) { + super(effect); + } + + @Override + public OchreJellyEffect copy() { + return new OchreJellyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) getValue("permanentLeftBattlefield"); + if (permanent == null) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(); + effect.setSavedPermanent(permanent); + effect.apply(game, source); + int counters = permanent.getCounters(game).getCount(CounterType.P1P1) / 2; + for (Permanent token : effect.getAddedPermanent()) { + permanent.addCounters(CounterType.P1P1.createInstance(counters), source.getControllerId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 94d4825a6aa..76509aa6e7c 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -170,6 +170,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 274, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nadaar, Selfless Paladin", 27, Rarity.RARE, mage.cards.n.NadaarSelflessPaladin.class)); cards.add(new SetCardInfo("Neverwinter Dryad", 195, Rarity.COMMON, mage.cards.n.NeverwinterDryad.class)); + cards.add(new SetCardInfo("Ochre Jelly", 196, Rarity.RARE, mage.cards.o.OchreJelly.class)); cards.add(new SetCardInfo("Old Gnawbone", 197, Rarity.MYTHIC, mage.cards.o.OldGnawbone.class)); cards.add(new SetCardInfo("Orb of Dragonkind", 157, Rarity.RARE, mage.cards.o.OrbOfDragonkind.class)); cards.add(new SetCardInfo("Orcus, Prince of Undeath", 229, Rarity.RARE, mage.cards.o.OrcusPrinceOfUndeath.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index 179a59f7364..9fe41bcabaf 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -77,4 +77,9 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { } } + @Override + public void setValue(String key, Object value) { + ability.getEffects().setValue(key, value); + super.setValue(key, value); + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index 69e9a2f118c..f090a430243 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -49,6 +49,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { private boolean isntLegendary = false; private int startingLoyalty = -1; private final List additionalAbilities = new ArrayList(); + private Permanent savedPermanent = null; public CreateTokenCopyTargetEffect(boolean useLKI) { this(); @@ -133,7 +134,9 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { targetId = getTargetPointer().getFirst(game, source); } Permanent permanent; - if (useLKI) { + if (savedPermanent != null) { + permanent = savedPermanent; + } else if (useLKI) { permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); } else { permanent = game.getPermanentOrLKIBattlefield(targetId); @@ -319,4 +322,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { public void addAdditionalAbilities(Ability... abilities) { Arrays.stream(abilities).forEach(this.additionalAbilities::add); } + + public void setSavedPermanent(Permanent savedPermanent) { + this.savedPermanent = savedPermanent; + } } From f7319eb70af806ec30c1b947e36b17c509308377 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Thu, 15 Jul 2021 20:59:33 -0500 Subject: [PATCH 07/48] - Fixed Life and Limb dependency to cards like Yavimaya, Cradle of Growth --- Mage.Sets/src/mage/cards/l/LifeAndLimb.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/l/LifeAndLimb.java b/Mage.Sets/src/mage/cards/l/LifeAndLimb.java index 4c1a1c1d399..c7ac0295611 100644 --- a/Mage.Sets/src/mage/cards/l/LifeAndLimb.java +++ b/Mage.Sets/src/mage/cards/l/LifeAndLimb.java @@ -49,8 +49,9 @@ class LifeAndLimbEffect extends ContinuousEffectImpl { LifeAndLimbEffect() { super(Duration.WhileOnBattlefield, Outcome.Neutral); staticText = "All Forests and all Saprolings are 1/1 green Saproling creatures and Forest lands in addition to their other types"; - this.dependencyTypes.add(DependencyType.BecomeForest); - this.dependencyTypes.add(DependencyType.BecomeCreature); + + this.dependendToTypes.add(DependencyType.BecomeForest); + this.dependendToTypes.add(DependencyType.BecomeCreature); } LifeAndLimbEffect(final LifeAndLimbEffect effect) { From a47af7a5a9eb5468e08c03fff3ab242cfaa99419 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 08:09:39 -0400 Subject: [PATCH 08/48] [AFR] Implemented Spare Dagger --- Mage.Sets/src/mage/cards/s/SpareDagger.java | 84 +++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + .../GainAbilityWithAttachmentEffect.java | 18 ++-- 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/SpareDagger.java diff --git a/Mage.Sets/src/mage/cards/s/SpareDagger.java b/Mage.Sets/src/mage/cards/s/SpareDagger.java new file mode 100644 index 00000000000..eb77c8d121f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpareDagger.java @@ -0,0 +1,84 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeAttachmentCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityWithAttachmentEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpareDagger extends CardImpl { + + public SpareDagger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+0 and has "Whenever this creature attacks, you may sacrifice Spare Dagger. When you do, this creature deals 1 damage to any target." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); + ability.addEffect(new SpareDaggerEffect()); + this.addAbility(ability); + + // Equip {1} + this.addAbility(new EquipAbility(1)); + } + + private SpareDagger(final SpareDagger card) { + super(card); + } + + @Override + public SpareDagger copy() { + return new SpareDagger(this); + } +} + +class SpareDaggerEffect extends GainAbilityWithAttachmentEffect { + + SpareDaggerEffect() { + super("and has \"Whenever this creature attacks, you may sacrifice {this}. " + + "When you do, this creature deals 1 damage to any target.\"", + (Effect) null, null, new SacrificeAttachmentCost(), null); + } + + private SpareDaggerEffect(final SpareDaggerEffect effect) { + super(effect); + } + + @Override + public SpareDaggerEffect copy() { + return new SpareDaggerEffect(this); + } + + @Override + protected Ability makeAbility(Game game, Ability source) { + if (source == null || game == null) { + return null; + } + String sourceName = source.getSourcePermanentIfItStillExists(game).getName(); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new DamageTargetEffect(1), false, + "This creature deals 1 damage to any target" + ); + return new AttacksTriggeredAbility(new DoWhenCostPaid( + ability, useAttachedCost.copy().setMageObjectReference(source, game), + "Sacrifice " + sourceName + "?" + ), false, "Whenever this creature attacks, you may sacrifice " + + sourceName + ". When you do, this creature deals 1 damage to any target."); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 76509aa6e7c..8ec70595294 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -214,6 +214,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Skeletal Swarming", 232, Rarity.RARE, mage.cards.s.SkeletalSwarming.class)); cards.add(new SetCardInfo("Skullport Merchant", 120, Rarity.UNCOMMON, mage.cards.s.SkullportMerchant.class)); cards.add(new SetCardInfo("Soulknife Spy", 75, Rarity.COMMON, mage.cards.s.SoulknifeSpy.class)); + cards.add(new SetCardInfo("Spare Dagger", 250, Rarity.COMMON, mage.cards.s.SpareDagger.class)); cards.add(new SetCardInfo("Sphere of Annihilation", 121, Rarity.RARE, mage.cards.s.SphereOfAnnihilation.class)); cards.add(new SetCardInfo("Spiked Pit Trap", 251, Rarity.COMMON, mage.cards.s.SpikedPitTrap.class)); cards.add(new SetCardInfo("Split the Party", 76, Rarity.UNCOMMON, mage.cards.s.SplitTheParty.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java index 6018a71566a..631195bc2cf 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java @@ -28,8 +28,8 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { private final Effects effects = new Effects(); private final Targets targets = new Targets(); - private final Costs costs = new CostsImpl(); - private final UseAttachedCost useAttachedCost; + private final Costs costs = new CostsImpl<>(); + protected final UseAttachedCost useAttachedCost; public GainAbilityWithAttachmentEffect(String rule, Effect effect, Target target, UseAttachedCost attachedCost, Cost... costs) { this(rule, new Effects(effect), new Targets(target), attachedCost, costs); @@ -42,10 +42,10 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { this.targets.addAll(targets); this.costs.addAll(Arrays.asList(costs)); this.useAttachedCost = attachedCost; - this.generateGainAbilityDependencies(makeAbility(this.effects, this.targets, this.costs), null); + this.generateGainAbilityDependencies(makeAbility(null, null), null); } - public GainAbilityWithAttachmentEffect(final GainAbilityWithAttachmentEffect effect) { + protected GainAbilityWithAttachmentEffect(final GainAbilityWithAttachmentEffect effect) { super(effect); this.effects.addAll(effect.effects); this.targets.addAll(effect.targets); @@ -87,14 +87,13 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { if (permanent == null) { return true; } - Ability ability = makeAbility(this.effects, this.targets, this.costs); + Ability ability = makeAbility(game, source); ability.getEffects().setValue("attachedPermanent", game.getPermanent(source.getSourceId())); - ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game)); permanent.addAbility(ability, source.getSourceId(), game); return true; } - private static Ability makeAbility(Effects effects, Targets targets, Cost... costs) { + protected Ability makeAbility(Game game, Ability source) { Ability ability = new SimpleActivatedAbility(null, null); for (Effect effect : effects) { if (effect == null) { @@ -108,12 +107,15 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { } ability.addTarget(target); } - for (Cost cost : costs) { + for (Cost cost : this.costs) { if (cost == null) { continue; } ability.addCost(cost.copy()); } + if (source != null && game != null) { + ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game)); + } return ability; } } From 25dc141caa44c74abd6548f6427d9cbed7035d43 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 08:18:25 -0400 Subject: [PATCH 09/48] [AFR] Implemented Trickster's Talisman --- .../src/mage/cards/t/TrickstersTalisman.java | 77 +++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 78 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TrickstersTalisman.java diff --git a/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java new file mode 100644 index 00000000000..8213c858f8a --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java @@ -0,0 +1,77 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeAttachmentCost; +import mage.abilities.effects.CreateTokenCopySourceEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityWithAttachmentEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TrickstersTalisman extends CardImpl { + + public TrickstersTalisman(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); + ability.addEffect(new TrickstersTalismanEffect()); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private TrickstersTalisman(final TrickstersTalisman card) { + super(card); + } + + @Override + public TrickstersTalisman copy() { + return new TrickstersTalisman(this); + } +} + +class TrickstersTalismanEffect extends GainAbilityWithAttachmentEffect { + + TrickstersTalismanEffect() { + super("and has \"Whenever this creature deals combat damage to a player, " + + "you may sacrifice {this}. If you do, create a token that's a copy of this creature.\"", + (Effect) null, null, new SacrificeAttachmentCost(), null); + } + + private TrickstersTalismanEffect(final TrickstersTalismanEffect effect) { + super(effect); + } + + @Override + public TrickstersTalismanEffect copy() { + return new TrickstersTalismanEffect(this); + } + + @Override + protected Ability makeAbility(Game game, Ability source) { + if (game == null || source == null) { + return null; + } + return new DealsCombatDamageToAPlayerTriggeredAbility(new DoIfCostPaid( + new CreateTokenCopySourceEffect(), useAttachedCost.setMageObjectReference(source, game) + ), false, "Whenever this creature deals combat damage to a player, you may sacrifice " + + source.getSourcePermanentIfItStillExists(game).getName() + + ". If you do, create a token that's a copy of this creature.", false); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 8ec70595294..2e7cd119904 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -239,6 +239,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Treasure Chest", 252, Rarity.RARE, mage.cards.t.TreasureChest.class)); cards.add(new SetCardInfo("Treasure Vault", 261, Rarity.RARE, mage.cards.t.TreasureVault.class)); cards.add(new SetCardInfo("Trelasarra, Moon Dancer", 236, Rarity.UNCOMMON, mage.cards.t.TrelasarraMoonDancer.class)); + cards.add(new SetCardInfo("Trickster's Talisman", 79, Rarity.UNCOMMON, mage.cards.t.TrickstersTalisman.class)); cards.add(new SetCardInfo("Triumphant Adventurer", 237, Rarity.RARE, mage.cards.t.TriumphantAdventurer.class)); cards.add(new SetCardInfo("True Polymorph", 80, Rarity.RARE, mage.cards.t.TruePolymorph.class)); cards.add(new SetCardInfo("Underdark Basilisk", 208, Rarity.COMMON, mage.cards.u.UnderdarkBasilisk.class)); From 15b8e496b4a42a6d4f372015a71705b024443537 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 09:19:19 -0400 Subject: [PATCH 10/48] [AFR] Implemented Wizards's Spellbook --- .../src/mage/cards/w/WizardsSpellbook.java | 158 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 159 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WizardsSpellbook.java diff --git a/Mage.Sets/src/mage/cards/w/WizardsSpellbook.java b/Mage.Sets/src/mage/cards/w/WizardsSpellbook.java new file mode 100644 index 00000000000..37f9375a765 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WizardsSpellbook.java @@ -0,0 +1,158 @@ +package mage.cards.w; + +import mage.ApprovingObject; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RollDieWithResultTableEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WizardsSpellbook extends CardImpl { + + public WizardsSpellbook(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}{U}{U}"); + + // {T}: Exile target instant or sorcery card from a graveyard. Roll a d20. Activate only as a sorcery. + RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect( + 20, "exile target instant or sorcery card " + + "from a graveyard. Roll a d20. Activate only as a sorcery" + ); + Ability ability = new SimpleActivatedAbility(effect, new TapSourceCost()); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY)); + this.addAbility(ability); + + // 1-9 | Copy that card. You may cast the copy. + effect.addTableEntry(1, 9, new WizardsSpellbookEffect(1)); + + // 10-19 | Copy that card. You may cast the copy by paying {1} rather than paying its mana cost. + effect.addTableEntry(10, 19, new WizardsSpellbookEffect(2)); + + // 20 | Copy each card exiled with Wizard's Spellbook. You may cast any number of the copies without paying their mana costs. + effect.addTableEntry(20, 20, new WizardsSpellbookEffect(3)); + } + + private WizardsSpellbook(final WizardsSpellbook card) { + super(card); + } + + @Override + public WizardsSpellbook copy() { + return new WizardsSpellbook(this); + } +} + +class WizardsSpellbookEffect extends OneShotEffect { + + private final int level; + + WizardsSpellbookEffect(int level) { + super(Outcome.Benefit); + this.level = level; + switch (level) { + case 1: + staticText = "copy that card. You may cast the copy"; + break; + case 2: + staticText = "copy that card. You may cast the copy by paying {1} rather than paying its mana cost"; + break; + case 3: + staticText = "copy each card exiled with {this}. You may cast any number of the copies without paying their mana costs"; + break; + default: + throw new IllegalArgumentException("Level must be 1-3"); + } + } + + private WizardsSpellbookEffect(final WizardsSpellbookEffect effect) { + super(effect); + this.level = effect.level; + } + + @Override + public WizardsSpellbookEffect copy() { + return new WizardsSpellbookEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source); + MageObject sourceObject = source.getSourcePermanentOrLKI(game); + player.moveCardsToExile(card, source, game, true, exileId, sourceObject != null ? sourceObject.getName() : null); + if (level < 3) { + Card copiedCard = game.copyCard(card, source, source.getControllerId()); + if (!player.chooseUse( + Outcome.Benefit, "Cast " + copiedCard.getName() + + (level == 1 ? "?" : " by paying {1}?"), source, game) + ) { + return false; + } + SpellAbility spellAbility = player.chooseAbilityForCast(copiedCard, game, level == 2); + if (spellAbility == null) { + return false; + } + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + if (level == 2) { + player.setCastSourceIdWithAlternateMana(copiedCard.getId(), new ManaCostsImpl<>("{1}"), null); + } + player.cast(spellAbility, game, false, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + return true; + } + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile == null || exile.isEmpty()) { + return true; + } + Set cards = new HashSet<>(); + for (Card exiledCard : exile.getCards(game)) { + cards.add(game.copyCard(exiledCard, source, source.getControllerId())); + } + while (!cards.isEmpty()) { + for (Card copiedCard : cards) { + if (!player.chooseUse( + Outcome.PlayForFree, "Cast " + copiedCard.getName() + + " without paying its mana cost?", source, game + )) { + continue; + } + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + player.cast( + player.chooseAbilityForCast(copiedCard, game, true), + game, true, new ApprovingObject(source, game) + ); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + } + if (!player.chooseUse(Outcome.Neutral, "Continue casting exiled cards?", source, game)) { + break; + } + cards.removeIf(c -> game.getState().getZone(c.getId()) != Zone.EXILED); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 2e7cd119904..62636f298c4 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -258,6 +258,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Wight", 127, Rarity.RARE, mage.cards.w.Wight.class)); cards.add(new SetCardInfo("Wild Shape", 212, Rarity.UNCOMMON, mage.cards.w.WildShape.class)); cards.add(new SetCardInfo("Wizard Class", 81, Rarity.UNCOMMON, mage.cards.w.WizardClass.class)); + cards.add(new SetCardInfo("Wizard's Spellbook", 82, Rarity.RARE, mage.cards.w.WizardsSpellbook.class)); cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class)); cards.add(new SetCardInfo("You Come to a River", 83, Rarity.COMMON, mage.cards.y.YouComeToARiver.class)); cards.add(new SetCardInfo("You Come to the Gnoll Camp", 168, Rarity.COMMON, mage.cards.y.YouComeToTheGnollCamp.class)); From 77e5a7da2d8b2cf237cb9d2350c0ff8cfd7fba8d Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 09:46:20 -0400 Subject: [PATCH 11/48] [AFR] Implemented Gelatinous Cube --- .../src/mage/cards/g/GelatinousCube.java | 106 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 107 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GelatinousCube.java diff --git a/Mage.Sets/src/mage/cards/g/GelatinousCube.java b/Mage.Sets/src/mage/cards/g/GelatinousCube.java new file mode 100644 index 00000000000..e13ea9e46d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GelatinousCube.java @@ -0,0 +1,106 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.targetadjustment.TargetAdjuster; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GelatinousCube extends CardImpl { + + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("non-Ooze creature an opponent controls"); + + static { + filter.add(Predicates.not(SubType.OOZE.getPredicate())); + } + + public GelatinousCube(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.subtype.add(SubType.OOZE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Engulf — When Gelatinous Cube enters the battlefield, exile target non-Ooze creature an opponent controls until Gelatinous Cube leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect(filter.getMessage())); + ability.addTarget(new TargetPermanent(filter)); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); + this.addAbility(ability.withFlavorWord("Engulf")); + + // Dissolve — {X}{B}: Put target creature card with mana value X exiled with Gelatinous Cube into its owner's graveyard. + ability = new SimpleActivatedAbility(new GelatinousCubeEffect(), new ManaCostsImpl<>("{X}{B}")); + ability.setTargetAdjuster(GelatinousCubeAdjuster.instance); + this.addAbility(ability.withFlavorWord("Dissolve")); + } + + private GelatinousCube(final GelatinousCube card) { + super(card); + } + + @Override + public GelatinousCube copy() { + return new GelatinousCube(this); + } +} + +enum GelatinousCubeAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + int xValue = ability.getManaCostsToPay().getX(); + FilterCard filter = new FilterCreatureCard("creature card with mana value " + xValue); + filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, xValue)); + ability.addTarget(new TargetCardInExile(filter, CardUtil.getExileZoneId(game, ability))); + } +} + +class GelatinousCubeEffect extends OneShotEffect { + + GelatinousCubeEffect() { + super(Outcome.Benefit); + staticText = "put target creature card with mana value X exiled with {this} into its owner's graveyard"; + } + + private GelatinousCubeEffect(final GelatinousCubeEffect effect) { + super(effect); + } + + @Override + public GelatinousCubeEffect copy() { + return new GelatinousCubeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + return player != null && card != null && player.moveCards(card, Zone.GRAVEYARD, source, game); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 62636f298c4..cb71589931d 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -105,6 +105,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Forest", 278, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forsworn Paladin", 104, Rarity.RARE, mage.cards.f.ForswornPaladin.class)); cards.add(new SetCardInfo("Froghemoth", 184, Rarity.RARE, mage.cards.f.Froghemoth.class)); + cards.add(new SetCardInfo("Gelatinous Cube", 105, Rarity.RARE, mage.cards.g.GelatinousCube.class)); cards.add(new SetCardInfo("Gloom Stalker", 16, Rarity.COMMON, mage.cards.g.GloomStalker.class)); cards.add(new SetCardInfo("Gnoll Hunter", 185, Rarity.COMMON, mage.cards.g.GnollHunter.class)); cards.add(new SetCardInfo("Goblin Javelineer", 144, Rarity.COMMON, mage.cards.g.GoblinJavelineer.class)); From c08c4bbb36cafbbd228fe50aa9b07c11d1491a64 Mon Sep 17 00:00:00 2001 From: Daniel Bomar Date: Fri, 16 Jul 2021 11:00:59 -0500 Subject: [PATCH 12/48] [AFR] Implemented Demilich --- Mage.Sets/src/mage/cards/d/Demilich.java | 168 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + ...SpellCostReductionForEachSourceEffect.java | 2 +- 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/d/Demilich.java diff --git a/Mage.Sets/src/mage/cards/d/Demilich.java b/Mage.Sets/src/mage/cards/d/Demilich.java new file mode 100644 index 00000000000..cb64df7b963 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Demilich.java @@ -0,0 +1,168 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.common.SpellsCastWatcher; + +/** + * + * @author weirddan455 + */ +public final class Demilich extends CardImpl { + + public Demilich(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{U}{U}{U}"); + + this.subtype.add(SubType.SKELETON); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // This spell costs {U} less to cast for each instant and sorcery you've cast this turn. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect( + new ManaCostsImpl<>("{U}"), DemilichValue.instance + )).addHint(new ValueHint("Instants and sorceries you've cast this turn", DemilichValue.instance)), new SpellsCastWatcher()); + + // Whenever Demilich attacks, exile up to one target instant or sorcery card from your graveyard. Copy it. You may cast the copy. + Ability ability = new AttacksTriggeredAbility(new DemilichCopyEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // You may cast Demilich from your graveyard by exiling four instants and/or sorcery cards from your graveyard in addition to paying its other costs. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new DemilichPlayEffect())); + } + + private Demilich(final Demilich card) { + super(card); + } + + @Override + public Demilich copy() { + return new Demilich(this); + } +} + +enum DemilichValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int spells = 0; + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher != null) { + for (Spell spell : watcher.getSpellsCastThisTurn(sourceAbility.getControllerId())) { + if (spell.isInstantOrSorcery()) { + spells++; + } + } + } + return spells; + } + + @Override + public DemilichValue copy() { + return instance; + } + + @Override + public String getMessage() { + return "instant and sorcery you've cast this turn"; + } +} + +class DemilichCopyEffect extends OneShotEffect { + + public DemilichCopyEffect() { + super(Outcome.Benefit); + this.staticText = "exile up to one target instant or sorcery card from your graveyard. Copy it. You may cast the copy"; + } + + private DemilichCopyEffect(final DemilichCopyEffect effect) { + super(effect); + } + + @Override + public DemilichCopyEffect copy() { + return new DemilichCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(targetPointer.getFirst(game, source)); + if (controller == null || card == null) { + return false; + } + controller.moveCards(card, Zone.EXILED, source, game); + if (controller.chooseUse(outcome, "Cast copy of " + card.getName() + '?', source, game)) { + Card copiedCard = game.copyCard(card, source, controller.getId()); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + controller.cast(controller.chooseAbilityForCast(copiedCard, game, false), + game, false, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + } + return true; + } +} + +class DemilichPlayEffect extends AsThoughEffectImpl { + + public DemilichPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + this.staticText = "You may cast {this} from your graveyard by exiling four instants and/or sorcery cards from your graveyard in addition to paying its other costs"; + } + + private DemilichPlayEffect(final DemilichPlayEffect effect) { + super(effect); + } + + @Override + public DemilichPlayEffect copy() { + return new DemilichPlayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (source.getSourceId().equals(objectId) && source.isControlledBy(affectedControllerId) + && game.getState().getZone(objectId) == Zone.GRAVEYARD) { + Player controller = game.getPlayer(affectedControllerId); + if (controller != null) { + Costs costs = new CostsImpl<>(); + costs.add(new ExileFromGraveCost(new TargetCardInYourGraveyard(4, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD))); + controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{U}{U}{U}{U}"), costs); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index cb71589931d..27697d31a0b 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -67,6 +67,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Death-Priest of Myrkul", 95, Rarity.UNCOMMON, mage.cards.d.DeathPriestOfMyrkul.class)); cards.add(new SetCardInfo("Delina, Wild Mage", 138, Rarity.RARE, mage.cards.d.DelinaWildMage.class)); cards.add(new SetCardInfo("Delver's Torch", 10, Rarity.COMMON, mage.cards.d.DelversTorch.class)); + cards.add(new SetCardInfo("Demilich", 53, Rarity.MYTHIC, mage.cards.d.Demilich.class)); cards.add(new SetCardInfo("Demogorgon's Clutches", 96, Rarity.UNCOMMON, mage.cards.d.DemogorgonsClutches.class)); cards.add(new SetCardInfo("Den of the Bugbear", 254, Rarity.RARE, mage.cards.d.DenOfTheBugbear.class)); cards.add(new SetCardInfo("Devoted Paladin", 11, Rarity.COMMON, mage.cards.d.DevotedPaladin.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java index 5b998cbd3b2..5025777ff60 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java @@ -62,7 +62,7 @@ public class SpellCostReductionForEachSourceEffect extends CostModificationEffec if (reduceManaCosts != null) { // color reduce ManaCosts needReduceMana = new ManaCostsImpl<>(); - for (int i = 0; i <= needReduceAmount; i++) { + for (int i = 0; i < needReduceAmount; i++) { needReduceMana.add(reduceManaCosts.copy()); } CardUtil.adjustCost((SpellAbility) abilityToModify, needReduceMana, false); From d3b5c51428e4172b187ca7dad1e25a31a46b41a5 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 13:08:24 -0400 Subject: [PATCH 13/48] fixed test failures (fixes #8016) --- Mage.Sets/src/mage/cards/s/SpareDagger.java | 2 +- Mage.Sets/src/mage/cards/t/TrickstersTalisman.java | 2 +- .../common/BeginningOfEndStepTriggeredAbility.java | 3 --- .../continuous/GainAbilityWithAttachmentEffect.java | 10 ++++++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SpareDagger.java b/Mage.Sets/src/mage/cards/s/SpareDagger.java index eb77c8d121f..2fef867de9a 100644 --- a/Mage.Sets/src/mage/cards/s/SpareDagger.java +++ b/Mage.Sets/src/mage/cards/s/SpareDagger.java @@ -53,7 +53,7 @@ class SpareDaggerEffect extends GainAbilityWithAttachmentEffect { SpareDaggerEffect() { super("and has \"Whenever this creature attacks, you may sacrifice {this}. " + "When you do, this creature deals 1 damage to any target.\"", - (Effect) null, null, new SacrificeAttachmentCost(), null); + (Effect) null, null, new SacrificeAttachmentCost()); } private SpareDaggerEffect(final SpareDaggerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java index 8213c858f8a..50b169b5338 100644 --- a/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java +++ b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java @@ -51,7 +51,7 @@ class TrickstersTalismanEffect extends GainAbilityWithAttachmentEffect { TrickstersTalismanEffect() { super("and has \"Whenever this creature deals combat damage to a player, " + "you may sacrifice {this}. If you do, create a token that's a copy of this creature.\"", - (Effect) null, null, new SacrificeAttachmentCost(), null); + (Effect) null, null, new SacrificeAttachmentCost()); } private TrickstersTalismanEffect(final TrickstersTalismanEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java index e11b5b6b29c..fa0b18c43df 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java @@ -134,9 +134,6 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { } return clauseText + ", "; } - System.out.println("=================="); - System.out.println(clauseText); - System.out.println("=================="); return "if " + clauseText + ", "; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java index 631195bc2cf..4b5262f6234 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java @@ -19,7 +19,7 @@ import mage.target.Target; import mage.target.Targets; import mage.target.targetpointer.FixedTarget; -import java.util.Arrays; +import java.util.Objects; /** * @author TheElk801 @@ -39,8 +39,14 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.staticText = rule; this.effects.addAll(effects); + this.effects.removeIf(Objects::isNull); this.targets.addAll(targets); - this.costs.addAll(Arrays.asList(costs)); + this.targets.removeIf(Objects::isNull); + for (Cost cost : costs) { + if (cost != null) { + this.costs.add(cost); + } + } this.useAttachedCost = attachedCost; this.generateGainAbilityDependencies(makeAbility(null, null), null); } From fd40085e4fa0f7a8dd6436f4c006f7b3fea5b209 Mon Sep 17 00:00:00 2001 From: Joseph Zeffiro <32608602+zeffirojoe@users.noreply.github.com> Date: Fri, 16 Jul 2021 16:28:18 -0400 Subject: [PATCH 14/48] [AFR] Implementing Fiendlash (#8011) * [AFR] Implementing Fiendlash * Updating Fiendlash implementation * Changing how trigger occurs during combat * Updating fiendlash logic * Updating copy constructor * Using DamagedPermanentBatchEvent * Fiendlash implementation --- Mage.Sets/src/mage/cards/f/Fiendlash.java | 162 ++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 163 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/Fiendlash.java diff --git a/Mage.Sets/src/mage/cards/f/Fiendlash.java b/Mage.Sets/src/mage/cards/f/Fiendlash.java new file mode 100644 index 00000000000..11ab580dcb9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/Fiendlash.java @@ -0,0 +1,162 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.ReachAbility; +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.constants.Zone; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPermanentBatchEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetPlayerOrPlaneswalker; + +/** + * + * @author zeffirojoe + */ +public final class Fiendlash extends CardImpl { + + public Fiendlash(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT }, "{1}{R}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +2/+0 and has reach. + Ability staticAbility = new SimpleStaticAbility(new BoostEquippedEffect(2, 0)); + staticAbility.addEffect(new GainAbilityAttachedEffect(ReachAbility.getInstance(), AttachmentType.EQUIPMENT) + .setText("and has reach")); + this.addAbility(staticAbility); + + // Whenever equipped creature is dealt damage, it deals damage equal to its + // power to target player or planeswalker. + this.addAbility(new FiendlashTriggeredAbility()); + + // Equip {2}{R} + this.addAbility(new EquipAbility(Outcome.AddAbility, new ManaCostsImpl<>("{2}{R}"))); + } + + private Fiendlash(final Fiendlash card) { + super(card); + } + + @Override + public Fiendlash copy() { + return new Fiendlash(this); + } +} + +class FiendlashTriggeredAbility extends TriggeredAbilityImpl { + + FiendlashTriggeredAbility() { + super(Zone.BATTLEFIELD, new FiendlashEffect(), false); + this.addTarget(new TargetPlayerOrPlaneswalker()); + } + + private FiendlashTriggeredAbility(final FiendlashTriggeredAbility ability) { + super(ability); + } + + @Override + public FiendlashTriggeredAbility copy() { + return new FiendlashTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent equipment = game.getPermanent(this.getSourceId()); + if (equipment == null) { + return false; + } + + UUID attachedCreature = equipment.getAttachedTo(); + if (attachedCreature == null) { + return false; + } + + game.getState().setValue("Fiendlash" + equipment.getId(), attachedCreature); + + DamagedPermanentBatchEvent dEvent = (DamagedPermanentBatchEvent) event; + for (DamagedEvent damagedEvent : dEvent.getEvents()) { + UUID targetID = damagedEvent.getTargetId(); + if (targetID == null) { + continue; + } + + if (targetID == attachedCreature) { + return true; + } + } + + return false; + } + + @Override + public String getRule() { + return "Whenever equipped creature is dealt damage, it deals damage equal to its power to target player or planeswalker."; + } +} + +class FiendlashEffect extends OneShotEffect { + + FiendlashEffect() { + super(Outcome.Benefit); + } + + private FiendlashEffect(final FiendlashEffect effect) { + super(effect); + } + + @Override + public FiendlashEffect copy() { + return new FiendlashEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game + .getPermanentOrLKIBattlefield((UUID) game.getState().getValue("Fiendlash" + source.getSourceId())); + if (creature == null) { + return false; + } + + int damage = creature.getPower().getValue(); + if (damage < 1) { + return false; + } + + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + if (permanent.isPlaneswalker()) { + permanent.damage(damage, creature.getId(), source, game); + return true; + } + } + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + player.damage(damage, creature.getId(), source, game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index da14a41a9ba..01355433d4c 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -97,6 +97,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Fellwar Stone", 206, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Fertile Ground", 158, Rarity.COMMON, mage.cards.f.FertileGround.class)); cards.add(new SetCardInfo("Fiend of the Shadows", 99, Rarity.RARE, mage.cards.f.FiendOfTheShadows.class)); + cards.add(new SetCardInfo("Fiendlash", 31, Rarity.RARE, mage.cards.f.Fiendlash.class)); cards.add(new SetCardInfo("Fleecemane Lion", 185, Rarity.RARE, mage.cards.f.FleecemaneLion.class)); cards.add(new SetCardInfo("Flood Plain", 237, Rarity.UNCOMMON, mage.cards.f.FloodPlain.class)); cards.add(new SetCardInfo("Forbidden Alchemy", 84, Rarity.UNCOMMON, mage.cards.f.ForbiddenAlchemy.class)); From 29a4fbc27a79732b28cab9e9b4a9003e265061c8 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Fri, 16 Jul 2021 16:46:42 -0500 Subject: [PATCH 15/48] - Added another complex layer test to LayerTests unit test --- .../test/cards/continuous/LayerTests.java | 113 +++++++++++++----- 1 file changed, 82 insertions(+), 31 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java index 42cbe4458eb..fd37221edf4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java @@ -1,7 +1,6 @@ - - package org.mage.test.cards.continuous; +import mage.ObjectColor; import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.SubType; @@ -16,7 +15,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * @author jeffwadsworth */ public class LayerTests extends CardTestPlayerBase { - + @Test public void testMultipleLayeredDependency() { //Conspiracy->Opalescence->Enchanted Evening @@ -24,33 +23,33 @@ public class LayerTests extends CardTestPlayerBase { //Opalescence is dependent on Enchanted Evening //So, the effects should be applied as follows: //Enchanted Evening->Opalescence->Conspiracy - + addCard(Zone.HAND, playerA, "Conspiracy"); // creatures get chosen subtype addCard(Zone.HAND, playerA, "Opalescence"); // enchantments become creatures P/T equal to CMC addCard(Zone.HAND, playerA, "Enchanted Evening"); // all permanents become enchantments - + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); addCard(Zone.BATTLEFIELD, playerA, "Island", 5); addCard(Zone.BATTLEFIELD, playerA, "Glorious Anthem", 1); // keep lands alive // all creatures +1/+1 - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Conspiracy"); setChoice(playerA, "Advisor"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Opalescence"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enchanted Evening"); - + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertAllCommandsUsed(); - + assertType("Swamp", CardType.LAND, SubType.ADVISOR); // Subtype changed with Conspiracy assertPowerToughness(playerA, "Swamp", 1, 1); // boosted with Glorious Anthem assertType("Enchanted Evening", CardType.ENCHANTMENT, SubType.ADVISOR); // Subtype changed with Conspiracy assertPowerToughness(playerA, "Enchanted Evening", 6, 6); // boosted with Glorious Anthem - + } - + @Test public void testMycosynthLatticeAndMarchOfTheMachinesAndHumility() { // example from Reddit @@ -61,32 +60,32 @@ public class LayerTests extends CardTestPlayerBase { Does the game get stuck in an endless loop of each card gaining and losing its respective creature-ness and abilities? Answer: No, they all die - */ - + */ + addCard(Zone.HAND, playerA, "Mycosynth Lattice"); // all permanents are artifacts addCard(Zone.HAND, playerA, "March of the Machines"); // artifacts become creatures addCard(Zone.HAND, playerA, "Humility"); // all creatures lose abilities and P/T is 1/1 - + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); addCard(Zone.BATTLEFIELD, playerA, "Island", 10); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Humility"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "March of the Machines"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mycosynth Lattice"); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertAllCommandsUsed(); - + // everything dies assertPermanentCount(playerA, "Humility", 0); assertPermanentCount(playerA, "March of the Machines", 0); assertPermanentCount(playerA, "Mycosynth Lattice", 0); assertPermanentCount(playerA, "Island", 0); - + } - + @Test public void testBloodMoon_UrborgTombOfYawgmothInteraction() { // Blood Moon : Nonbasic lands are Mountains. @@ -95,7 +94,7 @@ public class LayerTests extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Blood Moon"); addCard(Zone.BATTLEFIELD, playerA, "Urborg, Tomb of Yawgmoth", 1); // non-basic land addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -112,35 +111,35 @@ public class LayerTests extends CardTestPlayerBase { /*In play there is a Grizzly Bears which has already been Giant Growthed, a Bog Wraith enchanted by a Lignify, and Figure of Destiny with its 3rd ability activated. I then cast a Mirrorweave targeting the Figure of Destiny. What does each creature look like? - */ + */ addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); addCard(Zone.HAND, playerA, "Giant Growth", 1); addCard(Zone.BATTLEFIELD, playerA, "Bog Wraith", 1); addCard(Zone.HAND, playerA, "Lignify", 1); addCard(Zone.BATTLEFIELD, playerA, "Figure of Destiny", 1); addCard(Zone.HAND, playerA, "Mirrorweave", 1); - + addCard(Zone.BATTLEFIELD, playerA, "Forest", 20); addCard(Zone.BATTLEFIELD, playerA, "Island", 20); addCard(Zone.BATTLEFIELD, playerA, "Plains", 20); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lignify", "Bog Wrath"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R/W}:"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R/W}{R/W}{R/W}:"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R/W}{R/W}{R/W}{R/W}{R/W}{R/W}:"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mirrorweave", "Figure of Destiny"); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - + assertPermanentCount(playerA, "Figure of Destiny", 3); assertPowerToughness(playerA, "Figure of Destiny", 4, 4, Filter.ComparisonScope.All); assertPowerToughness(playerA, "Figure of Destiny", 8, 8, Filter.ComparisonScope.All); assertPowerToughness(playerA, "Figure of Destiny", 0, 4, Filter.ComparisonScope.All); } - + @Test public void testUrborgWithAnimateLandAndOvinize() { // Animate Land: target land is a 3/3 until end of turn and is still a land. @@ -152,7 +151,7 @@ public class LayerTests extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Urborg, Tomb of Yawgmoth", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Animate Land", "Urborg, Tomb of Yawgmoth"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ovinize", "Urborg, Tomb of Yawgmoth"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -172,25 +171,77 @@ public class LayerTests extends CardTestPlayerBase { one of his Hypnotist's abilities, targeting the Mimic. Aiden attacks with the Mimic, and casts Inside Out before the damage step. Once Inside Out resolves, Nick activates the ability of his other Hypnotist. How much damage will the Mimic deal? - */ + */ addCard(Zone.HAND, playerA, "Scourge of the Nobilis", 1); addCard(Zone.HAND, playerA, "Inside Out", 1); addCard(Zone.BATTLEFIELD, playerA, "Battlegate Mimic", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); addCard(Zone.BATTLEFIELD, playerA, "Island", 8); - + addCard(Zone.BATTLEFIELD, playerB, "Wilderness Hypnotist", 2); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of the Nobilis", "Battlegate Mimic"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}:", "Battlegate Mimic"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inside Out", "Battlegate Mimic"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}:", "Battlegate Mimic"); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPowerToughness(playerA, "Battlegate Mimic", 4, 2); } - + + @Test + public void testExampleFromReddit2021() { + // Life and Limb, Humility, and Yavimaya, Cradle of Growth + // Result: all lands (including Yavimaya) will be 1/1 green Forest + // lands and Saproling creatures in addition to their other types, + // and have no abilities. + + /* + All Forests and all Saprolings are 1/1 green Saproling creatures + and Forest lands in addition to their other types. (They're affected by summoning sickness.) + */ + addCard(Zone.BATTLEFIELD, playerA, "Life and Limb", 1); + + /* + All creatures lose all abilities and have base power and toughness 1/1. + */ + addCard(Zone.BATTLEFIELD, playerA, "Humility", 1); + + /* + Each land is a Forest in addition to its other land types. + */ + addCard(Zone.BATTLEFIELD, playerA, "Yavimaya, Cradle of Growth", 1); + + // added some lands to the battlefield + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + // all lands are forests in addition to other types + assertType("Plains", CardType.CREATURE, SubType.FOREST); + assertType("Swamp", CardType.CREATURE, SubType.FOREST); + assertType("Island", CardType.CREATURE, SubType.FOREST); + assertType("Yavimaya, Cradle of Growth", CardType.CREATURE, SubType.FOREST); + + // all lands are 1/1 Saproling creatures + assertPowerToughness(playerA, "Plains", 1, 1); + assertPowerToughness(playerA, "Swamp", 1, 1); + assertPowerToughness(playerB, "Island", 1, 1); + assertPowerToughness(playerA, "Yavimaya, Cradle of Growth", 1, 1); + + // all lands are green + assertColor(playerA, "Plains", ObjectColor.GREEN, true); + assertColor(playerA, "Swamp", ObjectColor.GREEN, true); + assertColor(playerB, "Island", ObjectColor.GREEN, true); + assertColor(playerA, "Yavimaya, Cradle of Growth", ObjectColor.GREEN, true); + + } + } From 4150a5bb12252a947d69b6386c215b51ab2275b8 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 18:43:09 -0400 Subject: [PATCH 16/48] small change to test failure fix --- .../continuous/GainAbilityWithAttachmentEffect.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java index 4b5262f6234..8cfe2280bbd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java @@ -19,7 +19,7 @@ import mage.target.Target; import mage.target.Targets; import mage.target.targetpointer.FixedTarget; -import java.util.Objects; +import java.util.Collections; /** * @author TheElk801 @@ -39,14 +39,8 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.staticText = rule; this.effects.addAll(effects); - this.effects.removeIf(Objects::isNull); this.targets.addAll(targets); - this.targets.removeIf(Objects::isNull); - for (Cost cost : costs) { - if (cost != null) { - this.costs.add(cost); - } - } + Collections.addAll(this.costs, costs); this.useAttachedCost = attachedCost; this.generateGainAbilityDependencies(makeAbility(null, null), null); } From b94a47b6ab9fbc856d68803ad15d91ea1ec3e34c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 19:45:12 -0400 Subject: [PATCH 17/48] [AFR] Implemented Flameskull --- Mage.Sets/src/mage/cards/f/Flameskull.java | 156 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 157 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/Flameskull.java diff --git a/Mage.Sets/src/mage/cards/f/Flameskull.java b/Mage.Sets/src/mage/cards/f/Flameskull.java new file mode 100644 index 00000000000..7d3ac303d89 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/Flameskull.java @@ -0,0 +1,156 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObject; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author TheElk801 + */ +public final class Flameskull extends CardImpl { + + public Flameskull(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); + + this.subtype.add(SubType.SKELETON); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Flameskull can't block. + this.addAbility(new CantBlockAbility()); + + // Rejuvenation — When Flameskull dies, exile it. If you do, exile the top card of your library. Until the end of your next turn, you may play one of those cards. + this.addAbility(new DiesSourceTriggeredAbility(new FlameskullEffect()) + .withFlavorWord("Rejuventation"), new FlameskullWatcher()); + } + + private Flameskull(final Flameskull card) { + super(card); + } + + @Override + public Flameskull copy() { + return new Flameskull(this); + } +} + +class FlameskullEffect extends OneShotEffect { + + FlameskullEffect() { + super(Outcome.Benefit); + staticText = "exile it. If you do, exile the top card of your library. " + + "Until the end of your next turn, you may play one of those cards"; + } + + private FlameskullEffect(final FlameskullEffect effect) { + super(effect); + } + + @Override + public FlameskullEffect copy() { + return new FlameskullEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObjectIfItStillExists(game); + if (player == null || !(sourceObject instanceof Card)) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getFromTop(game)); + cards.add((Card) sourceObject); + player.moveCards(cards, Zone.EXILED, source, game); + game.addEffect(new FlameskullPlayEffect(cards, game), source); + return true; + } +} + +class FlameskullPlayEffect extends AsThoughEffectImpl { + + private final Set morSet = new HashSet<>(); + + FlameskullPlayEffect(Cards cards, Game game) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.UntilEndOfYourNextTurn, Outcome.Benefit); + cards.stream() + .map(uuid -> new MageObjectReference(uuid, game)) + .forEach(morSet::add); + } + + private FlameskullPlayEffect(final FlameskullPlayEffect effect) { + super(effect); + this.morSet.addAll(effect.morSet); + } + + @Override + public FlameskullPlayEffect copy() { + return new FlameskullPlayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + UUID objectIdToCast = CardUtil.getMainCardId(game, sourceId); + return source.isControlledBy(affectedControllerId) + && morSet.stream().anyMatch(mor -> mor.refersTo(objectIdToCast, game)) + && FlameskullWatcher.checkRef(source, morSet, game); + } +} + +class FlameskullWatcher extends Watcher { + + private final Map> morMap = new HashMap<>(); + private static final Set emptySet = new HashSet<>(); + + FlameskullWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST + || event.getAdditionalReference() == null) { + return; + } + MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + Spell spell = game.getSpell(event.getTargetId()); + if (mor == null || spell == null) { + return; + } + morMap.computeIfAbsent(mor, x -> new HashSet<>()) + .add(new MageObjectReference(spell.getMainCard(), game, -1)); + } + + static boolean checkRef(Ability source, Set morSet, Game game) { + FlameskullWatcher watcher = game.getState().getWatcher(FlameskullWatcher.class); + return watcher != null + && watcher + .morMap + .getOrDefault(new MageObjectReference(source.getSourceObject(game), game), emptySet) + .stream() + .noneMatch(morSet::contains); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 27697d31a0b..2b9db117797 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -101,6 +101,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Fifty Feet of Rope", 244, Rarity.UNCOMMON, mage.cards.f.FiftyFeetOfRope.class)); cards.add(new SetCardInfo("Fighter Class", 222, Rarity.RARE, mage.cards.f.FighterClass.class)); cards.add(new SetCardInfo("Find the Path", 183, Rarity.COMMON, mage.cards.f.FindThePath.class)); + cards.add(new SetCardInfo("Flameskull", 143, Rarity.MYTHIC, mage.cards.f.Flameskull.class)); cards.add(new SetCardInfo("Flumph", 15, Rarity.RARE, mage.cards.f.Flumph.class)); cards.add(new SetCardInfo("Fly", 59, Rarity.UNCOMMON, mage.cards.f.Fly.class)); cards.add(new SetCardInfo("Forest", 278, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); From ff43670a2a62cadd396a252d92929168759613b6 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 16 Jul 2021 20:56:43 -0400 Subject: [PATCH 18/48] [AFR] Implemented You Find Some Prisoners --- .../mage/cards/y/YouFindSomePrisoners.java | 88 +++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 89 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/y/YouFindSomePrisoners.java diff --git a/Mage.Sets/src/mage/cards/y/YouFindSomePrisoners.java b/Mage.Sets/src/mage/cards/y/YouFindSomePrisoners.java new file mode 100644 index 00000000000..720a3e44259 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YouFindSomePrisoners.java @@ -0,0 +1,88 @@ +package mage.cards.y; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YouFindSomePrisoners extends CardImpl { + + public YouFindSomePrisoners(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Choose one — + // • Break Their Chains — Destroy target artifact. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetArtifactPermanent()); + this.getSpellAbility().withFirstModeFlavorWord("Break Their Chains"); + + // • Interrogate Them — Exile the top three cards of target opponent's library. Choose one of them. Until the end of your next turn, you may play that card, and you may spend mana as through it were mana of any color to cast it. + Mode mode = new Mode(new YouFindSomePrisonersEffect()); + mode.addTarget(new TargetOpponent()); + this.getSpellAbility().addMode(mode.withFlavorWord("Interrogate Them")); + } + + private YouFindSomePrisoners(final YouFindSomePrisoners card) { + super(card); + } + + @Override + public YouFindSomePrisoners copy() { + return new YouFindSomePrisoners(this); + } +} + +class YouFindSomePrisonersEffect extends OneShotEffect { + + YouFindSomePrisonersEffect() { + super(Outcome.Benefit); + staticText = "exile the top three cards of target opponent's library. " + + "Choose one of them. Until the end of your next turn, you may play that card, " + + "and you may spend mana as though it were mana of any color to cast it"; + } + + private YouFindSomePrisonersEffect(final YouFindSomePrisonersEffect effect) { + super(effect); + } + + @Override + public YouFindSomePrisonersEffect copy() { + return new YouFindSomePrisonersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(source.getFirstTarget()); + if (player == null || opponent == null) { + return false; + } + Cards cards = new CardsImpl(opponent.getLibrary().getTopCards(game, 3)); + player.moveCards(cards, Zone.EXILED, source, game); + TargetCardInExile target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.setNotTarget(true); + player.choose(Outcome.PlayForFree, cards, target, game); + Card card = cards.get(target.getFirstTarget(), game); + if (card != null) { + CardUtil.makeCardPlayable(game, source, card, Duration.UntilEndOfYourNextTurn, true); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 2b9db117797..fa2713df9fa 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -265,6 +265,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class)); cards.add(new SetCardInfo("You Come to a River", 83, Rarity.COMMON, mage.cards.y.YouComeToARiver.class)); cards.add(new SetCardInfo("You Come to the Gnoll Camp", 168, Rarity.COMMON, mage.cards.y.YouComeToTheGnollCamp.class)); + cards.add(new SetCardInfo("You Find Some Prisoners", 169, Rarity.UNCOMMON, mage.cards.y.YouFindSomePrisoners.class)); cards.add(new SetCardInfo("You Find a Cursed Idol", 213, Rarity.COMMON, mage.cards.y.YouFindACursedIdol.class)); cards.add(new SetCardInfo("You Find the Villains' Lair", 84, Rarity.COMMON, mage.cards.y.YouFindTheVillainsLair.class)); cards.add(new SetCardInfo("You Happen On a Glade", 214, Rarity.UNCOMMON, mage.cards.y.YouHappenOnAGlade.class)); From 45246b2d058fbb6174c05eb7b5fbc0481ef10699 Mon Sep 17 00:00:00 2001 From: Joe Zeffiro Date: Fri, 16 Jul 2021 21:49:53 -0400 Subject: [PATCH 19/48] [AFC] Implemented Fey Steed --- Mage.Sets/src/mage/cards/f/FeySteed.java | 110 ++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 111 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FeySteed.java diff --git a/Mage.Sets/src/mage/cards/f/FeySteed.java b/Mage.Sets/src/mage/cards/f/FeySteed.java new file mode 100644 index 00000000000..6266d63078f --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FeySteed.java @@ -0,0 +1,110 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; + +/** + * + * @author anonymous + */ +public final class FeySteed extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent( + "another target attacking creature you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(AnotherPredicate.instance); + filter.add(AttackingPredicate.instance); + } + + public FeySteed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{2}{W}{W}"); + + this.subtype.add(SubType.ELK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever Fey Steed attacks, another target attacking creature you control + // gains indestructible until end of turn. + Ability ability = new AttacksTriggeredAbility( + new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), false); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + + // Whenever a creature or planeswalker you control becomes the target of a spell + // or ability an opponent controls, you may draw a card. + this.addAbility(new FeySteedTriggeredAbility()); + } + + private FeySteed(final FeySteed card) { + super(card); + } + + @Override + public FeySteed copy() { + return new FeySteed(this); + } +} + +class FeySteedTriggeredAbility extends TriggeredAbilityImpl { + + public FeySteedTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); + } + + public FeySteedTriggeredAbility(FeySteedTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGETED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Player targetter = game.getPlayer(event.getPlayerId()); + if (targetter == null || !targetter.hasOpponent(this.controllerId, game)) { + return false; + } + + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !permanent.isControlledBy(this.getControllerId()) + || (!permanent.isCreature(game) && !permanent.isPlaneswalker(game))) { + return false; + } + return true; + } + + @Override + public String getRule() { + return "Whenever a creature or planeswalker you control becomes the target of a spell or ability an opponent controls, you may draw a card"; + } + + @Override + public FeySteedTriggeredAbility copy() { + return new FeySteedTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 01355433d4c..9e72b29c288 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -96,6 +96,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Explorer's Scope", 205, Rarity.COMMON, mage.cards.e.ExplorersScope.class)); cards.add(new SetCardInfo("Fellwar Stone", 206, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Fertile Ground", 158, Rarity.COMMON, mage.cards.f.FertileGround.class)); + cards.add(new SetCardInfo("Fey Steed", 5, Rarity.RARE, mage.cards.f.FeySteed.class)); cards.add(new SetCardInfo("Fiend of the Shadows", 99, Rarity.RARE, mage.cards.f.FiendOfTheShadows.class)); cards.add(new SetCardInfo("Fiendlash", 31, Rarity.RARE, mage.cards.f.Fiendlash.class)); cards.add(new SetCardInfo("Fleecemane Lion", 185, Rarity.RARE, mage.cards.f.FleecemaneLion.class)); From 86e5c5cb501833560e830c09d5864b633b88c0dc Mon Sep 17 00:00:00 2001 From: Joseph Zeffiro <32608602+zeffirojoe@users.noreply.github.com> Date: Sat, 17 Jul 2021 09:33:30 -0400 Subject: [PATCH 20/48] Fixing multiple triggers during combat (#8017) * Fixing multiple triggers during combat * Damage to Source Logic * Removing unused references --- .../src/mage/cards/b/BlazingSunsteel.java | 54 +++++++++++++------ .../DealtDamageToSourceTriggeredAbility.java | 46 ++++++++-------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java b/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java index eb9218ad7f0..36990b5141e 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java +++ b/Mage.Sets/src/mage/cards/b/BlazingSunsteel.java @@ -15,6 +15,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPermanentBatchEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -28,16 +30,16 @@ import java.util.UUID; public final class BlazingSunsteel extends CardImpl { public BlazingSunsteel(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}"); + super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT }, "{1}{R}"); this.subtype.add(SubType.EQUIPMENT); // Equipped creature gets +1/+0 for each opponent you have. - this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect( - OpponentsCount.instance, StaticValue.get(0) - ).setText("equipped creature gets +1/+0 for each opponent you have"))); + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(OpponentsCount.instance, StaticValue.get(0)) + .setText("equipped creature gets +1/+0 for each opponent you have"))); - // Whenever equipped creature is dealt damage, it deals that much damage to any target. + // Whenever equipped creature is dealt damage, it deals that much damage to any + // target. this.addAbility(new BlazingSunsteelTriggeredAbility()); // Equip {4} @@ -72,20 +74,40 @@ class BlazingSunsteelTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT_BATCH; } @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment == null - || equipment.getAttachedTo() == null - || !event.getTargetId().equals(equipment.getAttachedTo())) { + if (equipment == null) { return false; } - this.getEffects().setValue("equipped", game.getPermanent(equipment.getAttachedTo())); - this.getEffects().setValue("damage", event.getAmount()); - return true; + + UUID attachedCreature = equipment.getAttachedTo(); + if (attachedCreature == null) { + return false; + } + + int damage = 0; + DamagedPermanentBatchEvent dEvent = (DamagedPermanentBatchEvent) event; + for (DamagedEvent damagedEvent : dEvent.getEvents()) { + UUID targetID = damagedEvent.getTargetId(); + if (targetID == null) { + continue; + } + + if (targetID == attachedCreature) { + damage += damagedEvent.getAmount(); + } + } + + if (damage > 0) { + this.getEffects().setValue("equipped", attachedCreature); + this.getEffects().setValue("damage", damage); + return true; + } + return false; } @Override @@ -111,11 +133,13 @@ class BlazingSunsteelEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = (Permanent) getValue("equipped"); - Integer damage = (Integer) getValue("damage"); - if (creature == null || damage == null || damage < 1) { + Permanent creature = game.getPermanentOrLKIBattlefield((UUID) getValue("equipped")); + Integer damage = (Integer)getValue("damage"); + + if (creature == null || damage == null || damage < 1) { return false; } + Permanent permanent = game.getPermanent(source.getFirstTarget()); if (permanent != null) { permanent.damage(damage, creature.getId(), source, game); diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java index 7ccc832d36f..97fb969e584 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageToSourceTriggeredAbility.java @@ -1,12 +1,15 @@ package mage.abilities.common; +import java.util.UUID; + import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.AbilityWord; import mage.constants.Zone; import mage.game.Game; import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPermanentBatchEvent; import mage.game.events.GameEvent; /** @@ -15,7 +18,6 @@ import mage.game.events.GameEvent; public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { private final boolean useValue; - private boolean usedForCombatDamageStep; public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional) { this(effect, optional, false); @@ -28,7 +30,6 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional, boolean enrage, boolean useValue) { super(Zone.BATTLEFIELD, effect, optional); this.useValue = useValue; - this.usedForCombatDamageStep = false; if (enrage) { this.setAbilityWord(AbilityWord.ENRAGE); } @@ -37,7 +38,6 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { public DealtDamageToSourceTriggeredAbility(final DealtDamageToSourceTriggeredAbility ability) { super(ability); this.useValue = ability.useValue; - this.usedForCombatDamageStep = ability.usedForCombatDamageStep; } @Override @@ -47,30 +47,34 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST; + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT_BATCH; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGED_PERMANENT && event.getTargetId().equals(getSourceId())) { - if (useValue) { -// TODO: this ability should only trigger once for multiple creatures dealing combat damage. -// If the damaged creature uses the amount (e.g. Boros Reckoner), this will still trigger separately instead of all at once - getEffects().setValue("damage", event.getAmount()); - return true; - } else { - if (((DamagedEvent) event).isCombatDamage()) { - if (!usedForCombatDamageStep) { - usedForCombatDamageStep = true; - return true; - } - } else { - return true; - } + + if (event == null || game == null || this.getSourceId() == null) { + return false; + } + + int damage = 0; + DamagedPermanentBatchEvent dEvent = (DamagedPermanentBatchEvent) event; + for (DamagedEvent damagedEvent : dEvent.getEvents()) { + UUID targetID = damagedEvent.getTargetId(); + if (targetID == null) { + continue; + } + + if (targetID == this.getSourceId()) { + damage += damagedEvent.getAmount(); } } - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) { - usedForCombatDamageStep = false; + + if (damage > 0) { + if (this.useValue) { + this.getEffects().setValue("damage", damage); + } + return true; } return false; } From 167b488bf86e2ada08817d838446e4cc3189b45f Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 08:51:31 -0400 Subject: [PATCH 21/48] added morbid hints to all cards with morbid condition --- Mage.Sets/src/mage/cards/b/BonePicker.java | 3 ++- Mage.Sets/src/mage/cards/b/BrimstoneVolley.java | 2 ++ Mage.Sets/src/mage/cards/b/Bulette.java | 3 ++- Mage.Sets/src/mage/cards/c/CagedZombie.java | 3 ++- Mage.Sets/src/mage/cards/c/CaravanVigil.java | 2 ++ Mage.Sets/src/mage/cards/d/DeathreapRitual.java | 3 ++- Mage.Sets/src/mage/cards/e/EmissaryOfTheSleepless.java | 3 ++- Mage.Sets/src/mage/cards/f/FesterhideBoar.java | 3 ++- Mage.Sets/src/mage/cards/f/FungalRebirth.java | 2 ++ Mage.Sets/src/mage/cards/f/FunnelWebRecluse.java | 3 ++- Mage.Sets/src/mage/cards/g/GravetillerWurm.java | 3 ++- Mage.Sets/src/mage/cards/g/GrimWanderer.java | 3 ++- Mage.Sets/src/mage/cards/g/GruesomeDiscovery.java | 2 ++ Mage.Sets/src/mage/cards/h/HollowhengeScavenger.java | 3 ++- Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java | 2 ++ Mage.Sets/src/mage/cards/l/LifeGoesOn.java | 2 ++ Mage.Sets/src/mage/cards/l/LilianasDevotee.java | 3 ++- Mage.Sets/src/mage/cards/l/LilianasScrounger.java | 3 ++- Mage.Sets/src/mage/cards/m/MaliciousAffliction.java | 3 ++- Mage.Sets/src/mage/cards/m/MorkrutBanshee.java | 3 ++- Mage.Sets/src/mage/cards/o/OsaiVultures.java | 3 ++- Mage.Sets/src/mage/cards/p/PredatorsHowl.java | 2 ++ Mage.Sets/src/mage/cards/s/SabertoothMauler.java | 3 ++- Mage.Sets/src/mage/cards/s/SkirsdagHighPriest.java | 3 ++- Mage.Sets/src/mage/cards/s/SomberwaldSpider.java | 3 ++- Mage.Sets/src/mage/cards/t/TitanHunter.java | 3 ++- Mage.Sets/src/mage/cards/t/TragicSlip.java | 2 ++ Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java | 3 ++- Mage.Sets/src/mage/cards/u/UlvenwaldBear.java | 3 ++- Mage.Sets/src/mage/cards/v/VengefulDevil.java | 3 ++- Mage.Sets/src/mage/cards/w/Wakedancer.java | 3 ++- Mage.Sets/src/mage/cards/w/WoodlandSleuth.java | 3 ++- 32 files changed, 64 insertions(+), 24 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BonePicker.java b/Mage.Sets/src/mage/cards/b/BonePicker.java index 86a6574fa2d..cd488386634 100644 --- a/Mage.Sets/src/mage/cards/b/BonePicker.java +++ b/Mage.Sets/src/mage/cards/b/BonePicker.java @@ -7,6 +7,7 @@ import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -35,7 +36,7 @@ public final class BonePicker extends CardImpl { this.toughness = new MageInt(2); // Bone Picker costs {3} less to cast if a creature died this turn. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new BonePickerAdjustingCostsEffect()), new MorbidWatcher()); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new BonePickerAdjustingCostsEffect()).addHint(MorbidHint.instance), new MorbidWatcher()); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java b/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java index 201317ac069..4d7e74981a7 100644 --- a/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java +++ b/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java @@ -3,6 +3,7 @@ package mage.cards.b; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,6 +27,7 @@ public final class BrimstoneVolley extends CardImpl { "
Morbid — {this} deals 5 damage instead if a creature died this turn." )); this.getSpellAbility().addTarget(new TargetAnyTarget()); + this.getSpellAbility().addHint(MorbidHint.instance); this.getSpellAbility().addWatcher(new MorbidWatcher()); } diff --git a/Mage.Sets/src/mage/cards/b/Bulette.java b/Mage.Sets/src/mage/cards/b/Bulette.java index 850a60afb05..2338b591f35 100644 --- a/Mage.Sets/src/mage/cards/b/Bulette.java +++ b/Mage.Sets/src/mage/cards/b/Bulette.java @@ -6,6 +6,7 @@ import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,7 @@ public final class Bulette extends CardImpl { new BeginningOfYourEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false), MorbidCondition.instance, "At the beginning of your end step, if a creature died this turn, put a +1/+1 counter on {this}." - )); + ).addHint(MorbidHint.instance)); } private Bulette(final Bulette card) { diff --git a/Mage.Sets/src/mage/cards/c/CagedZombie.java b/Mage.Sets/src/mage/cards/c/CagedZombie.java index c3fffd077e2..9bd5d9875a3 100644 --- a/Mage.Sets/src/mage/cards/c/CagedZombie.java +++ b/Mage.Sets/src/mage/cards/c/CagedZombie.java @@ -7,6 +7,7 @@ import mage.abilities.condition.common.MorbidCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -34,7 +35,7 @@ public final class CagedZombie extends CardImpl { new ManaCostsImpl("{1}{B}"), MorbidCondition.instance ); ability.addCost(new TapSourceCost()); - this.addAbility(ability, new MorbidWatcher()); + this.addAbility(ability.addHint(MorbidHint.instance), new MorbidWatcher()); } private CagedZombie(final CagedZombie card) { diff --git a/Mage.Sets/src/mage/cards/c/CaravanVigil.java b/Mage.Sets/src/mage/cards/c/CaravanVigil.java index fd702a6e0ee..9806506813f 100644 --- a/Mage.Sets/src/mage/cards/c/CaravanVigil.java +++ b/Mage.Sets/src/mage/cards/c/CaravanVigil.java @@ -6,6 +6,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; @@ -27,6 +28,7 @@ public final class CaravanVigil extends CardImpl { // Search your library for a basic land card, reveal it, put it into your hand, then shuffle your library. // Morbid — You may put that card onto the battlefield instead of putting it into your hand if a creature died this turn. this.getSpellAbility().addEffect(new CaravanVigilEffect()); + this.getSpellAbility().addHint(MorbidHint.instance); } private CaravanVigil(final CaravanVigil card) { diff --git a/Mage.Sets/src/mage/cards/d/DeathreapRitual.java b/Mage.Sets/src/mage/cards/d/DeathreapRitual.java index 74e471fdc7c..3fea56bee97 100644 --- a/Mage.Sets/src/mage/cards/d/DeathreapRitual.java +++ b/Mage.Sets/src/mage/cards/d/DeathreapRitual.java @@ -5,6 +5,7 @@ import java.util.UUID; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -22,7 +23,7 @@ public final class DeathreapRitual extends CardImpl { // Morbid — At the beginning of each end step, if a creature died this turn, you may draw a card. this.addAbility(new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), - TargetController.ANY, MorbidCondition.instance, true)); + TargetController.ANY, MorbidCondition.instance, true).addHint(MorbidHint.instance)); } private DeathreapRitual(final DeathreapRitual card) { diff --git a/Mage.Sets/src/mage/cards/e/EmissaryOfTheSleepless.java b/Mage.Sets/src/mage/cards/e/EmissaryOfTheSleepless.java index 9d5e817ab6d..48e73d741f0 100644 --- a/Mage.Sets/src/mage/cards/e/EmissaryOfTheSleepless.java +++ b/Mage.Sets/src/mage/cards/e/EmissaryOfTheSleepless.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -32,7 +33,7 @@ public final class EmissaryOfTheSleepless extends CardImpl { // When Emissary of the Sleepless enters the battlefield, if a creature died this turn, create a 1/1 white Spirit creature token with flying. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new SpiritWhiteToken())); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, "When {this} enters the battlefield, if a creature died this turn, create a 1/1 white Spirit creature token with flying.")); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, "When {this} enters the battlefield, if a creature died this turn, create a 1/1 white Spirit creature token with flying.").addHint(MorbidHint.instance)); } private EmissaryOfTheSleepless(final EmissaryOfTheSleepless card) { diff --git a/Mage.Sets/src/mage/cards/f/FesterhideBoar.java b/Mage.Sets/src/mage/cards/f/FesterhideBoar.java index 916f9a6da4d..1beda4953c7 100644 --- a/Mage.Sets/src/mage/cards/f/FesterhideBoar.java +++ b/Mage.Sets/src/mage/cards/f/FesterhideBoar.java @@ -7,6 +7,7 @@ import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,7 @@ public final class FesterhideBoar extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Morbid — Festerhide Boar enters the battlefield with two +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), - MorbidCondition.instance, ""), "with two +1/+1 counters on it if a creature died this turn")); + MorbidCondition.instance, ""), "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); } private FesterhideBoar(final FesterhideBoar card) { diff --git a/Mage.Sets/src/mage/cards/f/FungalRebirth.java b/Mage.Sets/src/mage/cards/f/FungalRebirth.java index 801d277449a..4c21ac4fab7 100644 --- a/Mage.Sets/src/mage/cards/f/FungalRebirth.java +++ b/Mage.Sets/src/mage/cards/f/FungalRebirth.java @@ -4,6 +4,7 @@ import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,6 +34,7 @@ public final class FungalRebirth extends CardImpl { MorbidCondition.instance, "If a creature died this turn, create two 1/1 green Saproling creature tokens")); getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT)); + getSpellAbility().addHint(MorbidHint.instance); } private FungalRebirth(final FungalRebirth card) { diff --git a/Mage.Sets/src/mage/cards/f/FunnelWebRecluse.java b/Mage.Sets/src/mage/cards/f/FunnelWebRecluse.java index 226041ff3d0..2a4c822f596 100644 --- a/Mage.Sets/src/mage/cards/f/FunnelWebRecluse.java +++ b/Mage.Sets/src/mage/cards/f/FunnelWebRecluse.java @@ -5,6 +5,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,7 +35,7 @@ public final class FunnelWebRecluse extends CardImpl { MorbidCondition.instance, "Morbid — When {this} enters the battlefield, " + "if a creature died this turn, investigate. (Create a colorless Clue artifact token " + "with \"{2}, Sacrifice this artifact: Draw a card.\")" - )); + ).addHint(MorbidHint.instance)); } private FunnelWebRecluse(final FunnelWebRecluse card) { diff --git a/Mage.Sets/src/mage/cards/g/GravetillerWurm.java b/Mage.Sets/src/mage/cards/g/GravetillerWurm.java index 3c99e86d750..6b2684c788b 100644 --- a/Mage.Sets/src/mage/cards/g/GravetillerWurm.java +++ b/Mage.Sets/src/mage/cards/g/GravetillerWurm.java @@ -7,6 +7,7 @@ import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,7 @@ public final class GravetillerWurm extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Morbid — Gravetiller Wurm enters the battlefield with four +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)), - MorbidCondition.instance, ""), "with four +1/+1 counters on it if a creature died this turn")); + MorbidCondition.instance, ""), "with four +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); } private GravetillerWurm(final GravetillerWurm card) { diff --git a/Mage.Sets/src/mage/cards/g/GrimWanderer.java b/Mage.Sets/src/mage/cards/g/GrimWanderer.java index f579914e163..490d0ce9bd6 100644 --- a/Mage.Sets/src/mage/cards/g/GrimWanderer.java +++ b/Mage.Sets/src/mage/cards/g/GrimWanderer.java @@ -4,6 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.common.CastOnlyIfConditionIsTrueAbility; import mage.abilities.condition.common.MorbidCondition; +import mage.abilities.hint.common.MorbidHint; import mage.constants.SubType; import mage.abilities.keyword.FlashAbility; import mage.cards.CardImpl; @@ -28,7 +29,7 @@ public final class GrimWanderer extends CardImpl { this.addAbility(FlashAbility.getInstance()); // Tragic Backstory — Cast this spell only if a creature died this turn. - this.addAbility(new CastOnlyIfConditionIsTrueAbility(MorbidCondition.instance).withFlavorWord("Tragic Backstory")); + this.addAbility(new CastOnlyIfConditionIsTrueAbility(MorbidCondition.instance).withFlavorWord("Tragic Backstory").addHint(MorbidHint.instance)); } private GrimWanderer(final GrimWanderer card) { diff --git a/Mage.Sets/src/mage/cards/g/GruesomeDiscovery.java b/Mage.Sets/src/mage/cards/g/GruesomeDiscovery.java index 7c3a9bde9d6..87b6f5fcfb8 100644 --- a/Mage.Sets/src/mage/cards/g/GruesomeDiscovery.java +++ b/Mage.Sets/src/mage/cards/g/GruesomeDiscovery.java @@ -5,6 +5,7 @@ import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.CardsImpl; @@ -36,6 +37,7 @@ public final class GruesomeDiscovery extends CardImpl { "you choose two cards from it, then that player discards those cards" )); this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().addHint(MorbidHint.instance); } private GruesomeDiscovery(final GruesomeDiscovery card) { diff --git a/Mage.Sets/src/mage/cards/h/HollowhengeScavenger.java b/Mage.Sets/src/mage/cards/h/HollowhengeScavenger.java index d525206fd7d..a65ab1f9361 100644 --- a/Mage.Sets/src/mage/cards/h/HollowhengeScavenger.java +++ b/Mage.Sets/src/mage/cards/h/HollowhengeScavenger.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -30,7 +31,7 @@ public final class HollowhengeScavenger extends CardImpl { // Morbid — When Hollowhenge Scavenger enters the battlefield, if a creature died this turn, you gain 5 life. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5)); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText).addHint(MorbidHint.instance)); } private HollowhengeScavenger(final HollowhengeScavenger card) { diff --git a/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java b/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java index fcc3a39a895..b549c4071fa 100644 --- a/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java +++ b/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java @@ -5,6 +5,7 @@ import java.util.UUID; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -30,6 +31,7 @@ public final class HungerOfTheHowlpack extends CardImpl { MorbidCondition.instance, "Put a +1/+1 counter on target creature. Morbid — Put three +1/+1 counters on that creature instead if a creature died this turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(MorbidHint.instance); } private HungerOfTheHowlpack(final HungerOfTheHowlpack card) { diff --git a/Mage.Sets/src/mage/cards/l/LifeGoesOn.java b/Mage.Sets/src/mage/cards/l/LifeGoesOn.java index 134a50429f3..0d9874f655f 100644 --- a/Mage.Sets/src/mage/cards/l/LifeGoesOn.java +++ b/Mage.Sets/src/mage/cards/l/LifeGoesOn.java @@ -5,6 +5,7 @@ import java.util.UUID; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,6 +24,7 @@ public final class LifeGoesOn extends CardImpl { // You gain 4 life. If a creature died this turn, you gain 8 life instead. getSpellAbility().addWatcher(new MorbidWatcher()); getSpellAbility().addEffect(new ConditionalOneShotEffect(new GainLifeEffect(8), new GainLifeEffect(4), MorbidCondition.instance, "You gain 4 life. If a creature died this turn, you gain 8 life instead")); + this.getSpellAbility().addHint(MorbidHint.instance); } private LifeGoesOn(final LifeGoesOn card) { diff --git a/Mage.Sets/src/mage/cards/l/LilianasDevotee.java b/Mage.Sets/src/mage/cards/l/LilianasDevotee.java index 32a0236aebb..7b37c8b744a 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasDevotee.java +++ b/Mage.Sets/src/mage/cards/l/LilianasDevotee.java @@ -9,6 +9,7 @@ import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -48,7 +49,7 @@ public final class LilianasDevotee extends CardImpl { ), TargetController.YOU, false), MorbidCondition.instance, "At the beginning of your end step, if a creature died this turn, " + "you may pay {1}{B}. If you do, create a 2/2 black Zombie creature token." - ), new MorbidWatcher()); + ).addHint(MorbidHint.instance), new MorbidWatcher()); } private LilianasDevotee(final LilianasDevotee card) { diff --git a/Mage.Sets/src/mage/cards/l/LilianasScrounger.java b/Mage.Sets/src/mage/cards/l/LilianasScrounger.java index e6a762f294d..7175d79bb61 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasScrounger.java +++ b/Mage.Sets/src/mage/cards/l/LilianasScrounger.java @@ -6,6 +6,7 @@ import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -41,7 +42,7 @@ public final class LilianasScrounger extends CardImpl { new LilianasScroungerEffect(), TargetController.ANY, false ), MorbidCondition.instance, "At the beginning of each end step, " + "if a creature died this turn, you may put a loyalty counter on a Liliana planeswalker you control." - ), new MorbidWatcher()); + ).addHint(MorbidHint.instance), new MorbidWatcher()); } private LilianasScrounger(final LilianasScrounger card) { diff --git a/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java b/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java index 2922c55323e..829bcd50994 100644 --- a/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java +++ b/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java @@ -7,6 +7,7 @@ import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CastSourceTriggeredAbility; import mage.abilities.effects.common.CopySourceSpellEffect; import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -38,7 +39,7 @@ public final class MaliciousAffliction extends CardImpl { "if a creature died this turn, you may copy {this} and may choose a new target for the copy" ); ability.setRuleAtTheTop(true); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); // Destroy target nonblack creature. this.getSpellAbility().addEffect(new DestroyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/m/MorkrutBanshee.java b/Mage.Sets/src/mage/cards/m/MorkrutBanshee.java index d03414edf6c..34b0d72ef01 100644 --- a/Mage.Sets/src/mage/cards/m/MorkrutBanshee.java +++ b/Mage.Sets/src/mage/cards/m/MorkrutBanshee.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -34,7 +35,7 @@ public final class MorkrutBanshee extends CardImpl { TriggeredAbility triggeredAbility = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-4, -4, Duration.EndOfTurn)); TriggeredAbility ability = new ConditionalInterveningIfTriggeredAbility(triggeredAbility, MorbidCondition.instance, staticText); ability.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private MorkrutBanshee(final MorkrutBanshee card) { diff --git a/Mage.Sets/src/mage/cards/o/OsaiVultures.java b/Mage.Sets/src/mage/cards/o/OsaiVultures.java index 0a5fee5a147..598965b596a 100644 --- a/Mage.Sets/src/mage/cards/o/OsaiVultures.java +++ b/Mage.Sets/src/mage/cards/o/OsaiVultures.java @@ -10,6 +10,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,7 +38,7 @@ public final class OsaiVultures extends CardImpl { // At the beginning of each end step, if a creature died this turn, put a carrion counter on Osai Vultures. this.addAbility(new ConditionalInterveningIfTriggeredAbility(new BeginningOfEndStepTriggeredAbility( new AddCountersSourceEffect(CounterType.CARRION.createInstance()), TargetController.ANY, false), MorbidCondition.instance, - "At the beginning of each end step, if a creature died this turn, put a carrion counter on {this}.")); + "At the beginning of each end step, if a creature died this turn, put a carrion counter on {this}.").addHint(MorbidHint.instance)); // Remove two carrion counters from Osai Vultures: Osai Vultures gets +1/+1 until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 1, Duration.EndOfTurn), new RemoveCountersSourceCost(CounterType.CARRION.createInstance(2)))); diff --git a/Mage.Sets/src/mage/cards/p/PredatorsHowl.java b/Mage.Sets/src/mage/cards/p/PredatorsHowl.java index 666c85e8724..d7bd4084e32 100644 --- a/Mage.Sets/src/mage/cards/p/PredatorsHowl.java +++ b/Mage.Sets/src/mage/cards/p/PredatorsHowl.java @@ -6,6 +6,7 @@ import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -28,6 +29,7 @@ public final class PredatorsHowl extends CardImpl { MorbidCondition.instance, "Create a 2/2 green Wolf creature token.

Morbid — Create three 2/2 green Wolf creature tokens instead if a creature died this turn."); this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addHint(MorbidHint.instance); } private PredatorsHowl(final PredatorsHowl card) { diff --git a/Mage.Sets/src/mage/cards/s/SabertoothMauler.java b/Mage.Sets/src/mage/cards/s/SabertoothMauler.java index 4ee840e7214..abd3aa20b38 100644 --- a/Mage.Sets/src/mage/cards/s/SabertoothMauler.java +++ b/Mage.Sets/src/mage/cards/s/SabertoothMauler.java @@ -7,6 +7,7 @@ import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -37,7 +38,7 @@ public final class SabertoothMauler extends CardImpl { "if a creature died this turn, put a +1/+1 counter on {this} and untap it." ); ability.addEffect(new UntapSourceEffect()); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private SabertoothMauler(final SabertoothMauler card) { diff --git a/Mage.Sets/src/mage/cards/s/SkirsdagHighPriest.java b/Mage.Sets/src/mage/cards/s/SkirsdagHighPriest.java index cc7caeeff9e..b8df837ea25 100644 --- a/Mage.Sets/src/mage/cards/s/SkirsdagHighPriest.java +++ b/Mage.Sets/src/mage/cards/s/SkirsdagHighPriest.java @@ -9,6 +9,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; @@ -45,7 +46,7 @@ public final class SkirsdagHighPriest extends CardImpl { new TapSourceCost(), MorbidCondition.instance); ability.addCost(new TapTargetCost(new TargetControlledCreaturePermanent(2, 2, filter, false))); ability.setAbilityWord(AbilityWord.MORBID); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private SkirsdagHighPriest(final SkirsdagHighPriest card) { diff --git a/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java b/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java index d42a2fbbfdc..c332e5bbd54 100644 --- a/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java +++ b/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java @@ -7,6 +7,7 @@ import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,7 +32,7 @@ public final class SomberwaldSpider extends CardImpl { // Morbid — Somberwald Spider enters the battlefield with two +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility( new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), MorbidCondition.instance, ""), - "with two +1/+1 counters on it if a creature died this turn")); + "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); } private SomberwaldSpider(final SomberwaldSpider card) { diff --git a/Mage.Sets/src/mage/cards/t/TitanHunter.java b/Mage.Sets/src/mage/cards/t/TitanHunter.java index 29be00c4440..e121bae95e6 100644 --- a/Mage.Sets/src/mage/cards/t/TitanHunter.java +++ b/Mage.Sets/src/mage/cards/t/TitanHunter.java @@ -12,6 +12,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -45,7 +46,7 @@ public final class TitanHunter extends CardImpl { new TitanHunterEffect(), TargetController.EACH_PLAYER, false ), condition, "At the beginning of each player's end step, " + "if no creatures died this turn, {this} deals 4 damage to that player." - )); + ).addHint(MorbidHint.instance)); // {1}{B}, Sacrifice a creature: You gain 4 life. Ability ability = new SimpleActivatedAbility(new GainLifeEffect(4), new ManaCostsImpl("{1}{B}")); diff --git a/Mage.Sets/src/mage/cards/t/TragicSlip.java b/Mage.Sets/src/mage/cards/t/TragicSlip.java index 8916202a87a..01f57f3e826 100644 --- a/Mage.Sets/src/mage/cards/t/TragicSlip.java +++ b/Mage.Sets/src/mage/cards/t/TragicSlip.java @@ -6,6 +6,7 @@ import mage.abilities.condition.LockedInCondition; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -30,6 +31,7 @@ public final class TragicSlip extends CardImpl { new LockedInCondition(MorbidCondition.instance), "Target creature gets -1/-1 until end of turn.
Morbid — That creature gets -13/-13 until end of turn instead if a creature died this turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(MorbidHint.instance); } private TragicSlip(final TragicSlip card) { diff --git a/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java b/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java index d67fef44337..6011bd95433 100644 --- a/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java +++ b/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java @@ -5,6 +5,7 @@ import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,7 +34,7 @@ public final class TwinbladeAssassins extends CardImpl { new DrawCardSourceControllerEffect(1), TargetController.YOU, false ), MorbidCondition.instance, "At the beginning of your end step, " + "if a creature died this turn, draw a card." - ), new MorbidWatcher()); + ).addHint(MorbidHint.instance), new MorbidWatcher()); } private TwinbladeAssassins(final TwinbladeAssassins card) { diff --git a/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java b/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java index 57665ac2aa0..772b7a3585f 100644 --- a/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java +++ b/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,7 +34,7 @@ public final class UlvenwaldBear extends CardImpl { Ability ability = new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2), Outcome.BoostCreature)), MorbidCondition.instance, "When {this} enters the battlefield, if a creature died this turn, put two +1/+1 counters on target creature."); ability.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private UlvenwaldBear(final UlvenwaldBear card) { diff --git a/Mage.Sets/src/mage/cards/v/VengefulDevil.java b/Mage.Sets/src/mage/cards/v/VengefulDevil.java index 7a339d5d69f..5502c231501 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulDevil.java +++ b/Mage.Sets/src/mage/cards/v/VengefulDevil.java @@ -6,6 +6,7 @@ import mage.abilities.common.ActivateIfConditionActivatedAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,7 +40,7 @@ public final class VengefulDevil extends CardImpl { ); ability.addTarget(new TargetAnyTarget()); ability.setAbilityWord(AbilityWord.MORBID); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private VengefulDevil(final VengefulDevil card) { diff --git a/Mage.Sets/src/mage/cards/w/Wakedancer.java b/Mage.Sets/src/mage/cards/w/Wakedancer.java index 781ba37ea93..fb55d8120b4 100644 --- a/Mage.Sets/src/mage/cards/w/Wakedancer.java +++ b/Mage.Sets/src/mage/cards/w/Wakedancer.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,7 +34,7 @@ public final class Wakedancer extends CardImpl { // Morbid — When Wakedancer enters the battlefield, if a creature died this turn, create a 2/2 black Zombie creature token. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ZombieToken())); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText).addHint(MorbidHint.instance)); } private Wakedancer(final Wakedancer card) { diff --git a/Mage.Sets/src/mage/cards/w/WoodlandSleuth.java b/Mage.Sets/src/mage/cards/w/WoodlandSleuth.java index f0bbd1665f0..c11d2d11e9c 100644 --- a/Mage.Sets/src/mage/cards/w/WoodlandSleuth.java +++ b/Mage.Sets/src/mage/cards/w/WoodlandSleuth.java @@ -8,6 +8,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.MorbidHint; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,7 +40,7 @@ public final class WoodlandSleuth extends CardImpl { // Morbid — When Woodland Sleuth enters the battlefield, if a creature died this turn, return a creature card at random from your graveyard to your hand. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new WoodlandSleuthEffect()); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, MorbidCondition.instance, staticText).addHint(MorbidHint.instance)); } private WoodlandSleuth(final WoodlandSleuth card) { From 58dac0a890243f535b18a64d858b109a7e02073a Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 09:03:57 -0400 Subject: [PATCH 22/48] removed unnecessary morbid watchers --- Mage.Sets/src/mage/cards/b/BonePicker.java | 3 +- .../src/mage/cards/b/BrimstoneVolley.java | 2 - Mage.Sets/src/mage/cards/c/CagedZombie.java | 3 +- .../src/mage/cards/d/DeathPriestOfMyrkul.java | 3 +- Mage.Sets/src/mage/cards/f/FungalRebirth.java | 2 - Mage.Sets/src/mage/cards/l/LifeGoesOn.java | 2 - .../src/mage/cards/l/LilianasDevotee.java | 3 +- .../src/mage/cards/l/LilianasScrounger.java | 3 +- Mage.Sets/src/mage/cards/p/PurpleWorm.java | 3 +- .../src/mage/cards/r/ReaperFromTheAbyss.java | 67 +++++-------------- .../mage/cards/s/ShessraDeathsWhisper.java | 3 +- .../src/mage/cards/s/SkeletalSwarming.java | 3 +- .../src/mage/cards/t/TwinbladeAssassins.java | 3 +- Mage.Sets/src/mage/cards/w/WarlockClass.java | 3 +- Mage.Sets/src/mage/cards/z/ZombieOgre.java | 3 +- 15 files changed, 27 insertions(+), 79 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BonePicker.java b/Mage.Sets/src/mage/cards/b/BonePicker.java index cd488386634..7101ae17f7e 100644 --- a/Mage.Sets/src/mage/cards/b/BonePicker.java +++ b/Mage.Sets/src/mage/cards/b/BonePicker.java @@ -20,7 +20,6 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.util.CardUtil; -import mage.watchers.common.MorbidWatcher; /** * @@ -36,7 +35,7 @@ public final class BonePicker extends CardImpl { this.toughness = new MageInt(2); // Bone Picker costs {3} less to cast if a creature died this turn. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new BonePickerAdjustingCostsEffect()).addHint(MorbidHint.instance), new MorbidWatcher()); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new BonePickerAdjustingCostsEffect()).addHint(MorbidHint.instance)); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java b/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java index 4d7e74981a7..c67f8245902 100644 --- a/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java +++ b/Mage.Sets/src/mage/cards/b/BrimstoneVolley.java @@ -8,7 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetAnyTarget; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; /** @@ -28,7 +27,6 @@ public final class BrimstoneVolley extends CardImpl { )); this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addHint(MorbidHint.instance); - this.getSpellAbility().addWatcher(new MorbidWatcher()); } private BrimstoneVolley(final BrimstoneVolley card) { diff --git a/Mage.Sets/src/mage/cards/c/CagedZombie.java b/Mage.Sets/src/mage/cards/c/CagedZombie.java index 9bd5d9875a3..675a0a4c196 100644 --- a/Mage.Sets/src/mage/cards/c/CagedZombie.java +++ b/Mage.Sets/src/mage/cards/c/CagedZombie.java @@ -13,7 +13,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -35,7 +34,7 @@ public final class CagedZombie extends CardImpl { new ManaCostsImpl("{1}{B}"), MorbidCondition.instance ); ability.addCost(new TapSourceCost()); - this.addAbility(ability.addHint(MorbidHint.instance), new MorbidWatcher()); + this.addAbility(ability.addHint(MorbidHint.instance)); } private CagedZombie(final CagedZombie card) { diff --git a/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java b/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java index fcbdae45f3f..51a906c2b8b 100644 --- a/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java +++ b/Mage.Sets/src/mage/cards/d/DeathPriestOfMyrkul.java @@ -15,7 +15,6 @@ import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.permanent.token.SkeletonToken; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -53,7 +52,7 @@ public final class DeathPriestOfMyrkul extends CardImpl { Zone.BATTLEFIELD, new DoIfCostPaid(new CreateTokenEffect(new SkeletonToken()), new GenericManaCost(1)), TargetController.YOU, MorbidCondition.instance, false - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private DeathPriestOfMyrkul(final DeathPriestOfMyrkul card) { diff --git a/Mage.Sets/src/mage/cards/f/FungalRebirth.java b/Mage.Sets/src/mage/cards/f/FungalRebirth.java index 4c21ac4fab7..3fc2a1b1aa4 100644 --- a/Mage.Sets/src/mage/cards/f/FungalRebirth.java +++ b/Mage.Sets/src/mage/cards/f/FungalRebirth.java @@ -11,7 +11,6 @@ import mage.constants.CardType; import mage.filter.StaticFilters; import mage.game.permanent.token.SaprolingToken; import mage.target.common.TargetCardInYourGraveyard; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -28,7 +27,6 @@ public final class FungalRebirth extends CardImpl { getSpellAbility().addEffect( new ReturnFromGraveyardToHandTargetEffect().setText("Return target permanent card from your graveyard to your hand") ); - getSpellAbility().addWatcher(new MorbidWatcher()); getSpellAbility().addEffect(new ConditionalOneShotEffect( new CreateTokenEffect(new SaprolingToken(), 2), MorbidCondition.instance, diff --git a/Mage.Sets/src/mage/cards/l/LifeGoesOn.java b/Mage.Sets/src/mage/cards/l/LifeGoesOn.java index 0d9874f655f..316b92ad522 100644 --- a/Mage.Sets/src/mage/cards/l/LifeGoesOn.java +++ b/Mage.Sets/src/mage/cards/l/LifeGoesOn.java @@ -9,7 +9,6 @@ import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.watchers.common.MorbidWatcher; /** * @@ -22,7 +21,6 @@ public final class LifeGoesOn extends CardImpl { // You gain 4 life. If a creature died this turn, you gain 8 life instead. - getSpellAbility().addWatcher(new MorbidWatcher()); getSpellAbility().addEffect(new ConditionalOneShotEffect(new GainLifeEffect(8), new GainLifeEffect(4), MorbidCondition.instance, "You gain 4 life. If a creature died this turn, you gain 8 life instead")); this.getSpellAbility().addHint(MorbidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/l/LilianasDevotee.java b/Mage.Sets/src/mage/cards/l/LilianasDevotee.java index 7b37c8b744a..5ec22413107 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasDevotee.java +++ b/Mage.Sets/src/mage/cards/l/LilianasDevotee.java @@ -18,7 +18,6 @@ import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.common.FilterCreaturePermanent; import mage.game.permanent.token.ZombieToken; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -49,7 +48,7 @@ public final class LilianasDevotee extends CardImpl { ), TargetController.YOU, false), MorbidCondition.instance, "At the beginning of your end step, if a creature died this turn, " + "you may pay {1}{B}. If you do, create a 2/2 black Zombie creature token." - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private LilianasDevotee(final LilianasDevotee card) { diff --git a/Mage.Sets/src/mage/cards/l/LilianasScrounger.java b/Mage.Sets/src/mage/cards/l/LilianasScrounger.java index 7175d79bb61..bea115fe3ad 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasScrounger.java +++ b/Mage.Sets/src/mage/cards/l/LilianasScrounger.java @@ -20,7 +20,6 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -42,7 +41,7 @@ public final class LilianasScrounger extends CardImpl { new LilianasScroungerEffect(), TargetController.ANY, false ), MorbidCondition.instance, "At the beginning of each end step, " + "if a creature died this turn, you may put a loyalty counter on a Liliana planeswalker you control." - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private LilianasScrounger(final LilianasScrounger card) { diff --git a/Mage.Sets/src/mage/cards/p/PurpleWorm.java b/Mage.Sets/src/mage/cards/p/PurpleWorm.java index 441072a9c92..78e3b026b5d 100644 --- a/Mage.Sets/src/mage/cards/p/PurpleWorm.java +++ b/Mage.Sets/src/mage/cards/p/PurpleWorm.java @@ -13,7 +13,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -34,7 +33,7 @@ public final class PurpleWorm extends CardImpl { Zone.ALL, new SpellCostReductionSourceEffect(2, MorbidCondition.instance) ); ability.setRuleAtTheTop(true); - this.addAbility(ability.addHint(MorbidHint.instance), new MorbidWatcher()); + this.addAbility(ability.addHint(MorbidHint.instance)); // Ward {2} this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); diff --git a/Mage.Sets/src/mage/cards/r/ReaperFromTheAbyss.java b/Mage.Sets/src/mage/cards/r/ReaperFromTheAbyss.java index 66c46bee952..adefb7338a2 100644 --- a/Mage.Sets/src/mage/cards/r/ReaperFromTheAbyss.java +++ b/Mage.Sets/src/mage/cards/r/ReaperFromTheAbyss.java @@ -1,50 +1,47 @@ - - package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.common.MorbidCondition; import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; -import mage.watchers.common.MorbidWatcher; +import mage.target.TargetPermanent; + +import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public final class ReaperFromTheAbyss extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Demon creature"); + private static final FilterPermanent filter = new FilterCreaturePermanent("non-Demon creature"); static { filter.add(Predicates.not(SubType.DEMON.getPredicate())); } public ReaperFromTheAbyss(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}{B}"); this.subtype.add(SubType.DEMON); this.power = new MageInt(6); this.toughness = new MageInt(6); this.addAbility(FlyingAbility.getInstance()); - Ability ability = new ReaperFromTheAbyssAbility(); - ability.addTarget(new TargetCreaturePermanent(filter)); - this.addAbility(ability); + Ability ability = new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new DestroyTargetEffect(), + TargetController.ANY, MorbidCondition.instance, false + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.MORBID).addHint(MorbidHint.instance)); } private ReaperFromTheAbyss(final ReaperFromTheAbyss card) { @@ -57,35 +54,3 @@ public final class ReaperFromTheAbyss extends CardImpl { } } - -class ReaperFromTheAbyssAbility extends TriggeredAbilityImpl { - - public ReaperFromTheAbyssAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(), false); - } - - public ReaperFromTheAbyssAbility(final ReaperFromTheAbyssAbility ability) { - super(ability); - } - - @Override - public ReaperFromTheAbyssAbility copy() { - return new ReaperFromTheAbyssAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Watcher watcher = game.getState().getWatcher(MorbidWatcher.class); - return watcher != null && watcher.conditionMet(); - } - - @Override - public String getRule() { - return "Morbid — At the beginning of each end step, if a creature died this turn, destroy target non-demon creature."; - } -} diff --git a/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java b/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java index e7d9f9724b8..28c22acdafc 100644 --- a/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java +++ b/Mage.Sets/src/mage/cards/s/ShessraDeathsWhisper.java @@ -14,7 +14,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -42,7 +41,7 @@ public final class ShessraDeathsWhisper extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( Zone.BATTLEFIELD, new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new PayLifeCost(2)), TargetController.YOU, MorbidCondition.instance, false - ).addHint(MorbidHint.instance).withFlavorWord("Whispers of the Grave"), new MorbidWatcher()); + ).addHint(MorbidHint.instance).withFlavorWord("Whispers of the Grave")); } private ShessraDeathsWhisper(final ShessraDeathsWhisper card) { diff --git a/Mage.Sets/src/mage/cards/s/SkeletalSwarming.java b/Mage.Sets/src/mage/cards/s/SkeletalSwarming.java index 1a7ff137ac9..b091a84e19e 100644 --- a/Mage.Sets/src/mage/cards/s/SkeletalSwarming.java +++ b/Mage.Sets/src/mage/cards/s/SkeletalSwarming.java @@ -25,7 +25,6 @@ import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.game.permanent.token.SkeletonToken; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -59,7 +58,7 @@ public final class SkeletalSwarming extends CardImpl { MorbidCondition.instance, "create a tapped 1/1 black Skeleton creature token. " + "If a creature died this turn, create two of those tokens instead" ), TargetController.YOU, false - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private SkeletalSwarming(final SkeletalSwarming card) { diff --git a/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java b/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java index 6011bd95433..617b8c13c6f 100644 --- a/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java +++ b/Mage.Sets/src/mage/cards/t/TwinbladeAssassins.java @@ -11,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -34,7 +33,7 @@ public final class TwinbladeAssassins extends CardImpl { new DrawCardSourceControllerEffect(1), TargetController.YOU, false ), MorbidCondition.instance, "At the beginning of your end step, " + "if a creature died this turn, draw a card." - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private TwinbladeAssassins(final TwinbladeAssassins card) { diff --git a/Mage.Sets/src/mage/cards/w/WarlockClass.java b/Mage.Sets/src/mage/cards/w/WarlockClass.java index d094f88a3a7..8b325b0cf87 100644 --- a/Mage.Sets/src/mage/cards/w/WarlockClass.java +++ b/Mage.Sets/src/mage/cards/w/WarlockClass.java @@ -20,7 +20,6 @@ import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; -import mage.watchers.common.MorbidWatcher; import mage.watchers.common.PlayerLostLifeWatcher; import java.util.UUID; @@ -44,7 +43,7 @@ public final class WarlockClass extends CardImpl { new LoseLifeOpponentsEffect(1), TargetController.YOU, false ), MorbidCondition.instance, "At the beginning of your end step, " + "if a creature died this turn, each opponent loses 1 life." - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); // {1}{B}: Level 2 this.addAbility(new ClassLevelAbility(2, "{1}{B}")); diff --git a/Mage.Sets/src/mage/cards/z/ZombieOgre.java b/Mage.Sets/src/mage/cards/z/ZombieOgre.java index 47d2efc4142..43f04c185f0 100644 --- a/Mage.Sets/src/mage/cards/z/ZombieOgre.java +++ b/Mage.Sets/src/mage/cards/z/ZombieOgre.java @@ -11,7 +11,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; import mage.constants.Zone; -import mage.watchers.common.MorbidWatcher; import java.util.UUID; @@ -32,7 +31,7 @@ public final class ZombieOgre extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( Zone.BATTLEFIELD, new VentureIntoTheDungeonEffect(), TargetController.YOU, MorbidCondition.instance, false - ).addHint(MorbidHint.instance), new MorbidWatcher()); + ).addHint(MorbidHint.instance)); } private ZombieOgre(final ZombieOgre card) { From f57693392ccb445a7437c93c67334d568eb8746d Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 11:17:17 -0400 Subject: [PATCH 23/48] various ability word text fixes --- Mage.Sets/src/mage/cards/a/AdmiralsOrder.java | 3 +- .../src/mage/cards/a/AidFromTheCowl.java | 2 +- Mage.Sets/src/mage/cards/a/AkoumFirebird.java | 2 +- .../src/mage/cards/a/AngelicArbiter.java | 2 +- Mage.Sets/src/mage/cards/a/ArrowStorm.java | 3 +- .../src/mage/cards/a/AuriokSunchaser.java | 31 ++++++++----------- .../src/mage/cards/b/BalothWoodcrasher.java | 4 +-- .../mage/cards/b/BellowingSaddlebrute.java | 2 +- Mage.Sets/src/mage/cards/b/BreakOfDay.java | 2 +- .../src/mage/cards/b/BrilliantSpectrum.java | 2 +- .../src/mage/cards/c/CacklingFlames.java | 2 +- .../src/mage/cards/c/CarapaceForger.java | 19 ++++++------ Mage.Sets/src/mage/cards/c/ChromeMox.java | 3 +- Mage.Sets/src/mage/cards/c/ChromeSteed.java | 20 ++++++------ Mage.Sets/src/mage/cards/c/CloneShell.java | 7 ++--- .../src/mage/cards/c/CodeOfConstraint.java | 2 +- .../src/mage/cards/c/ConcussiveBolt.java | 3 +- Mage.Sets/src/mage/cards/c/CropSigil.java | 2 +- Mage.Sets/src/mage/cards/d/DarkDabbling.java | 2 +- .../src/mage/cards/d/DeathreapRitual.java | 3 +- .../src/mage/cards/d/DispenseJustice.java | 1 - .../src/mage/cards/e/EtchedChampion.java | 2 +- .../src/mage/cards/e/ExclusionRitual.java | 7 ++--- .../src/mage/cards/e/ExertInfluence.java | 2 +- Mage.Sets/src/mage/cards/e/EzurisBrigade.java | 4 +-- .../src/mage/cards/f/FesterhideBoar.java | 3 +- Mage.Sets/src/mage/cards/g/GalvanicBlast.java | 1 - Mage.Sets/src/mage/cards/g/GhalmasWarden.java | 2 +- .../src/mage/cards/g/GravetillerWurm.java | 3 +- .../src/mage/cards/g/GreenwheelLiberator.java | 2 +- .../src/mage/cards/h/HeartlessPillage.java | 3 +- .../src/mage/cards/h/HiddenStockpile.java | 2 +- .../src/mage/cards/h/HowlOfTheHorde.java | 3 +- .../src/mage/cards/h/HungerOfTheHowlpack.java | 2 +- .../mage/cards/i/IndomitableArchangel.java | 2 +- .../src/mage/cards/i/InexorableBlob.java | 4 +-- Mage.Sets/src/mage/cards/k/KnowledgePool.java | 3 +- .../src/mage/cards/l/LifecraftCavalry.java | 2 +- .../src/mage/cards/m/MalakirSoothsayer.java | 2 +- .../src/mage/cards/m/MaliciousAffliction.java | 2 +- .../src/mage/cards/m/MightBeyondReason.java | 2 +- Mage.Sets/src/mage/cards/m/MimicVat.java | 3 +- Mage.Sets/src/mage/cards/m/MirranMettle.java | 3 +- Mage.Sets/src/mage/cards/m/MoltenPsyche.java | 1 - Mage.Sets/src/mage/cards/m/MyrWelder.java | 10 ++---- .../src/mage/cards/o/ObNixilisTheFallen.java | 2 +- Mage.Sets/src/mage/cards/p/PainfulTruths.java | 2 +- .../src/mage/cards/p/PanopticMirror.java | 2 +- .../src/mage/cards/p/PhyrexianIngester.java | 10 ++---- Mage.Sets/src/mage/cards/p/ProteanRaider.java | 2 +- .../src/mage/cards/r/RazorfieldRhino.java | 2 +- .../src/mage/cards/r/RetreatToHagra.java | 2 +- Mage.Sets/src/mage/cards/r/RustedRelic.java | 2 +- .../src/mage/cards/s/SemblanceAnvil.java | 2 +- Mage.Sets/src/mage/cards/s/ShoalSerpent.java | 2 +- .../src/mage/cards/s/SiegehornCeratops.java | 2 +- Mage.Sets/src/mage/cards/s/SilverSeraph.java | 2 +- .../mage/cards/s/SlaughterhouseBouncer.java | 3 +- .../src/mage/cards/s/SnapsailGlider.java | 2 +- .../src/mage/cards/s/SomberwaldSpider.java | 3 +- .../src/mage/cards/s/SpiralingDuelist.java | 2 +- Mage.Sets/src/mage/cards/s/SpireSerpent.java | 2 +- Mage.Sets/src/mage/cards/s/StrataScythe.java | 7 ++--- .../src/mage/cards/s/SummaryJudgment.java | 2 +- .../src/mage/cards/s/SunCrownedHunters.java | 2 +- .../src/mage/cards/s/SunspringExpedition.java | 23 ++++++-------- Mage.Sets/src/mage/cards/t/TribalFlames.java | 2 ++ Mage.Sets/src/mage/cards/u/UlvenwaldBear.java | 3 +- .../mage/cards/u/UnbreakableFormation.java | 2 +- Mage.Sets/src/mage/cards/u/UnholyHunger.java | 2 +- .../src/mage/cards/w/WarNameAspirant.java | 2 +- Mage.Sets/src/mage/cards/z/ZendikarsRoil.java | 12 +++---- .../abilityword/ConstellationAbility.java | 2 +- .../abilities/abilityword/StriveAbility.java | 3 +- .../abilities/keyword/IntimidateAbility.java | 2 +- .../permanent/token/EldraziHorrorToken.java | 2 +- .../token/GrovetenderDruidsPlantToken.java | 2 +- .../decks/importer/samples/testdeck.json | 2 +- 78 files changed, 132 insertions(+), 169 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AdmiralsOrder.java b/Mage.Sets/src/mage/cards/a/AdmiralsOrder.java index b0331a3e626..225eea9cbb0 100644 --- a/Mage.Sets/src/mage/cards/a/AdmiralsOrder.java +++ b/Mage.Sets/src/mage/cards/a/AdmiralsOrder.java @@ -24,12 +24,11 @@ public final class AdmiralsOrder extends CardImpl { // Raid - If you attacked with a creature this turn, you may pay {U} rather than pay this spell's mana cost. this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{U}"), RaidCondition.instance, - "

Raid — If you attacked with a creature this turn, you may pay {U} rather than pay this spell's mana cost"), + "
Raid — If you attacked this turn, you may pay {U} rather than pay this spell's mana cost"), new PlayerAttackedWatcher()); // Counter target spell. this.getSpellAbility().addEffect(new CounterTargetEffect()); this.getSpellAbility().addTarget(new TargetSpell()); - this.getSpellAbility().setAbilityWord(AbilityWord.RAID); this.getSpellAbility().addHint(RaidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java b/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java index d22ba61bc2b..adfcd640ca9 100644 --- a/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java +++ b/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java @@ -25,7 +25,7 @@ import mage.watchers.common.RevoltWatcher; public final class AidFromTheCowl extends CardImpl { private static final String ruleText = "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, " - + "you may reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, put it on the bottom of your library."; + + "reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, put it on the bottom of your library."; public AidFromTheCowl(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/a/AkoumFirebird.java b/Mage.Sets/src/mage/cards/a/AkoumFirebird.java index f233a9f061b..c64e50ab76b 100644 --- a/Mage.Sets/src/mage/cards/a/AkoumFirebird.java +++ b/Mage.Sets/src/mage/cards/a/AkoumFirebird.java @@ -44,7 +44,7 @@ public final class AkoumFirebird extends CardImpl { // Landfall-Whenever a land enters the battlefield under your control, you may pay {4}{R}{R}. // If you do, return Akoum Firebird from your graveyard to the battlefield. this.addAbility(new AkoumFirebirdLandfallAbility(new DoIfCostPaid( - new ReturnSourceFromGraveyardToBattlefieldEffect(), new ManaCostsImpl("{4}{R}{R}")), false)); + new ReturnSourceFromGraveyardToBattlefieldEffect(false, false), new ManaCostsImpl("{4}{R}{R}")), false)); } private AkoumFirebird(final AkoumFirebird card) { diff --git a/Mage.Sets/src/mage/cards/a/AngelicArbiter.java b/Mage.Sets/src/mage/cards/a/AngelicArbiter.java index 81f26520e8b..64b74a7824e 100644 --- a/Mage.Sets/src/mage/cards/a/AngelicArbiter.java +++ b/Mage.Sets/src/mage/cards/a/AngelicArbiter.java @@ -86,7 +86,7 @@ class AngelicArbiterEffect2 extends ContinuousRuleModifyingEffectImpl { public AngelicArbiterEffect2() { super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "Each opponent who attacked with a creature this turn can't cast spells"; + staticText = "Each opponent who attacked this turn can't cast spells"; } public AngelicArbiterEffect2(final AngelicArbiterEffect2 effect) { diff --git a/Mage.Sets/src/mage/cards/a/ArrowStorm.java b/Mage.Sets/src/mage/cards/a/ArrowStorm.java index ad7273389b2..7d68830e13d 100644 --- a/Mage.Sets/src/mage/cards/a/ArrowStorm.java +++ b/Mage.Sets/src/mage/cards/a/ArrowStorm.java @@ -32,9 +32,8 @@ public final class ArrowStorm extends CardImpl { this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new DamageTargetEffect(5, false), RaidCondition.instance, - "

Raid — If you attacked with a creature this turn, instead {this} deals 5 damage to that permanent or player and the damage can't be prevented")); + "

Raid — If you attacked this turn, instead {this} deals 5 damage to that permanent or player and the damage can't be prevented")); this.getSpellAbility().addWatcher(new PlayerAttackedWatcher()); - this.getSpellAbility().setAbilityWord(AbilityWord.RAID); this.getSpellAbility().addHint(RaidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/a/AuriokSunchaser.java b/Mage.Sets/src/mage/cards/a/AuriokSunchaser.java index 1f71e3f3930..1a483827cad 100644 --- a/Mage.Sets/src/mage/cards/a/AuriokSunchaser.java +++ b/Mage.Sets/src/mage/cards/a/AuriokSunchaser.java @@ -1,17 +1,20 @@ package mage.cards.a; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.MetalcraftCondition; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.hint.common.MetalcraftHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import java.util.UUID; @@ -20,9 +23,6 @@ import java.util.UUID; */ public final class AuriokSunchaser extends CardImpl { - protected static String effect1Text = "Metalcraft — As long as you control three or more artifacts, Auriok Sunchaser gets +2/+2"; - protected static String effect2Text = "Metalcraft — As long as you control three or more artifacts, Auriok Sunchaser has flying"; - public AuriokSunchaser(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); this.subtype.add(SubType.HUMAN); @@ -31,19 +31,14 @@ public final class AuriokSunchaser extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - ContinuousEffect effect1 = new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new ConditionalContinuousEffect(effect1, MetalcraftCondition.instance, effect1Text)) - .setAbilityWord(AbilityWord.METALCRAFT) - .addHint(MetalcraftHint.instance) - ); - - ContinuousEffect effect2 = new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new ConditionalContinuousEffect(effect2, MetalcraftCondition.instance, effect2Text)) - .setAbilityWord(AbilityWord.METALCRAFT) - .addHint(MetalcraftHint.instance) - ); + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), + MetalcraftCondition.instance, "as long as you control three or more artifacts, {this} gets +2/+2" + )); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield + ), MetalcraftCondition.instance, "and has flying")); + this.addAbility(ability.setAbilityWord(AbilityWord.METALCRAFT).addHint(MetalcraftHint.instance)); } private AuriokSunchaser(final AuriokSunchaser card) { diff --git a/Mage.Sets/src/mage/cards/b/BalothWoodcrasher.java b/Mage.Sets/src/mage/cards/b/BalothWoodcrasher.java index 6c94ac1628b..1c1879adf8f 100644 --- a/Mage.Sets/src/mage/cards/b/BalothWoodcrasher.java +++ b/Mage.Sets/src/mage/cards/b/BalothWoodcrasher.java @@ -26,8 +26,8 @@ public final class BalothWoodcrasher extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(4); - LandfallAbility ability = new LandfallAbility(new BoostSourceEffect(4, 4, Duration.EndOfTurn), false); - ability.addEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)); + LandfallAbility ability = new LandfallAbility(new BoostSourceEffect(4, 4, Duration.EndOfTurn).setText("{this} gets +4/+4"), false); + ability.addEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.EndOfTurn).setText("and gains trample until end of turn")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BellowingSaddlebrute.java b/Mage.Sets/src/mage/cards/b/BellowingSaddlebrute.java index bdd745139e3..83c8dfd3755 100644 --- a/Mage.Sets/src/mage/cards/b/BellowingSaddlebrute.java +++ b/Mage.Sets/src/mage/cards/b/BellowingSaddlebrute.java @@ -32,7 +32,7 @@ public final class BellowingSaddlebrute extends CardImpl { this.addAbility(new ConditionalInterveningIfTriggeredAbility( new EntersBattlefieldTriggeredAbility(new LoseLifeSourceControllerEffect(4)), new InvertCondition(RaidCondition.instance), - "Raid — When {this} enters the battlefield, you lose 4 life unless you attacked with a creature this turn") + "Raid — When {this} enters the battlefield, you lose 4 life unless you attacked this turn.") .setAbilityWord(AbilityWord.RAID) .addHint(RaidHint.instance), new PlayerAttackedWatcher()); diff --git a/Mage.Sets/src/mage/cards/b/BreakOfDay.java b/Mage.Sets/src/mage/cards/b/BreakOfDay.java index 2bb5a09bd14..02502d868ec 100644 --- a/Mage.Sets/src/mage/cards/b/BreakOfDay.java +++ b/Mage.Sets/src/mage/cards/b/BreakOfDay.java @@ -32,7 +32,7 @@ public final class BreakOfDay extends CardImpl { StaticFilters.FILTER_PERMANENT_CREATURES, false ), new LockedInCondition(FatefulHourCondition.instance), "
Fateful hour — If you have 5 or less life, " + - "those creatures also are indestructible this turn" + "those creatures gain indestructible until end of turn" )); } diff --git a/Mage.Sets/src/mage/cards/b/BrilliantSpectrum.java b/Mage.Sets/src/mage/cards/b/BrilliantSpectrum.java index 352f72ef6d2..f3ffee2a352 100644 --- a/Mage.Sets/src/mage/cards/b/BrilliantSpectrum.java +++ b/Mage.Sets/src/mage/cards/b/BrilliantSpectrum.java @@ -25,7 +25,7 @@ public final class BrilliantSpectrum extends CardImpl { Effect effect = new DrawCardSourceControllerEffect(ColorsOfManaSpentToCastCount.getInstance()); effect.setText("Draw X cards, where X is the number of colors of mana spent to cast this spell"); this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addEffect(new DiscardControllerEffect(2)); + this.getSpellAbility().addEffect(new DiscardControllerEffect(2).concatBy("Then")); } private BrilliantSpectrum(final BrilliantSpectrum card) { diff --git a/Mage.Sets/src/mage/cards/c/CacklingFlames.java b/Mage.Sets/src/mage/cards/c/CacklingFlames.java index 5ef7543cb0e..501f8cb4bdb 100644 --- a/Mage.Sets/src/mage/cards/c/CacklingFlames.java +++ b/Mage.Sets/src/mage/cards/c/CacklingFlames.java @@ -22,7 +22,7 @@ public final class CacklingFlames extends CardImpl { // Hellbent - Cackling Flames deals 5 damage to that creature or player instead if you have no cards in hand. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new DamageTargetEffect(5), new DamageTargetEffect(3), HellbentCondition.instance, - "{this} deals 3 damage to any target
Hellbent " + + "{this} deals 3 damage to any target.
Hellbent " + "— {this} deals 5 damage instead if you have no cards in hand." )); this.getSpellAbility().addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/c/CarapaceForger.java b/Mage.Sets/src/mage/cards/c/CarapaceForger.java index 06c196070ba..40e48b860a0 100644 --- a/Mage.Sets/src/mage/cards/c/CarapaceForger.java +++ b/Mage.Sets/src/mage/cards/c/CarapaceForger.java @@ -8,7 +8,10 @@ import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.hint.common.MetalcraftHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import java.util.UUID; @@ -25,15 +28,11 @@ public final class CarapaceForger extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); - this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, - new ConditionalContinuousEffect( - new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), - MetalcraftCondition.instance, "Metalcraft — {this} gets " + - "+2/+2 as long as you control three or more artifacts" - )) - .setAbilityWord(AbilityWord.METALCRAFT) - .addHint(MetalcraftHint.instance)); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), + MetalcraftCondition.instance, "{this} gets " + + "+2/+2 as long as you control three or more artifacts" + )).setAbilityWord(AbilityWord.METALCRAFT).addHint(MetalcraftHint.instance)); } private CarapaceForger(final CarapaceForger card) { diff --git a/Mage.Sets/src/mage/cards/c/ChromeMox.java b/Mage.Sets/src/mage/cards/c/ChromeMox.java index 52d284f598f..0b7ccdc18c4 100644 --- a/Mage.Sets/src/mage/cards/c/ChromeMox.java +++ b/Mage.Sets/src/mage/cards/c/ChromeMox.java @@ -14,6 +14,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; import mage.choices.ChoiceColor; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -39,7 +40,7 @@ public final class ChromeMox extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{0}"); // Imprint - When Chrome Mox enters the battlefield, you may exile a nonartifact, nonland card from your hand. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ChromeMoxEffect(), true)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ChromeMoxEffect(), true).setAbilityWord(AbilityWord.IMPRINT)); // {T}: Add one mana of any of the exiled card's colors. this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new ChromeMoxManaEffect(), new TapSourceCost())); } diff --git a/Mage.Sets/src/mage/cards/c/ChromeSteed.java b/Mage.Sets/src/mage/cards/c/ChromeSteed.java index e65cd74a3c8..26f35224a09 100644 --- a/Mage.Sets/src/mage/cards/c/ChromeSteed.java +++ b/Mage.Sets/src/mage/cards/c/ChromeSteed.java @@ -8,7 +8,10 @@ import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.hint.common.MetalcraftHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import java.util.UUID; @@ -23,15 +26,11 @@ public final class ChromeSteed extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); - this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, - new ConditionalContinuousEffect( - new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), - MetalcraftCondition.instance, "Metalcraft — {this} gets " + - "+2/+2 as long as you control three or more artifacts" - )) - .setAbilityWord(AbilityWord.METALCRAFT) - .addHint(MetalcraftHint.instance)); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), + MetalcraftCondition.instance, "{this} gets " + + "+2/+2 as long as you control three or more artifacts" + )).setAbilityWord(AbilityWord.METALCRAFT).addHint(MetalcraftHint.instance)); } private ChromeSteed(final ChromeSteed card) { @@ -42,5 +41,4 @@ public final class ChromeSteed extends CardImpl { public ChromeSteed copy() { return new ChromeSteed(this); } - } diff --git a/Mage.Sets/src/mage/cards/c/CloneShell.java b/Mage.Sets/src/mage/cards/c/CloneShell.java index d31fd85d05f..6ce2cfdaa2d 100644 --- a/Mage.Sets/src/mage/cards/c/CloneShell.java +++ b/Mage.Sets/src/mage/cards/c/CloneShell.java @@ -6,10 +6,7 @@ import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.*; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.Permanent; @@ -33,7 +30,7 @@ public final class CloneShell extends CardImpl { this.toughness = new MageInt(2); // Imprint - When Clone Shell enters the battlefield, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library in any order. - this.addAbility(new EntersBattlefieldTriggeredAbility(new CloneShellEffect(), false)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new CloneShellEffect(), false).setAbilityWord(AbilityWord.IMPRINT)); // When Clone Shell dies, turn the exiled card face up. If it's a creature card, put it onto the battlefield under your control. this.addAbility(new DiesSourceTriggeredAbility(new CloneShellDiesEffect())); diff --git a/Mage.Sets/src/mage/cards/c/CodeOfConstraint.java b/Mage.Sets/src/mage/cards/c/CodeOfConstraint.java index f548ade2784..a88990cfc8e 100644 --- a/Mage.Sets/src/mage/cards/c/CodeOfConstraint.java +++ b/Mage.Sets/src/mage/cards/c/CodeOfConstraint.java @@ -29,7 +29,7 @@ public final class CodeOfConstraint extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Draw a card. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); // Addendum — If you cast this spell during your main phase, tap that creature and it doesn't untap during its controller's next untap step. this.getSpellAbility().addEffect(new CodeOfConstraintEffect()); diff --git a/Mage.Sets/src/mage/cards/c/ConcussiveBolt.java b/Mage.Sets/src/mage/cards/c/ConcussiveBolt.java index ba0d5f960e9..8f2ed7780f8 100644 --- a/Mage.Sets/src/mage/cards/c/ConcussiveBolt.java +++ b/Mage.Sets/src/mage/cards/c/ConcussiveBolt.java @@ -34,7 +34,6 @@ public final class ConcussiveBolt extends CardImpl { // Metalcraft — If you control three or more artifacts, creatures that player controls can't block this turn. this.getSpellAbility().addEffect(new ConcussiveBoltEffect()); this.getSpellAbility().addEffect(new ConcussiveBoltRestrictionEffect()); - this.getSpellAbility().setAbilityWord(AbilityWord.METALCRAFT); this.getSpellAbility().addHint(MetalcraftHint.instance); } @@ -52,7 +51,7 @@ class ConcussiveBoltEffect extends OneShotEffect { public ConcussiveBoltEffect() { super(Outcome.Benefit); - this.staticText = "Metalcraft — If you control three or more artifacts, creatures controlled by that player or by that planeswalker's controller can't block this turn."; + this.staticText = "
Metalcraft — If you control three or more artifacts, creatures controlled by that player or by that planeswalker's controller can't block this turn."; } public ConcussiveBoltEffect(final ConcussiveBoltEffect effect) { diff --git a/Mage.Sets/src/mage/cards/c/CropSigil.java b/Mage.Sets/src/mage/cards/c/CropSigil.java index bb9d2af4467..1efa3cb98b6 100644 --- a/Mage.Sets/src/mage/cards/c/CropSigil.java +++ b/Mage.Sets/src/mage/cards/c/CropSigil.java @@ -43,7 +43,7 @@ public final class CropSigil extends CardImpl { Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(true), new ManaCostsImpl<>("{2}{G}"), DeliriumCondition.instance, "Delirium — {2}{G}, Sacrifice {this}: Return up to one target creature card and up to one target land card from your graveyard to your hand. " - + "Activate only if there are four or more card types among cards in your graveyard"); + + "Activate only if there are four or more card types among cards in your graveyard."); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetCardInYourGraveyard(0, 1, filterCreature)); ability.addTarget(new TargetCardInYourGraveyard(0, 1, filterLand)); diff --git a/Mage.Sets/src/mage/cards/d/DarkDabbling.java b/Mage.Sets/src/mage/cards/d/DarkDabbling.java index bab340e52df..8337340f6f4 100644 --- a/Mage.Sets/src/mage/cards/d/DarkDabbling.java +++ b/Mage.Sets/src/mage/cards/d/DarkDabbling.java @@ -48,7 +48,7 @@ class DarkDabblingEffect extends OneShotEffect { public DarkDabblingEffect() { super(Outcome.Benefit); - this.staticText = "Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, also regenerate each other creature you control"; + this.staticText = "
Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, also regenerate each other creature you control"; } public DarkDabblingEffect(final DarkDabblingEffect effect) { diff --git a/Mage.Sets/src/mage/cards/d/DeathreapRitual.java b/Mage.Sets/src/mage/cards/d/DeathreapRitual.java index 3fea56bee97..a4a9f34b931 100644 --- a/Mage.Sets/src/mage/cards/d/DeathreapRitual.java +++ b/Mage.Sets/src/mage/cards/d/DeathreapRitual.java @@ -8,6 +8,7 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.TargetController; import mage.constants.Zone; @@ -23,7 +24,7 @@ public final class DeathreapRitual extends CardImpl { // Morbid — At the beginning of each end step, if a creature died this turn, you may draw a card. this.addAbility(new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), - TargetController.ANY, MorbidCondition.instance, true).addHint(MorbidHint.instance)); + TargetController.ANY, MorbidCondition.instance, true).addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); } private DeathreapRitual(final DeathreapRitual card) { diff --git a/Mage.Sets/src/mage/cards/d/DispenseJustice.java b/Mage.Sets/src/mage/cards/d/DispenseJustice.java index 84b1e3e4960..8a09c91c822 100644 --- a/Mage.Sets/src/mage/cards/d/DispenseJustice.java +++ b/Mage.Sets/src/mage/cards/d/DispenseJustice.java @@ -28,7 +28,6 @@ public final class DispenseJustice extends CardImpl { // Metalcraft — That player sacrifices two attacking creatures instead if you control three or more artifacts. this.getSpellAbility().addEffect(new DispenseJusticeEffect()); this.getSpellAbility().addTarget(new TargetPlayer()); - this.getSpellAbility().setAbilityWord(AbilityWord.METALCRAFT); this.getSpellAbility().addHint(MetalcraftHint.instance); } diff --git a/Mage.Sets/src/mage/cards/e/EtchedChampion.java b/Mage.Sets/src/mage/cards/e/EtchedChampion.java index 176017ef353..872ae670259 100644 --- a/Mage.Sets/src/mage/cards/e/EtchedChampion.java +++ b/Mage.Sets/src/mage/cards/e/EtchedChampion.java @@ -22,7 +22,7 @@ import java.util.UUID; * @author North */ public final class EtchedChampion extends CardImpl { - private static final String ruleText = "Metalcraft — Etched Champion has protection from all colors as long as you control three or more artifacts"; + private static final String ruleText = "{this} has protection from all colors as long as you control three or more artifacts"; private static final FilterCard filter = new FilterCard("all colors"); diff --git a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java index d94793960c7..57102524f93 100644 --- a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java +++ b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java @@ -8,10 +8,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.game.Game; @@ -40,7 +37,7 @@ public final class ExclusionRitual extends CardImpl { // Imprint - When Exclusion Ritual enters the battlefield, exile target nonland permanent. Ability ability = new EntersBattlefieldTriggeredAbility(new ExclusionRitualImprintEffect(), false); ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability); + this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT); // Players can't cast spells with the same name as the exiled card. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ExclusionRitualReplacementEffect())); } diff --git a/Mage.Sets/src/mage/cards/e/ExertInfluence.java b/Mage.Sets/src/mage/cards/e/ExertInfluence.java index c071c37ad09..c56cb657859 100644 --- a/Mage.Sets/src/mage/cards/e/ExertInfluence.java +++ b/Mage.Sets/src/mage/cards/e/ExertInfluence.java @@ -48,7 +48,7 @@ class ExertInfluenceEffect extends OneShotEffect { public ExertInfluenceEffect() { super(Outcome.GainControl); - this.staticText = "Gain control of target creature if its power is less than or equal to the number of colors spent to cast this spell"; + this.staticText = "Gain control of target creature if its power is less than or equal to the number of colors of mana spent to cast this spell"; } public ExertInfluenceEffect(final ExertInfluenceEffect effect) { diff --git a/Mage.Sets/src/mage/cards/e/EzurisBrigade.java b/Mage.Sets/src/mage/cards/e/EzurisBrigade.java index 888af884a05..70c285bf42d 100644 --- a/Mage.Sets/src/mage/cards/e/EzurisBrigade.java +++ b/Mage.Sets/src/mage/cards/e/EzurisBrigade.java @@ -20,7 +20,7 @@ import java.util.UUID; * @author Loki */ public final class EzurisBrigade extends CardImpl { - private static final String rule = "Metalcraft — As long as you control three or more artifacts, Ezuri's Brigade gets +4/+4 and has trample"; + private static final String rule = "As long as you control three or more artifacts, {this} gets +4/+4"; public EzurisBrigade(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); @@ -33,7 +33,7 @@ public final class EzurisBrigade extends CardImpl { ContinuousEffect boostSource = new BoostSourceEffect(4, 4, Duration.WhileOnBattlefield); ConditionalContinuousEffect effect = new ConditionalContinuousEffect(boostSource, MetalcraftCondition.instance, rule); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); - ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield), MetalcraftCondition.instance, "")); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield), MetalcraftCondition.instance, "and has trample")); ability.setAbilityWord(AbilityWord.METALCRAFT); ability.addHint(MetalcraftHint.instance); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/f/FesterhideBoar.java b/Mage.Sets/src/mage/cards/f/FesterhideBoar.java index 1beda4953c7..9714339aaea 100644 --- a/Mage.Sets/src/mage/cards/f/FesterhideBoar.java +++ b/Mage.Sets/src/mage/cards/f/FesterhideBoar.java @@ -11,6 +11,7 @@ import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; @@ -31,7 +32,7 @@ public final class FesterhideBoar extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Morbid — Festerhide Boar enters the battlefield with two +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), - MorbidCondition.instance, ""), "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); + MorbidCondition.instance, ""), "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); } private FesterhideBoar(final FesterhideBoar card) { diff --git a/Mage.Sets/src/mage/cards/g/GalvanicBlast.java b/Mage.Sets/src/mage/cards/g/GalvanicBlast.java index d0962d36bdd..3f715e60704 100644 --- a/Mage.Sets/src/mage/cards/g/GalvanicBlast.java +++ b/Mage.Sets/src/mage/cards/g/GalvanicBlast.java @@ -31,7 +31,6 @@ public final class GalvanicBlast extends CardImpl { MetalcraftCondition.instance, effectText )); this.getSpellAbility().addTarget(new TargetAnyTarget()); - this.getSpellAbility().setAbilityWord(AbilityWord.METALCRAFT); this.getSpellAbility().addHint(MetalcraftHint.instance); } diff --git a/Mage.Sets/src/mage/cards/g/GhalmasWarden.java b/Mage.Sets/src/mage/cards/g/GhalmasWarden.java index 98f11973e56..b38ebfa6516 100644 --- a/Mage.Sets/src/mage/cards/g/GhalmasWarden.java +++ b/Mage.Sets/src/mage/cards/g/GhalmasWarden.java @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class GhalmasWarden extends CardImpl { - private static final String rule = "Metalcraft — Ghalma's Warden gets +2/+2 as long as you control three or more artifacts"; + private static final String rule = "{this} gets +2/+2 as long as you control three or more artifacts"; public GhalmasWarden(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); diff --git a/Mage.Sets/src/mage/cards/g/GravetillerWurm.java b/Mage.Sets/src/mage/cards/g/GravetillerWurm.java index 6b2684c788b..3ae7150b9f0 100644 --- a/Mage.Sets/src/mage/cards/g/GravetillerWurm.java +++ b/Mage.Sets/src/mage/cards/g/GravetillerWurm.java @@ -11,6 +11,7 @@ import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; @@ -31,7 +32,7 @@ public final class GravetillerWurm extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Morbid — Gravetiller Wurm enters the battlefield with four +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)), - MorbidCondition.instance, ""), "with four +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); + MorbidCondition.instance, ""), "with four +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); } private GravetillerWurm(final GravetillerWurm card) { diff --git a/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java b/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java index 7ced58150c5..560f85dc0da 100644 --- a/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java +++ b/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java @@ -31,7 +31,7 @@ public final class GreenwheelLiberator extends CardImpl { // permanent you controlled left the battlefield this turn. Ability ability = new EntersBattlefieldAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, RevoltCondition.instance, - "Revolt — {this} enters the battlefield with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn", null); + "Revolt — {this} enters the battlefield with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn.", null); ability.addWatcher(new RevoltWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/h/HeartlessPillage.java b/Mage.Sets/src/mage/cards/h/HeartlessPillage.java index 9c366f5a7d6..0bb94d9f380 100644 --- a/Mage.Sets/src/mage/cards/h/HeartlessPillage.java +++ b/Mage.Sets/src/mage/cards/h/HeartlessPillage.java @@ -31,9 +31,8 @@ public final class HeartlessPillage extends CardImpl { this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new CreateTokenEffect(new TreasureToken()), RaidCondition.instance, - "

Raid — If you attacked with a creature this turn, create a colorless Treasure artifact token with \"{T}, Sacrifice this artifact: Add one mana of any color.\"")); + "

Raid — If you attacked this turn, create a colorless Treasure artifact token with \"{T}, Sacrifice this artifact: Add one mana of any color.\"")); this.getSpellAbility().addWatcher(new PlayerAttackedWatcher()); - this.getSpellAbility().setAbilityWord(AbilityWord.RAID); this.getSpellAbility().addHint(RaidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/h/HiddenStockpile.java b/Mage.Sets/src/mage/cards/h/HiddenStockpile.java index 3bca1b1a4ed..9d50a5e0e5c 100644 --- a/Mage.Sets/src/mage/cards/h/HiddenStockpile.java +++ b/Mage.Sets/src/mage/cards/h/HiddenStockpile.java @@ -32,7 +32,7 @@ public final class HiddenStockpile extends CardImpl { // Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 colorless Servo artifact creature token. Ability ability = new ConditionalInterveningIfTriggeredAbility(new BeginningOfYourEndStepTriggeredAbility(new CreateTokenEffect(new ServoToken()), false), RevoltCondition.instance, - "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 colorless Servo artifact creature token"); + "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 colorless Servo artifact creature token."); ability.setAbilityWord(AbilityWord.REVOLT); ability.addWatcher(new RevoltWatcher()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/h/HowlOfTheHorde.java b/Mage.Sets/src/mage/cards/h/HowlOfTheHorde.java index 011b9b4bc06..121d8d766b1 100644 --- a/Mage.Sets/src/mage/cards/h/HowlOfTheHorde.java +++ b/Mage.Sets/src/mage/cards/h/HowlOfTheHorde.java @@ -38,10 +38,9 @@ public final class HowlOfTheHorde extends CardImpl { this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new CreateDelayedTriggeredAbilityEffect(new HowlOfTheHordeDelayedTriggeredAbility()), RaidCondition.instance, - "

Raid — If you attacked with a creature this turn, when you cast your next instant or sorcery spell this turn, copy that spell an additional time. You may choose new targets for the copy.") + "

Raid — If you attacked this turn, when you cast your next instant or sorcery spell this turn, copy that spell an additional time. You may choose new targets for the copy.") ); this.getSpellAbility().addWatcher(new PlayerAttackedWatcher()); - this.getSpellAbility().setAbilityWord(AbilityWord.RAID); this.getSpellAbility().addHint(RaidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java b/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java index b549c4071fa..173ca7df69a 100644 --- a/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java +++ b/Mage.Sets/src/mage/cards/h/HungerOfTheHowlpack.java @@ -29,7 +29,7 @@ public final class HungerOfTheHowlpack extends CardImpl { new AddCountersTargetEffect(CounterType.P1P1.createInstance(3)), new AddCountersTargetEffect(CounterType.P1P1.createInstance()), MorbidCondition.instance, - "Put a +1/+1 counter on target creature. Morbid — Put three +1/+1 counters on that creature instead if a creature died this turn")); + "Put a +1/+1 counter on target creature.
Morbid — Put three +1/+1 counters on that creature instead if a creature died this turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addHint(MorbidHint.instance); } diff --git a/Mage.Sets/src/mage/cards/i/IndomitableArchangel.java b/Mage.Sets/src/mage/cards/i/IndomitableArchangel.java index 2d2b06ae033..a89daa819a9 100644 --- a/Mage.Sets/src/mage/cards/i/IndomitableArchangel.java +++ b/Mage.Sets/src/mage/cards/i/IndomitableArchangel.java @@ -21,7 +21,7 @@ import java.util.UUID; */ public final class IndomitableArchangel extends CardImpl { - private static final String rule = "Metalcraft — Artifacts you control have shroud as long as you control three or more artifacts."; + private static final String rule = "Artifacts you control have shroud as long as you control three or more artifacts."; private static final FilterPermanent filter = new FilterPermanent("Artifacts"); diff --git a/Mage.Sets/src/mage/cards/i/InexorableBlob.java b/Mage.Sets/src/mage/cards/i/InexorableBlob.java index 0500f61cefb..1c708a27b90 100644 --- a/Mage.Sets/src/mage/cards/i/InexorableBlob.java +++ b/Mage.Sets/src/mage/cards/i/InexorableBlob.java @@ -29,8 +29,8 @@ public final class InexorableBlob extends CardImpl { // in your graveyard, create a 3/3 green Ooze creature token that’s tapped and attacking. this.addAbility(new ConditionalInterveningIfTriggeredAbility(new AttacksTriggeredAbility(new CreateTokenEffect(new InexorableBlobOozeToken(), 1, true, true), false), DeliriumCondition.instance, - "Delirium — Whenever {this} attacks and there are at least four card types among cards in your graveyard, " - + "create a 3/3 green Ooze creature token tapped and attacking.") + "Delirium — Whenever {this} attacks, if there are four or more card types among cards in your graveyard, " + + "create a 3/3 green Ooze creature token that's tapped and attacking.") .addHint(CardTypesInGraveyardHint.YOU)); } diff --git a/Mage.Sets/src/mage/cards/k/KnowledgePool.java b/Mage.Sets/src/mage/cards/k/KnowledgePool.java index 06d3300b3d7..c4af8384624 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgePool.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgePool.java @@ -9,6 +9,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -37,7 +38,7 @@ public final class KnowledgePool extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}"); // Imprint - When Knowledge Pool enters the battlefield, each player exiles the top three cards of their library - this.addAbility(new EntersBattlefieldTriggeredAbility(new KnowledgePoolEffect1(), false)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new KnowledgePoolEffect1(), false).setAbilityWord(AbilityWord.IMPRINT)); // Whenever a player casts a spell from their hand, that player exiles it. If the player does, they may cast another nonland card exiled with Knowledge Pool without paying that card's mana cost. this.addAbility(new KnowledgePoolAbility()); diff --git a/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java b/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java index bdba958ee09..d66cc678d58 100644 --- a/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java +++ b/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java @@ -37,7 +37,7 @@ public final class LifecraftCavalry extends CardImpl { new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, RevoltCondition.instance, - "Revolt — {this} enters the battlefield with two +1/+1 counter on it if a permanent you controlled left the battlefield this turn", null), + "Revolt — {this} enters the battlefield with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn.", null), new RevoltWatcher() ); } diff --git a/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java b/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java index 184bdfc754a..3e05b674b76 100644 --- a/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java +++ b/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java @@ -42,7 +42,7 @@ public final class MalakirSoothsayer extends CardImpl { // Cohort — {T}, Tap an untapped Ally you control: You draw a card and you lose a life. SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new DrawCardSourceControllerEffect(1), + new DrawCardSourceControllerEffect(1).setText("you draw a card"), new TapSourceCost()); ability.setAbilityWord(AbilityWord.COHORT); ability.addCost(new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false))); diff --git a/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java b/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java index 829bcd50994..7f1534ab42d 100644 --- a/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java +++ b/Mage.Sets/src/mage/cards/m/MaliciousAffliction.java @@ -36,7 +36,7 @@ public final class MaliciousAffliction extends CardImpl { Ability ability = new ConditionalInterveningIfTriggeredAbility( new CastSourceTriggeredAbility(new CopySourceSpellEffect(), true), MorbidCondition.instance, "Morbid — When you cast this spell, " + - "if a creature died this turn, you may copy {this} and may choose a new target for the copy" + "if a creature died this turn, you may copy {this} and may choose a new target for the copy." ); ability.setRuleAtTheTop(true); this.addAbility(ability.addHint(MorbidHint.instance)); diff --git a/Mage.Sets/src/mage/cards/m/MightBeyondReason.java b/Mage.Sets/src/mage/cards/m/MightBeyondReason.java index a4f1bef316e..eced211738a 100644 --- a/Mage.Sets/src/mage/cards/m/MightBeyondReason.java +++ b/Mage.Sets/src/mage/cards/m/MightBeyondReason.java @@ -27,7 +27,7 @@ public final class MightBeyondReason extends CardImpl { new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), DeliriumCondition.instance, "Put two +1/+1 counter on target creature.
" - + "Delirium — Put three +1/+1 counter on that creature instead if there are four or more card types among cards in your graveyard" + + "Delirium — Put three +1/+1 counters on that creature instead if there are four or more card types among cards in your graveyard" )); getSpellAbility().addTarget(new TargetCreaturePermanent()); getSpellAbility().addHint(CardTypesInGraveyardHint.YOU); diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 116f55e0050..a951e44e4b6 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -17,6 +17,7 @@ import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -102,7 +103,7 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with {this} to its owner's graveyard."; + return AbilityWord.IMPRINT.formatWord() + "Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with {this} to its owner's graveyard."; } } diff --git a/Mage.Sets/src/mage/cards/m/MirranMettle.java b/Mage.Sets/src/mage/cards/m/MirranMettle.java index 77beffebf79..b87643552fb 100644 --- a/Mage.Sets/src/mage/cards/m/MirranMettle.java +++ b/Mage.Sets/src/mage/cards/m/MirranMettle.java @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class MirranMettle extends CardImpl { - private static final String effectText = "Metalcraft — That creature gets +4/+4 until end of turn instead if you control three or more artifacts."; + private static final String effectText = "
Metalcraft — That creature gets +4/+4 until end of turn instead if you control three or more artifacts."; public MirranMettle(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); @@ -31,7 +31,6 @@ public final class MirranMettle extends CardImpl { // Metalcraft — That creature gets +4/+4 until end of turn instead if you control three or more artifacts. this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new BoostTargetEffect(2, 2, Duration.EndOfTurn), new LockedInCondition(MetalcraftCondition.instance), effectText)); - this.getSpellAbility().setAbilityWord(AbilityWord.METALCRAFT); this.getSpellAbility().addHint(MetalcraftHint.instance); } diff --git a/Mage.Sets/src/mage/cards/m/MoltenPsyche.java b/Mage.Sets/src/mage/cards/m/MoltenPsyche.java index bbec0aa34e5..ac66622c24f 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenPsyche.java +++ b/Mage.Sets/src/mage/cards/m/MoltenPsyche.java @@ -29,7 +29,6 @@ public final class MoltenPsyche extends CardImpl { // Metalcraft — If you control three or more artifacts, Molten Psyche deals damage to each opponent equal to the number of cards that player has drawn this turn. this.getSpellAbility().addEffect(new MoltenPsycheEffect()); this.getSpellAbility().addWatcher(new MoltenPsycheWatcher()); - this.getSpellAbility().setAbilityWord(AbilityWord.METALCRAFT); this.getSpellAbility().addHint(MetalcraftHint.instance); } diff --git a/Mage.Sets/src/mage/cards/m/MyrWelder.java b/Mage.Sets/src/mage/cards/m/MyrWelder.java index 77ca9e70423..d8b71b15129 100644 --- a/Mage.Sets/src/mage/cards/m/MyrWelder.java +++ b/Mage.Sets/src/mage/cards/m/MyrWelder.java @@ -12,13 +12,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterArtifactCard; import mage.game.Game; import mage.game.permanent.Permanent; @@ -39,7 +33,7 @@ public final class MyrWelder extends CardImpl { // Imprint - {tap}: Exile target artifact card from a graveyard SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MyrWelderEffect(), new TapSourceCost()); ability.addTarget(new TargetCardInGraveyard(new FilterArtifactCard("artifact card from a graveyard"))); - this.addAbility(ability); + this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT)); // Myr Welder has all activated abilities of all cards exiled with it this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MyrWelderContinuousEffect())); diff --git a/Mage.Sets/src/mage/cards/o/ObNixilisTheFallen.java b/Mage.Sets/src/mage/cards/o/ObNixilisTheFallen.java index fc2ec9a3c0a..3a68864fa39 100644 --- a/Mage.Sets/src/mage/cards/o/ObNixilisTheFallen.java +++ b/Mage.Sets/src/mage/cards/o/ObNixilisTheFallen.java @@ -32,7 +32,7 @@ public final class ObNixilisTheFallen extends CardImpl { // Landfall - Whenever a land enters the battlefield under your control, you may have target player lose 3 life. // If you do, put three +1/+1 counters on Ob Nixilis, the Fallen. Ability ability = new LandfallAbility(new LoseLifeTargetEffect(3), true); - ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(3))); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)).concatBy("If you do,")); ability.addTarget(new TargetPlayer()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PainfulTruths.java b/Mage.Sets/src/mage/cards/p/PainfulTruths.java index 78ddc7b4bb6..9064c4f8d8a 100644 --- a/Mage.Sets/src/mage/cards/p/PainfulTruths.java +++ b/Mage.Sets/src/mage/cards/p/PainfulTruths.java @@ -26,7 +26,7 @@ public final class PainfulTruths extends CardImpl { effect.setText("You draw X cards"); getSpellAbility().addEffect(effect); effect = new LoseLifeSourceControllerEffect(ColorsOfManaSpentToCastCount.getInstance()); - effect.setText("and lose X life, where X is the number of colors of mana spent to cast this spell"); + effect.setText("and you lose X life, where X is the number of colors of mana spent to cast this spell"); getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/p/PanopticMirror.java b/Mage.Sets/src/mage/cards/p/PanopticMirror.java index 8faeea8f0df..ee1aabaccfc 100644 --- a/Mage.Sets/src/mage/cards/p/PanopticMirror.java +++ b/Mage.Sets/src/mage/cards/p/PanopticMirror.java @@ -32,7 +32,7 @@ public final class PanopticMirror extends CardImpl { // Imprint - {X}, {tap}: You may exile an instant or sorcery card with converted mana cost X from your hand. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PanopticMirrorExileEffect(), new VariableManaCost()); ability.addCost(new TapSourceCost()); - this.addAbility(ability); + this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT)); // At the beginning of your upkeep, you may copy a card exiled with Panoptic Mirror. If you do, you may cast the copy without paying its mana cost. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new PanopticMirrorCastEffect(), TargetController.YOU, true)); } diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianIngester.java b/Mage.Sets/src/mage/cards/p/PhyrexianIngester.java index 333c7b2efdf..770ced6731a 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianIngester.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianIngester.java @@ -12,13 +12,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.TokenPredicate; @@ -50,7 +44,7 @@ public final class PhyrexianIngester extends CardImpl { // Imprint - When Phyrexian Ingester enters the battlefield, you may exile target nontoken creature. Ability ability = new EntersBattlefieldTriggeredAbility(new PhyrexianIngesterImprintEffect(), true); ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability); + this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT)); // Phyrexian Ingester gets +X/+Y, where X is the exiled creature card's power and Y is its toughness. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhyrexianIngesterBoostEffect())); } diff --git a/Mage.Sets/src/mage/cards/p/ProteanRaider.java b/Mage.Sets/src/mage/cards/p/ProteanRaider.java index 46674dac3b6..b65252b7ee8 100644 --- a/Mage.Sets/src/mage/cards/p/ProteanRaider.java +++ b/Mage.Sets/src/mage/cards/p/ProteanRaider.java @@ -30,7 +30,7 @@ public final class ProteanRaider extends CardImpl { // Raid — If you attacked with a creature this turn, you may have Protean Raider enter the battlefield as a copy of any creature on the battlefield. Ability ability = new EntersBattlefieldAbility(new CopyPermanentEffect(), true, RaidCondition.instance, - "Raid — If you attacked with a creature this turn, you may have {this} enter the battlefield as a copy of any creature on the battlefield.", ""); + "Raid — If you attacked this turn, you may have {this} enter the battlefield as a copy of any creature on the battlefield.", ""); ability.setAbilityWord(AbilityWord.RAID); ability.addHint(RaidHint.instance); this.addAbility(ability, new PlayerAttackedWatcher()); diff --git a/Mage.Sets/src/mage/cards/r/RazorfieldRhino.java b/Mage.Sets/src/mage/cards/r/RazorfieldRhino.java index 44199fd08d9..92502e35c98 100644 --- a/Mage.Sets/src/mage/cards/r/RazorfieldRhino.java +++ b/Mage.Sets/src/mage/cards/r/RazorfieldRhino.java @@ -26,7 +26,7 @@ public final class RazorfieldRhino extends CardImpl { // Metalcraft — Razorfield Rhino gets +2/+2 as long as you control three or more artifacts. ContinuousEffect effect1 = new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(effect1, MetalcraftCondition.instance, "Metalcraft — Razorfield Rhino gets +2/+2 as long as you control three or more artifacts")) + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(effect1, MetalcraftCondition.instance, "{this} gets +2/+2 as long as you control three or more artifacts")) .setAbilityWord(AbilityWord.METALCRAFT) .addHint(MetalcraftHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/r/RetreatToHagra.java b/Mage.Sets/src/mage/cards/r/RetreatToHagra.java index 809847beaaa..86f40f2d08c 100644 --- a/Mage.Sets/src/mage/cards/r/RetreatToHagra.java +++ b/Mage.Sets/src/mage/cards/r/RetreatToHagra.java @@ -28,7 +28,7 @@ public final class RetreatToHagra extends CardImpl { // Landfall- Whenever a land enters the battlefield under your control, // choose one - Target creature gets +1/+0 and gains deathtouch until end of turn; LandfallAbility ability = new LandfallAbility(new BoostTargetEffect(1, 0, Duration.EndOfTurn), false); - Effect effect = new GainAbilityTargetEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn); + Effect effect = new GainAbilityTargetEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn).setText("and gains deathtouch until end of turn"); effect.setOutcome(Outcome.Benefit); ability.addEffect(effect); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/r/RustedRelic.java b/Mage.Sets/src/mage/cards/r/RustedRelic.java index be3eff980fb..514d30a0854 100644 --- a/Mage.Sets/src/mage/cards/r/RustedRelic.java +++ b/Mage.Sets/src/mage/cards/r/RustedRelic.java @@ -26,7 +26,7 @@ public final class RustedRelic extends CardImpl { new ConditionalContinuousEffect( new BecomesCreatureSourceEffect(new RustedRelicToken(), "artifact", Duration.WhileOnBattlefield), MetalcraftCondition.instance, - "Metalcraft — {this} is a 5/5 Golem artifact creature as long as you control three or more artifacts")) + "{this} is a 5/5 Golem artifact creature as long as you control three or more artifacts")) .setAbilityWord(AbilityWord.METALCRAFT) .addHint(MetalcraftHint.instance) ); diff --git a/Mage.Sets/src/mage/cards/s/SemblanceAnvil.java b/Mage.Sets/src/mage/cards/s/SemblanceAnvil.java index afef026fe8a..82f3a11d07f 100644 --- a/Mage.Sets/src/mage/cards/s/SemblanceAnvil.java +++ b/Mage.Sets/src/mage/cards/s/SemblanceAnvil.java @@ -29,7 +29,7 @@ public final class SemblanceAnvil extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // Imprint - When Semblance Anvil enters the battlefield, you may exile a nonland card from your hand. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SemblanceAnvilEffect(), true)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SemblanceAnvilEffect(), true).setAbilityWord(AbilityWord.IMPRINT)); // Spells you cast that share a card type with the exiled card cost {2} less to cast. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SemblanceAnvilCostReductionEffect())); diff --git a/Mage.Sets/src/mage/cards/s/ShoalSerpent.java b/Mage.Sets/src/mage/cards/s/ShoalSerpent.java index 778aa865ef6..1c7e1d8a099 100644 --- a/Mage.Sets/src/mage/cards/s/ShoalSerpent.java +++ b/Mage.Sets/src/mage/cards/s/ShoalSerpent.java @@ -48,7 +48,7 @@ class ShoalSerpentEffect extends ContinuousEffectImpl { public ShoalSerpentEffect() { super(Duration.EndOfTurn, Outcome.AddAbility); - staticText = "Until end of turn, {this} loses defender"; + staticText = "{this} loses defender until end of turn"; } public ShoalSerpentEffect(final ShoalSerpentEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SiegehornCeratops.java b/Mage.Sets/src/mage/cards/s/SiegehornCeratops.java index 427d94439ab..a232f75a5e8 100644 --- a/Mage.Sets/src/mage/cards/s/SiegehornCeratops.java +++ b/Mage.Sets/src/mage/cards/s/SiegehornCeratops.java @@ -27,7 +27,7 @@ public final class SiegehornCeratops extends CardImpl { // Enrage — Whenever Siegehorn Ceratops is dealt damage, put two +1/+1 counters on it. this.addAbility(new DealtDamageToSourceTriggeredAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)) - .setText("put two +1/+1 counter on it"), false, true)); + .setText("put two +1/+1 counters on it"), false, true)); } private SiegehornCeratops(final SiegehornCeratops card) { diff --git a/Mage.Sets/src/mage/cards/s/SilverSeraph.java b/Mage.Sets/src/mage/cards/s/SilverSeraph.java index 20b99c3e778..e17d2334f2a 100644 --- a/Mage.Sets/src/mage/cards/s/SilverSeraph.java +++ b/Mage.Sets/src/mage/cards/s/SilverSeraph.java @@ -34,7 +34,7 @@ public final class SilverSeraph extends CardImpl { // Threshold - Other creatures you control get +2/+2 as long as seven or more cards are in your graveyard. Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new BoostControlledEffect(2, 2, Duration.WhileOnBattlefield, true), new CardsInControllerGraveyardCondition(7), - "other creatures you control +2/+2 as long as seven or more cards are in your graveyard")); + "other creatures you control get +2/+2 as long as seven or more cards are in your graveyard")); ability.setAbilityWord(AbilityWord.THRESHOLD); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SlaughterhouseBouncer.java b/Mage.Sets/src/mage/cards/s/SlaughterhouseBouncer.java index eb039b4cae0..8627abe95d4 100644 --- a/Mage.Sets/src/mage/cards/s/SlaughterhouseBouncer.java +++ b/Mage.Sets/src/mage/cards/s/SlaughterhouseBouncer.java @@ -34,9 +34,8 @@ public final class SlaughterhouseBouncer extends CardImpl { Ability ability = new ConditionalInterveningIfTriggeredAbility( new DiesSourceTriggeredAbility(new BoostTargetEffect(-3, -3, Duration.EndOfTurn)), HellbentCondition.instance, - "When {this} dies, if you have no cards in hand, target creature gets -3/-3 until end of turn." + AbilityWord.HELLBENT.formatWord() + "When {this} dies, if you have no cards in hand, target creature gets -3/-3 until end of turn." ); - ability.setAbilityWord(AbilityWord.HELLBENT); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SnapsailGlider.java b/Mage.Sets/src/mage/cards/s/SnapsailGlider.java index 0461c306db7..87112631742 100644 --- a/Mage.Sets/src/mage/cards/s/SnapsailGlider.java +++ b/Mage.Sets/src/mage/cards/s/SnapsailGlider.java @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class SnapsailGlider extends CardImpl { - protected static String rule = "Metalcraft — Snapsail Glider has flying as long as you control three or more artifacts"; + protected static String rule = "{this} has flying as long as you control three or more artifacts"; public SnapsailGlider(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); diff --git a/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java b/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java index c332e5bbd54..565da40ac6a 100644 --- a/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java +++ b/Mage.Sets/src/mage/cards/s/SomberwaldSpider.java @@ -11,6 +11,7 @@ import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; @@ -32,7 +33,7 @@ public final class SomberwaldSpider extends CardImpl { // Morbid — Somberwald Spider enters the battlefield with two +1/+1 counters on it if a creature died this turn. this.addAbility(new EntersBattlefieldAbility( new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), MorbidCondition.instance, ""), - "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance)); + "with two +1/+1 counters on it if a creature died this turn").addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); } private SomberwaldSpider(final SomberwaldSpider card) { diff --git a/Mage.Sets/src/mage/cards/s/SpiralingDuelist.java b/Mage.Sets/src/mage/cards/s/SpiralingDuelist.java index 7ad2a65f6e6..91974047010 100644 --- a/Mage.Sets/src/mage/cards/s/SpiralingDuelist.java +++ b/Mage.Sets/src/mage/cards/s/SpiralingDuelist.java @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class SpiralingDuelist extends CardImpl { - private static final String effectText = "Metalcraft — Spiraling Duelist has double strike as long as you control three or more artifacts."; + private static final String effectText = "{this} has double strike as long as you control three or more artifacts."; public SpiralingDuelist(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); diff --git a/Mage.Sets/src/mage/cards/s/SpireSerpent.java b/Mage.Sets/src/mage/cards/s/SpireSerpent.java index a10afcbe59d..81c34fc7a1a 100644 --- a/Mage.Sets/src/mage/cards/s/SpireSerpent.java +++ b/Mage.Sets/src/mage/cards/s/SpireSerpent.java @@ -22,7 +22,7 @@ import java.util.UUID; */ public final class SpireSerpent extends CardImpl { - private static final String abilityText1 = "Metalcraft — As long as you control three or more artifacts, {this} gets +2/+2"; + private static final String abilityText1 = "As long as you control three or more artifacts, {this} gets +2/+2"; public SpireSerpent(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); diff --git a/Mage.Sets/src/mage/cards/s/StrataScythe.java b/Mage.Sets/src/mage/cards/s/StrataScythe.java index c9bb0eec607..6905e435e84 100644 --- a/Mage.Sets/src/mage/cards/s/StrataScythe.java +++ b/Mage.Sets/src/mage/cards/s/StrataScythe.java @@ -14,10 +14,7 @@ import mage.abilities.keyword.EquipAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterLandCard; import mage.filter.predicate.mageobject.NamePredicate; @@ -35,7 +32,7 @@ public final class StrataScythe extends CardImpl { public StrataScythe(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); this.subtype.add(SubType.EQUIPMENT); - this.addAbility(new EntersBattlefieldTriggeredAbility(new StrataScytheImprintEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new StrataScytheImprintEffect()).setAbilityWord(AbilityWord.IMPRINT)); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(SameNameAsExiledCountValue.getInstance(), SameNameAsExiledCountValue.getInstance()))); this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3))); } diff --git a/Mage.Sets/src/mage/cards/s/SummaryJudgment.java b/Mage.Sets/src/mage/cards/s/SummaryJudgment.java index 615f1d9159e..a6ed77c27e4 100644 --- a/Mage.Sets/src/mage/cards/s/SummaryJudgment.java +++ b/Mage.Sets/src/mage/cards/s/SummaryJudgment.java @@ -52,7 +52,7 @@ class SummaryJudgementEffect extends OneShotEffect { super(Outcome.Benefit); staticText = "{this} deals 3 damage to target tapped creature." + "
Addendum — If you cast this spell during your main phase, " + - "it deals 5 damage to that creature instead."; + "it deals 5 damage."; } private SummaryJudgementEffect(final SummaryJudgementEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SunCrownedHunters.java b/Mage.Sets/src/mage/cards/s/SunCrownedHunters.java index fbd8fbb2169..7d487a7128d 100644 --- a/Mage.Sets/src/mage/cards/s/SunCrownedHunters.java +++ b/Mage.Sets/src/mage/cards/s/SunCrownedHunters.java @@ -27,7 +27,7 @@ public final class SunCrownedHunters extends CardImpl { // Enrage — Whenever Sun-Crowned Hunters is dealt damage, it deals 3 damage to target opponent. Ability ability = new DealtDamageToSourceTriggeredAbility( - new DamageTargetEffect(3).setText("it deals 3 damage to target opponent"), false, true + new DamageTargetEffect(3, "it"), false, true ); ability.addTarget(new TargetOpponentOrPlaneswalker()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SunspringExpedition.java b/Mage.Sets/src/mage/cards/s/SunspringExpedition.java index 79a950295e4..5e76cd18826 100644 --- a/Mage.Sets/src/mage/cards/s/SunspringExpedition.java +++ b/Mage.Sets/src/mage/cards/s/SunspringExpedition.java @@ -1,14 +1,9 @@ - - package mage.cards.s; -import java.util.UUID; import mage.abilities.ActivatedAbility; import mage.abilities.common.LandfallAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.CompositeCost; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.effects.common.GainLifeEffect; @@ -16,24 +11,25 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; import mage.counters.CounterType; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public final class SunspringExpedition extends CardImpl { public SunspringExpedition(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); this.addAbility(new LandfallAbility(new AddCountersSourceEffect(CounterType.QUEST.createInstance()), true)); - Costs costs = new CostsImpl<>(); - costs.add(new RemoveCountersSourceCost(CounterType.QUEST.createInstance(3))); - costs.add(new SacrificeSourceCost()); - ActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(8), costs); + ActivatedAbility ability = new SimpleActivatedAbility( + new GainLifeEffect(8), new CompositeCost( + new RemoveCountersSourceCost(CounterType.QUEST.createInstance(3)), + new SacrificeSourceCost(), "Remove three quest counters from {this} and sacrifice it" + )); this.addAbility(ability); } @@ -45,5 +41,4 @@ public final class SunspringExpedition extends CardImpl { public SunspringExpedition copy() { return new SunspringExpedition(this); } - } diff --git a/Mage.Sets/src/mage/cards/t/TribalFlames.java b/Mage.Sets/src/mage/cards/t/TribalFlames.java index 24eef4f2022..dd8cebe2a19 100644 --- a/Mage.Sets/src/mage/cards/t/TribalFlames.java +++ b/Mage.Sets/src/mage/cards/t/TribalFlames.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.hint.common.DomainHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.target.common.TargetAnyTarget; @@ -24,6 +25,7 @@ public final class TribalFlames extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(new DomainValue())); this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addHint(DomainHint.instance); + this.getSpellAbility().setAbilityWord(AbilityWord.DOMAIN); } private TribalFlames(final TribalFlames card) { diff --git a/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java b/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java index 772b7a3585f..a7450f9da67 100644 --- a/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java +++ b/Mage.Sets/src/mage/cards/u/UlvenwaldBear.java @@ -11,6 +11,7 @@ import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; @@ -34,7 +35,7 @@ public final class UlvenwaldBear extends CardImpl { Ability ability = new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2), Outcome.BoostCreature)), MorbidCondition.instance, "When {this} enters the battlefield, if a creature died this turn, put two +1/+1 counters on target creature."); ability.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability.addHint(MorbidHint.instance)); + this.addAbility(ability.addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); } private UlvenwaldBear(final UlvenwaldBear card) { diff --git a/Mage.Sets/src/mage/cards/u/UnbreakableFormation.java b/Mage.Sets/src/mage/cards/u/UnbreakableFormation.java index 62ced3ec1d4..34df150c947 100644 --- a/Mage.Sets/src/mage/cards/u/UnbreakableFormation.java +++ b/Mage.Sets/src/mage/cards/u/UnbreakableFormation.java @@ -51,7 +51,7 @@ class UnbreakableFormationEffect extends OneShotEffect { UnbreakableFormationEffect() { super(Outcome.Benefit); staticText = "
Addendum — If you cast this spell during your main phase, " + - "put a +1/+1 counter on each of those creatures, and they also gain vigilance until end of turn."; + "put a +1/+1 counter on each of those creatures and they gain vigilance until end of turn."; } private UnbreakableFormationEffect(final UnbreakableFormationEffect effect) { diff --git a/Mage.Sets/src/mage/cards/u/UnholyHunger.java b/Mage.Sets/src/mage/cards/u/UnholyHunger.java index 0298c4b3fbd..8e15034517e 100644 --- a/Mage.Sets/src/mage/cards/u/UnholyHunger.java +++ b/Mage.Sets/src/mage/cards/u/UnholyHunger.java @@ -27,7 +27,7 @@ public final class UnholyHunger extends CardImpl { // Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, you gain 2 life. Effect effect = new ConditionalOneShotEffect(new GainLifeEffect(2), - SpellMasteryCondition.instance, "Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, you gain 2 life"); + SpellMasteryCondition.instance, "
Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, you gain 2 life"); this.getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/w/WarNameAspirant.java b/Mage.Sets/src/mage/cards/w/WarNameAspirant.java index de94cf8fe48..76a632da8fa 100644 --- a/Mage.Sets/src/mage/cards/w/WarNameAspirant.java +++ b/Mage.Sets/src/mage/cards/w/WarNameAspirant.java @@ -39,7 +39,7 @@ public final class WarNameAspirant extends CardImpl { // Raid — War-Name Aspirant enters the battlefield with a +1/+1 counter on it if you attacked this turn. this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1), false), RaidCondition.instance, - "Raid — {this} enters the battlefield with a +1/+1 counter on it if you attacked this turn", + "Raid — {this} enters the battlefield with a +1/+1 counter on it if you attacked this turn.", "{this} enters the battlefield with a +1/+1 counter") .setAbilityWord(AbilityWord.RAID) .addHint(RaidHint.instance), diff --git a/Mage.Sets/src/mage/cards/z/ZendikarsRoil.java b/Mage.Sets/src/mage/cards/z/ZendikarsRoil.java index 01be0ac8b56..49d27a75e2e 100644 --- a/Mage.Sets/src/mage/cards/z/ZendikarsRoil.java +++ b/Mage.Sets/src/mage/cards/z/ZendikarsRoil.java @@ -1,18 +1,15 @@ - package mage.cards.z; -import java.util.UUID; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; -import mage.abilities.effects.Effect; +import mage.abilities.common.LandfallAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterLandPermanent; import mage.game.permanent.token.ZendikarsRoilElementalToken; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ZendikarsRoil extends CardImpl { @@ -21,8 +18,7 @@ public final class ZendikarsRoil extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); // Whenever a land enters the battlefield under your control, create a 2/2 green Elemental creature token. - Effect effect = new CreateTokenEffect(new ZendikarsRoilElementalToken()); - this.addAbility(new EntersBattlefieldControlledTriggeredAbility(effect, new FilterLandPermanent("a land"))); + this.addAbility(new LandfallAbility(new CreateTokenEffect(new ZendikarsRoilElementalToken()))); } private ZendikarsRoil(final ZendikarsRoil card) { diff --git a/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java b/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java index c12c6a2f8b1..45acd06a856 100644 --- a/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java +++ b/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java @@ -22,7 +22,6 @@ public class ConstellationAbility extends TriggeredAbilityImpl { public ConstellationAbility(Effect effect) { this(effect, false); - setAbilityWord(AbilityWord.CONSTELLATION); } public ConstellationAbility(Effect effect, boolean optional) { @@ -32,6 +31,7 @@ public class ConstellationAbility extends TriggeredAbilityImpl { public ConstellationAbility(Effect effect, boolean optional, boolean thisOr) { super(Zone.BATTLEFIELD, effect, optional); this.thisOr = thisOr; + setAbilityWord(AbilityWord.CONSTELLATION); } public ConstellationAbility(final ConstellationAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/abilityword/StriveAbility.java b/Mage/src/main/java/mage/abilities/abilityword/StriveAbility.java index 78b056226c9..0f63428318e 100644 --- a/Mage/src/main/java/mage/abilities/abilityword/StriveAbility.java +++ b/Mage/src/main/java/mage/abilities/abilityword/StriveAbility.java @@ -37,7 +37,8 @@ public class StriveAbility extends SimpleStaticAbility { @Override public String getRule() { - return new StringBuilder("this spell costs ").append(striveCost).append(" more to cast for each target beyond the first.").toString(); + return abilityWord.formatWord() + "This spell costs " + + striveCost + " more to cast for each target beyond the first."; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/IntimidateAbility.java b/Mage/src/main/java/mage/abilities/keyword/IntimidateAbility.java index 7b487f3a5db..0b4d247a84c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/IntimidateAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/IntimidateAbility.java @@ -31,7 +31,7 @@ public class IntimidateAbility extends EvasionAbility implements MageSingleton { @Override public String getRule() { - return "Intimidate"; + return "intimidate"; } @Override diff --git a/Mage/src/main/java/mage/game/permanent/token/EldraziHorrorToken.java b/Mage/src/main/java/mage/game/permanent/token/EldraziHorrorToken.java index 95f042f15be..4c541c96b36 100644 --- a/Mage/src/main/java/mage/game/permanent/token/EldraziHorrorToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/EldraziHorrorToken.java @@ -21,7 +21,7 @@ public final class EldraziHorrorToken extends TokenImpl { } public EldraziHorrorToken() { - super("Eldrazi Horror", "3/2 colorless Eldrazi Horror creature"); + super("Eldrazi Horror", "3/2 colorless Eldrazi Horror creature token"); cardType.add(CardType.CREATURE); subtype.add(SubType.ELDRAZI); subtype.add(SubType.HORROR); diff --git a/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java b/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java index 424cf239d9e..22094914661 100644 --- a/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java @@ -12,7 +12,7 @@ import mage.MageInt; public final class GrovetenderDruidsPlantToken extends TokenImpl { public GrovetenderDruidsPlantToken() { - super("Plant", "1/1 green Plant creature"); + super("Plant", "1/1 green Plant creature token"); cardType.add(CardType.CREATURE); color.setGreen(true); subtype.add(SubType.PLANT); diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json index 70e99295b88..34c54f991c9 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json +++ b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json @@ -1008,7 +1008,7 @@ "manaCost": "{1}{U}", "name": "Chart a Course", "number": "48", - "originalText": "Draw two cards. Then discard a card unless you attacked with a creature this turn.", + "originalText": "Draw two cards. Then discard a card unless you attacked this turn.", "originalType": "Sorcery", "printings": [ "JMP", From daed98fb0be21180100f5c3fb13897d27571d45a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 17 Jul 2021 19:41:25 +0400 Subject: [PATCH 24/48] Fixed missing clear code in combat (maybe related to #7997); --- Mage/src/main/java/mage/game/combat/Combat.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 871387e5f86..d29ef524ce5 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -178,6 +178,7 @@ public class Combat implements Serializable, Copyable { numberCreaturesDefenderAttackedBy.clear(); creaturesForcedToAttack.clear(); maxAttackers = Integer.MIN_VALUE; + attackersTappedByAttack.clear(); } public String getValue() { From f2ba76ca7564db2758ab48147e6e62bab2108d18 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 17 Jul 2021 19:43:05 +0400 Subject: [PATCH 25/48] Tests: fixed miss commands, fixed debug info for permanents on battlefield; --- .../test/java/org/mage/test/cards/continuous/LayerTests.java | 2 ++ Mage/src/main/java/mage/game/permanent/PermanentCard.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java index fd37221edf4..af0e70cef3a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/LayerTests.java @@ -221,8 +221,10 @@ public class LayerTests extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); + assertAllCommandsUsed(); // all lands are forests in addition to other types assertType("Plains", CardType.CREATURE, SubType.FOREST); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 6ce8e1062bf..ffa2cb18c4b 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -220,4 +220,9 @@ public class PermanentCard extends PermanentImpl { public Card getMainCard() { return card.getMainCard(); } + + @Override + public String toString() { + return card.toString(); + } } From 75393a148e3fee33e9dae82289e8aa09c893ef3e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 11:58:42 -0400 Subject: [PATCH 26/48] [MH2] fixed Sylvan Anthem boosting all creatures (fixes #8020) --- Mage.Sets/src/mage/cards/s/SylvanAnthem.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/s/SylvanAnthem.java b/Mage.Sets/src/mage/cards/s/SylvanAnthem.java index 730f293d6cd..b3c9b21f9e2 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanAnthem.java +++ b/Mage.Sets/src/mage/cards/s/SylvanAnthem.java @@ -4,6 +4,7 @@ import mage.ObjectColor; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -29,7 +30,7 @@ public final class SylvanAnthem extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{G}"); // Green creatures you control get +1/+1. - this.addAbility(new SimpleStaticAbility(new BoostAllEffect( + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( 1, 1, Duration.WhileOnBattlefield, filter, false ).setText("green creatures you control get +1/+1"))); From 9b5bb8d68f35df39742ec62ec59aa9f7bf327ca8 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 12:09:57 -0400 Subject: [PATCH 27/48] fixed compile error --- Mage.Sets/src/mage/cards/e/ExclusionRitual.java | 2 +- Mage.Sets/src/mage/cards/s/SylvanAnthem.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java index 57102524f93..c6ab1c28ee9 100644 --- a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java +++ b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java @@ -37,7 +37,7 @@ public final class ExclusionRitual extends CardImpl { // Imprint - When Exclusion Ritual enters the battlefield, exile target nonland permanent. Ability ability = new EntersBattlefieldTriggeredAbility(new ExclusionRitualImprintEffect(), false); ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT); + this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT)); // Players can't cast spells with the same name as the exiled card. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ExclusionRitualReplacementEffect())); } diff --git a/Mage.Sets/src/mage/cards/s/SylvanAnthem.java b/Mage.Sets/src/mage/cards/s/SylvanAnthem.java index b3c9b21f9e2..8953b252ebf 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanAnthem.java +++ b/Mage.Sets/src/mage/cards/s/SylvanAnthem.java @@ -3,7 +3,6 @@ package mage.cards.s; import mage.ObjectColor; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; From d13b55e772483f13fccdf56a0c35cd6c8c762990 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 14:18:44 -0400 Subject: [PATCH 28/48] [AFR] Implemented Monk Class --- Mage.Sets/src/mage/cards/m/MonkClass.java | 152 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 153 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MonkClass.java diff --git a/Mage.Sets/src/mage/cards/m/MonkClass.java b/Mage.Sets/src/mage/cards/m/MonkClass.java new file mode 100644 index 00000000000..b146bb421ca --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MonkClass.java @@ -0,0 +1,152 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.BecomesClassLevelTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.ClassLevelAbility; +import mage.abilities.keyword.ClassReminderAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MonkClass extends CardImpl { + + public MonkClass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}{U}"); + + this.subtype.add(SubType.CLASS); + + // (Gain the next level as a sorcery to add its ability.) + this.addAbility(new ClassReminderAbility()); + + // The second spell you cast each turn costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), + MonkClassCondition.instance, "the second spell you cast each turn costs {1} less to cast" + )), new SpellsCastWatcher()); + + // {W}{U}: Level 2 + this.addAbility(new ClassLevelAbility(2, "{W}{U}")); + + // When this Class becomes level 2, return up to one target nonland permanent to its owner's hand. + Ability ability = new BecomesClassLevelTriggeredAbility(new ReturnToHandTargetEffect(), 2); + ability.addTarget(new TargetNonlandPermanent(0, 1, false)); + this.addAbility(ability); + + // {1}{W}{U}: Level 3 + this.addAbility(new ClassLevelAbility(3, "{1}{W}{U}")); + + // At the beginning of your upkeep, exile the top card of your library. For as long as it remains exiled, it has "You may cast this card from exile as long as you've cast another spell this turn." + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new MonkClassEffect(), TargetController.YOU, false + )); + } + + private MonkClass(final MonkClass card) { + super(card); + } + + @Override + public MonkClass copy() { + return new MonkClass(this); + } +} + +enum MonkClassCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1; + } +} + +class MonkClassEffect extends OneShotEffect { + + MonkClassEffect() { + super(Outcome.Benefit); + staticText = "exile the top card of your library. For as long as it remains exiled, " + + "it has \"You may cast this card from exile as long as you've cast another spell this turn.\""; + } + + private MonkClassEffect(final MonkClassEffect effect) { + super(effect); + } + + @Override + public MonkClassEffect copy() { + return new MonkClassEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + game.addEffect(new GainAbilityTargetEffect( + new SimpleStaticAbility(new MonkClassCastEffect()), + Duration.Custom, null, true + ).setTargetPointer(new FixedTarget(card, game)), source); + return true; + } +} + +class MonkClassCastEffect extends AsThoughEffectImpl { + + MonkClassCastEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + staticText = "you may cast this card from exile as long as you've cast another spell this turn"; + } + + private MonkClassCastEffect(final MonkClassCastEffect effect) { + super(effect); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (!sourceId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId)) { + return false; + } + Card card = game.getCard(source.getSourceId()); + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return card != null && watcher != null + && watcher.getSpellsCastThisTurn(affectedControllerId).size() > 0; + } + + @Override + public MonkClassCastEffect copy() { + return new MonkClassCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index fa2713df9fa..42ac54f6478 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -166,6 +166,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Minimus Containment", 24, Rarity.COMMON, mage.cards.m.MinimusContainment.class)); cards.add(new SetCardInfo("Minion of the Mighty", 156, Rarity.RARE, mage.cards.m.MinionOfTheMighty.class)); cards.add(new SetCardInfo("Minsc, Beloved Ranger", 227, Rarity.MYTHIC, mage.cards.m.MinscBelovedRanger.class)); + cards.add(new SetCardInfo("Monk Class", 228, Rarity.RARE, mage.cards.m.MonkClass.class)); cards.add(new SetCardInfo("Monk of the Open Hand", 25, Rarity.UNCOMMON, mage.cards.m.MonkOfTheOpenHand.class)); cards.add(new SetCardInfo("Moon-Blessed Cleric", 26, Rarity.UNCOMMON, mage.cards.m.MoonBlessedCleric.class)); cards.add(new SetCardInfo("Mordenkainen", 64, Rarity.MYTHIC, mage.cards.m.Mordenkainen.class)); From 86afdc52b9e9c8f31273f2b1370a70338e1f1643 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 14:52:51 -0400 Subject: [PATCH 29/48] [AFR] Implemented Sorcerer Class --- Mage.Sets/src/mage/cards/m/MonkClass.java | 9 +- Mage.Sets/src/mage/cards/s/SorcererClass.java | 198 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/SorcererClass.java diff --git a/Mage.Sets/src/mage/cards/m/MonkClass.java b/Mage.Sets/src/mage/cards/m/MonkClass.java index b146bb421ca..b603c85c54e 100644 --- a/Mage.Sets/src/mage/cards/m/MonkClass.java +++ b/Mage.Sets/src/mage/cards/m/MonkClass.java @@ -10,6 +10,7 @@ import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; import mage.abilities.keyword.ClassLevelAbility; import mage.abilities.keyword.ClassReminderAbility; @@ -57,9 +58,11 @@ public final class MonkClass extends CardImpl { this.addAbility(new ClassLevelAbility(3, "{1}{W}{U}")); // At the beginning of your upkeep, exile the top card of your library. For as long as it remains exiled, it has "You may cast this card from exile as long as you've cast another spell this turn." - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new MonkClassEffect(), TargetController.YOU, false - )); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( + new BeginningOfUpkeepTriggeredAbility( + new MonkClassEffect(), TargetController.YOU, false + ), 3 + ))); } private MonkClass(final MonkClass card) { diff --git a/Mage.Sets/src/mage/cards/s/SorcererClass.java b/Mage.Sets/src/mage/cards/s/SorcererClass.java new file mode 100644 index 00000000000..960ac7e99a1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SorcererClass.java @@ -0,0 +1,198 @@ +package mage.cards.s; + +import mage.ConditionalMana; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.Cost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; +import mage.abilities.keyword.ClassLevelAbility; +import mage.abilities.keyword.ClassReminderAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ManaCondition; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SorcererClass extends CardImpl { + + public SorcererClass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}{R}"); + + this.subtype.add(SubType.CLASS); + + // (Gain the next level as a sorcery to add its ability.) + this.addAbility(new ClassReminderAbility()); + + // When Sorcerer Class enters the battlefield, draw two cards, then discard two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DrawDiscardControllerEffect(2, 2) + )); + + // {U}{R}: Level 2 + this.addAbility(new ClassLevelAbility(2, "{U}{R}")); + + // Creatures you control have "{T}: Add {U} or {R}. Spend this mana only to cast an instant or sorcery spell or to gain a Class level." + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + new ConditionalColoredManaAbility(Mana.BlueMana(1), new SorcererClassManaBuilder()), + Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("creatures you control have \"{T}: Add {U} or {R}.")); + ability.addEffect(new GainAbilityControlledEffect( + new ConditionalColoredManaAbility(Mana.RedMana(1), new SorcererClassManaBuilder()), + Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("Spend this mana only to cast an instant or sorcery spell or to gain a Class level.\"")); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 2))); + + // {3}{U}{R}: Level 3 + this.addAbility(new ClassLevelAbility(3, "{3}{U}{R}")); + + // Whenever you cast an instant or sorcery spell, that spell deals damage to each opponent equal to the number of instant or sorcery spells you've cast this turn. + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( + new SpellCastControllerTriggeredAbility( + new SorcererClassEffect(), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, + false, true + ), 3 + )), new SorcererClassWatcher()); + } + + private SorcererClass(final SorcererClass card) { + super(card); + } + + @Override + public SorcererClass copy() { + return new SorcererClass(this); + } +} + +class SorcererClassManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new SorcererClassConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast an instant or sorcery spell or to gain a Class level"; + } +} + +class SorcererClassConditionalMana extends ConditionalMana { + + public SorcererClassConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast an instant or sorcery spell or to gain a Class level"; + addCondition(new SorcererClassManaCondition()); + } +} + +class SorcererClassManaCondition extends ManaCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + if (source instanceof SpellAbility) { + MageObject object = game.getObject(source.getSourceId()); + return object != null && object.isInstantOrSorcery(game); + } + return source instanceof ClassLevelAbility; + } + + @Override + public boolean apply(Game game, Ability source, UUID originalId, Cost costsToPay) { + return apply(game, source); + } +} + +class SorcererClassEffect extends OneShotEffect { + + SorcererClassEffect() { + super(Outcome.Benefit); + staticText = "that spell deals damage to each opponent equal " + + "to the number of instant or sorcery spells you've cast this turn"; + } + + private SorcererClassEffect(final SorcererClassEffect effect) { + super(effect); + } + + @Override + public SorcererClassEffect copy() { + return new SorcererClassEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = (Spell) getValue("spellCast"); + if (spell == null) { + return false; + } + int count = SorcererClassWatcher.spellCount(source.getControllerId(), game); + if (count < 1) { + return false; + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + player.damage(count, spell.getId(), source, game); + } + return true; + } +} + +class SorcererClassWatcher extends Watcher { + + private final Map spellMap = new HashMap<>(); + + SorcererClassWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null || !spell.isInstantOrSorcery(game)) { + return; + } + spellMap.compute(spell.getControllerId(), (u, i) -> i == null ? 1 : Integer.sum(i, 1)); + } + + @Override + public void reset() { + spellMap.clear(); + super.reset(); + } + + static int spellCount(UUID playerId, Game game) { + SorcererClassWatcher watcher = game.getState().getWatcher(SorcererClassWatcher.class); + return watcher != null ? watcher.spellMap.getOrDefault(playerId, 0) : 0; + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 42ac54f6478..63184045b59 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -217,6 +217,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Silver Raven", 74, Rarity.COMMON, mage.cards.s.SilverRaven.class)); cards.add(new SetCardInfo("Skeletal Swarming", 232, Rarity.RARE, mage.cards.s.SkeletalSwarming.class)); cards.add(new SetCardInfo("Skullport Merchant", 120, Rarity.UNCOMMON, mage.cards.s.SkullportMerchant.class)); + cards.add(new SetCardInfo("Sorcerer Class", 233, Rarity.RARE, mage.cards.s.SorcererClass.class)); cards.add(new SetCardInfo("Soulknife Spy", 75, Rarity.COMMON, mage.cards.s.SoulknifeSpy.class)); cards.add(new SetCardInfo("Spare Dagger", 250, Rarity.COMMON, mage.cards.s.SpareDagger.class)); cards.add(new SetCardInfo("Sphere of Annihilation", 121, Rarity.RARE, mage.cards.s.SphereOfAnnihilation.class)); From 1584bc958a4e664dece20f946ff744c97c25986c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 17:56:24 -0400 Subject: [PATCH 30/48] [AFR] fixed missing predicate in Volo, Guide to Monsters and also slightly reworked card --- .../src/mage/cards/v/VoloGuideToMonsters.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java b/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java index c86ac91d54f..93a6ede3e28 100644 --- a/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java +++ b/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java @@ -1,7 +1,6 @@ package mage.cards.v; import mage.MageInt; -import mage.MageObject; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.cards.CardImpl; @@ -12,9 +11,9 @@ import mage.constants.SuperType; import mage.filter.FilterSpell; import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureSpell; -import mage.filter.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicate; import mage.game.Game; +import mage.game.stack.StackObject; import mage.players.Player; import mage.util.CardUtil; @@ -30,6 +29,10 @@ public final class VoloGuideToMonsters extends CardImpl { "with a creature you control or a creature card in your graveyard" ); + static { + filter.add(VoloGuideToMonstersPredicate.instance); + } + public VoloGuideToMonsters(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}"); @@ -57,24 +60,24 @@ public final class VoloGuideToMonsters extends CardImpl { } } -enum VoloGuideToMonstersPredicate implements ObjectSourcePlayerPredicate> { +enum VoloGuideToMonstersPredicate implements Predicate { instance; @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - Player player = game.getPlayer(input.getPlayerId()); + public boolean apply(StackObject input, Game game) { + Player player = game.getPlayer(input.getControllerId()); if (player != null && player .getGraveyard() .getCards(StaticFilters.FILTER_CARD_CREATURE, game) .stream() - .anyMatch(card -> CardUtil.haveSameNames(card, input.getObject()))) { + .anyMatch(card -> CardUtil.haveSameNames(card, input))) { return false; } return game .getBattlefield() - .getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, input.getPlayerId(), game) + .getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, input.getControllerId(), game) .stream() - .noneMatch(permanent -> CardUtil.haveSameNames(permanent, input.getObject())); + .noneMatch(permanent -> CardUtil.haveSameNames(permanent, input)); } -} \ No newline at end of file +} From 0200813db9fb72d8407413f50c80f6a1ac7098ee Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 17 Jul 2021 21:13:00 -0400 Subject: [PATCH 31/48] [AFR] Implemented Rogue Class --- Mage.Sets/src/mage/cards/r/RogueClass.java | 214 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + 2 files changed, 215 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RogueClass.java diff --git a/Mage.Sets/src/mage/cards/r/RogueClass.java b/Mage.Sets/src/mage/cards/r/RogueClass.java new file mode 100644 index 00000000000..5211d568e68 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RogueClass.java @@ -0,0 +1,214 @@ +package mage.cards.r; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; +import mage.abilities.keyword.ClassLevelAbility; +import mage.abilities.keyword.ClassReminderAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RogueClass extends CardImpl { + + public RogueClass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}{B}"); + + this.subtype.add(SubType.CLASS); + + // (Gain the next level as a sorcery to add its ability.) + this.addAbility(new ClassReminderAbility()); + + // Whenever a creature you controls deals combat damage to a player, exile the top card of that player's library face down. You may look at it for as long as it remains exiled. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new RogueClassExileEffect(), StaticFilters.FILTER_CONTROLLED_A_CREATURE, + false, SetTargetPointer.PLAYER, true, true + )); + + // {1}{U}{B}: Level 2 + this.addAbility(new ClassLevelAbility(2, "{1}{U}{B}")); + + // Creatures you control have menace. + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( + new GainAbilityControlledEffect( + new MenaceAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_CREATURES + ), 2 + ))); + + // {2}{U}{B}: Level 3 + this.addAbility(new ClassLevelAbility(3, "{2}{U}{B}")); + + // You may play cards exiled with Rogue Class, and you may spend mana as through it were mana of any color to cast them. + Ability ability = new SimpleStaticAbility(new RogueClassPlayEffect()); + ability.addEffect(new RogueClassManaEffect()); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3))); + } + + private RogueClass(final RogueClass card) { + super(card); + } + + @Override + public RogueClass copy() { + return new RogueClass(this); + } +} + +class RogueClassExileEffect extends OneShotEffect { + + RogueClassExileEffect() { + super(Outcome.Benefit); + staticText = "exile the top card of that player's library face down. " + + "You may look at it for as long as it remains exiled"; + } + + private RogueClassExileEffect(final RogueClassExileEffect effect) { + super(effect); + } + + @Override + public RogueClassExileEffect copy() { + return new RogueClassExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null || opponent == null) { + return false; + } + Card card = opponent.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + MageObject sourceObject = source.getSourcePermanentOrLKI(game); + controller.moveCardsToExile( + card, source, game, false, + CardUtil.getExileZoneId(game, source), + sourceObject != null ? sourceObject.getName() : null + ); + card.setFaceDown(true, game); + game.addEffect(new RogueClassLookEffect().setTargetPointer(new FixedTarget(card, game)), source); + return true; + } +} + +class RogueClassLookEffect extends AsThoughEffectImpl { + + RogueClassLookEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + } + + private RogueClassLookEffect(final RogueClassLookEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public RogueClassLookEffect copy() { + return new RogueClassLookEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + discard(); + return false; + } + return source.isControlledBy(affectedControllerId) + && cardId.equals(objectId); + } +} + +class RogueClassPlayEffect extends AsThoughEffectImpl { + + RogueClassPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "you may play cards exiled with {this}"; + } + + private RogueClassPlayEffect(final RogueClassPlayEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public RogueClassPlayEffect copy() { + return new RogueClassPlayEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + ExileZone exileZone = game.getState().getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + return exileZone != null && exileZone.contains(CardUtil.getMainCardId(game, objectId)); + } +} + +class RogueClassManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + RogueClassManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.WhileOnBattlefield, Outcome.Benefit); + this.staticText = ", and you may spend mana as through it were mana of any color to cast them"; + } + + private RogueClassManaEffect(final RogueClassManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public RogueClassManaEffect copy() { + return new RogueClassManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + ExileZone exileZone = game.getState().getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + return source.isControlledBy(affectedControllerId) + && exileZone != null + && exileZone.contains(CardUtil.getMainCardId(game, objectId)); + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 63184045b59..29d2e93a519 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -205,6 +205,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Reaper's Talisman", 117, Rarity.UNCOMMON, mage.cards.r.ReapersTalisman.class)); cards.add(new SetCardInfo("Red Dragon", 160, Rarity.UNCOMMON, mage.cards.r.RedDragon.class)); cards.add(new SetCardInfo("Rimeshield Frost Giant", 69, Rarity.COMMON, mage.cards.r.RimeshieldFrostGiant.class)); + cards.add(new SetCardInfo("Rogue Class", 230, Rarity.RARE, mage.cards.r.RogueClass.class)); cards.add(new SetCardInfo("Rust Monster", 161, Rarity.UNCOMMON, mage.cards.r.RustMonster.class)); cards.add(new SetCardInfo("Scaled Herbalist", 204, Rarity.COMMON, mage.cards.s.ScaledHerbalist.class)); cards.add(new SetCardInfo("Scion of Stygia", 70, Rarity.COMMON, mage.cards.s.ScionOfStygia.class)); From d4ef2ec414db5385c374755ff2f8caa13361d2ad Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 18 Jul 2021 10:46:05 -0400 Subject: [PATCH 32/48] [AFC] Implemented Midnight Pathlighter --- .../src/mage/cards/m/MidnightPathlighter.java | 58 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + .../CantBeBlockedByCreaturesAllEffect.java | 6 +- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/m/MidnightPathlighter.java diff --git a/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java new file mode 100644 index 00000000000..a4d2c8773ad --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java @@ -0,0 +1,58 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAllEffect; +import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MidnightPathlighter extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("except by legendary creatures"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public MidnightPathlighter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Creatures you control can't be blocked except by legendary creatures. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByCreaturesAllEffect( + StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED, + filter, Duration.WhileOnBattlefield + ))); + + // Whenever one or more creatures you control deal combat damage to a player, venture into the dungeon. + this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(new VentureIntoTheDungeonEffect())); + } + + private MidnightPathlighter(final MidnightPathlighter card) { + super(card); + } + + @Override + public MidnightPathlighter copy() { + return new MidnightPathlighter(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 9e72b29c288..e1a656faf86 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -138,6 +138,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Masterwork of Ingenuity", 209, Rarity.RARE, mage.cards.m.MasterworkOfIngenuity.class)); cards.add(new SetCardInfo("Merfolk Looter", 86, Rarity.UNCOMMON, mage.cards.m.MerfolkLooter.class)); cards.add(new SetCardInfo("Meteor Golem", 210, Rarity.UNCOMMON, mage.cards.m.MeteorGolem.class)); + cards.add(new SetCardInfo("Midnight Pathlighter", 52, Rarity.RARE, mage.cards.m.MidnightPathlighter.class)); cards.add(new SetCardInfo("Mind Stone", 211, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); cards.add(new SetCardInfo("Mishra's Factory", 248, Rarity.UNCOMMON, mage.cards.m.MishrasFactory.class)); cards.add(new SetCardInfo("Moonsilver Spear", 212, Rarity.RARE, mage.cards.m.MoonsilverSpear.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBeBlockedByCreaturesAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBeBlockedByCreaturesAllEffect.java index bb0bbc9ffbb..51a6dc95308 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBeBlockedByCreaturesAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBeBlockedByCreaturesAllEffect.java @@ -10,7 +10,6 @@ import mage.game.permanent.Permanent; /** * @author LevelX2 */ - public class CantBeBlockedByCreaturesAllEffect extends RestrictionEffect { private final FilterCreaturePermanent filterBlockedBy; @@ -20,8 +19,9 @@ public class CantBeBlockedByCreaturesAllEffect extends RestrictionEffect { super(duration); this.filterCreatures = filterCreatures; this.filterBlockedBy = filterBlockedBy; - staticText = new StringBuilder(filterCreatures.getMessage()).append(" can't be blocked ") - .append(filterBlockedBy.getMessage().startsWith("except by") ? "" : "by ").append(filterBlockedBy.getMessage()).toString(); + staticText = filterCreatures.getMessage() + " can't be blocked " + + (filterBlockedBy.getMessage().startsWith("except by") ? "" : "by ") + + filterBlockedBy.getMessage(); } public CantBeBlockedByCreaturesAllEffect(final CantBeBlockedByCreaturesAllEffect effect) { From 9ce81dca3af2faacd3641a2c3acad20c5a265632 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 18 Jul 2021 11:02:09 -0400 Subject: [PATCH 33/48] [AFC] Implemented Prosper, Tome-Bound --- .../src/mage/cards/p/ProsperTomeBound.java | 87 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + .../ExileTopXMayPlayUntilEndOfTurnEffect.java | 42 +++++---- 3 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/p/ProsperTomeBound.java diff --git a/Mage.Sets/src/mage/cards/p/ProsperTomeBound.java b/Mage.Sets/src/mage/cards/p/ProsperTomeBound.java new file mode 100644 index 00000000000..30bb123887e --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProsperTomeBound.java @@ -0,0 +1,87 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEndOfTurnEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ProsperTomeBound extends CardImpl { + + public ProsperTomeBound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.TIEFLING); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Mystic Arcanum — At the beginning of your end step, exile the top card of your library. Until the end of your next turn, you may play that card. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new ExileTopXMayPlayUntilEndOfTurnEffect( + 1, false, Duration.UntilEndOfYourNextTurn + ), TargetController.YOU, false + ).withFlavorWord("Mystic Arcanum")); + + // Pact Boon — Whenever you play a card from exile, create a Treasure token. + this.addAbility(new ProsperTomeBoundTriggeredAbility()); + } + + private ProsperTomeBound(final ProsperTomeBound card) { + super(card); + } + + @Override + public ProsperTomeBound copy() { + return new ProsperTomeBound(this); + } +} + +class ProsperTomeBoundTriggeredAbility extends TriggeredAbilityImpl { + + ProsperTomeBoundTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new TreasureToken())); + this.flavorWord = "Pact Boon"; + } + + private ProsperTomeBoundTriggeredAbility(final ProsperTomeBoundTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST + || event.getType() == GameEvent.EventType.LAND_PLAYED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()) && event.getZone() == Zone.EXILED; + } + + @Override + public ProsperTomeBoundTriggeredAbility copy() { + return new ProsperTomeBoundTriggeredAbility(this); + } + + @Override + public String getTriggerPhrase() { + return "Whenever you play a card from exile, "; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index e1a656faf86..0c90ab43882 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -168,6 +168,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Prairie Stream", 256, Rarity.RARE, mage.cards.p.PrairieStream.class)); cards.add(new SetCardInfo("Prognostic Sphinx", 90, Rarity.RARE, mage.cards.p.PrognosticSphinx.class)); cards.add(new SetCardInfo("Propaganda", 91, Rarity.UNCOMMON, mage.cards.p.Propaganda.class)); + cards.add(new SetCardInfo("Prosper, Tome-Bound", 2, Rarity.MYTHIC, mage.cards.p.ProsperTomeBound.class)); cards.add(new SetCardInfo("Psychic Impetus", 92, Rarity.UNCOMMON, mage.cards.p.PsychicImpetus.class)); cards.add(new SetCardInfo("Puresteel Paladin", 69, Rarity.RARE, mage.cards.p.PuresteelPaladin.class)); cards.add(new SetCardInfo("Radiant Solar", 9, Rarity.RARE, mage.cards.r.RadiantSolar.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java index 02696d2bc47..2f7bf62e9f9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java @@ -3,7 +3,6 @@ package mage.abilities.effects.common; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.cards.Card; @@ -21,21 +20,28 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect { private final int amount; private final boolean showHint; + private final Duration duration; public ExileTopXMayPlayUntilEndOfTurnEffect(int amount) { this(amount, false); } public ExileTopXMayPlayUntilEndOfTurnEffect(int amount, boolean showHint) { + this(amount, showHint, Duration.EndOfTurn); + } + + public ExileTopXMayPlayUntilEndOfTurnEffect(int amount, boolean showHint, Duration duration) { super(Outcome.Benefit); this.amount = amount; this.showHint = showHint; + this.duration = duration; } private ExileTopXMayPlayUntilEndOfTurnEffect(final ExileTopXMayPlayUntilEndOfTurnEffect effect) { super(effect); this.amount = effect.amount; this.showHint = effect.showHint; + this.duration = effect.duration; } @Override @@ -47,21 +53,21 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); - if (controller != null && sourceObject != null) { - Set cards = controller.getLibrary().getTopCards(game, amount); - if (!cards.isEmpty()) { - controller.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getIdName()); - // remove cards that could not be moved to exile - cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId()))); - if (!cards.isEmpty()) { - ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn); - effect.setTargetPointer(new FixedTargets(cards, game)); - game.addEffect(effect, source); - } - } + if (controller == null || sourceObject == null) { + return false; + } + Set cards = controller.getLibrary().getTopCards(game, amount); + if (cards.isEmpty()) { return true; } - return false; + controller.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getIdName()); + // remove cards that could not be moved to exile + cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId()))); + if (!cards.isEmpty()) { + game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, duration) + .setTargetPointer(new FixedTargets(cards, game)), source); + } + return true; } @Override @@ -71,11 +77,15 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect { } StringBuilder sb = new StringBuilder(); if (amount == 1) { - sb.append("exile the top card of your library. You may play that card this turn"); + sb.append("exile the top card of your library. "); + sb.append(CardUtil.getTextWithFirstCharUpperCase(duration.toString())); + sb.append(", you may play that card"); } else { sb.append("exile the top "); sb.append(CardUtil.numberToText(amount)); - sb.append(" cards of your library. Until end of turn, you may play cards exiled this way"); + sb.append(" cards of your library. "); + sb.append(CardUtil.getTextWithFirstCharUpperCase(duration.toString())); + sb.append(", you may play cards exiled this way"); } if (showHint) { sb.append(". (You still pay its costs. You can play a land this way only if you have an available land play remaining.)"); From 0d739baa63cb5f815b7016784a56db7c301d914d Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 18 Jul 2021 11:07:48 -0400 Subject: [PATCH 34/48] [AFC] Implemented Storvard, Frost Giant Jarl --- .../mage/cards/s/StorvaldFrostGiantJarl.java | 68 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 69 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StorvaldFrostGiantJarl.java diff --git a/Mage.Sets/src/mage/cards/s/StorvaldFrostGiantJarl.java b/Mage.Sets/src/mage/cards/s/StorvaldFrostGiantJarl.java new file mode 100644 index 00000000000..77bdc884129 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StorvaldFrostGiantJarl.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessTargetEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StorvaldFrostGiantJarl extends CardImpl { + + public StorvaldFrostGiantJarl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{W}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.GIANT); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Ward {3} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{3}"))); + + // Other creatures you control have ward {3}. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new WardAbility(new GenericManaCost(3)), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_CREATURES, true + ))); + + // Whenever Storvald, Frost Giant Jarl enters the battlefield or attacks, choose one or both — + // • Target creature has base power and toughness 7/7 until end of turn. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new SetPowerToughnessTargetEffect(7, 7, Duration.EndOfTurn) + ); + ability.addTarget(new TargetCreaturePermanent()); + + // • Target creature has base power and toughness 1/1 until end of turn. + Mode mode = new Mode(new SetPowerToughnessTargetEffect(1, 1, Duration.EndOfTurn)); + mode.addTarget(new TargetCreaturePermanent()); + ability.addMode(mode); + this.addAbility(ability); + } + + private StorvaldFrostGiantJarl(final StorvaldFrostGiantJarl card) { + super(card); + } + + @Override + public StorvaldFrostGiantJarl copy() { + return new StorvaldFrostGiantJarl(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 0c90ab43882..14ecf66b1ab 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -208,6 +208,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Spinerock Knoll", 263, Rarity.RARE, mage.cards.s.SpinerockKnoll.class)); cards.add(new SetCardInfo("Spit Flame", 142, Rarity.RARE, mage.cards.s.SpitFlame.class)); cards.add(new SetCardInfo("Sram, Senior Edificer", 72, Rarity.RARE, mage.cards.s.SramSeniorEdificer.class)); + cards.add(new SetCardInfo("Storvald, Frost Giant Jarl", 55, Rarity.MYTHIC, mage.cards.s.StorvaldFrostGiantJarl.class)); cards.add(new SetCardInfo("Sun Titan", 73, Rarity.MYTHIC, mage.cards.s.SunTitan.class)); cards.add(new SetCardInfo("Sunblast Angel", 74, Rarity.RARE, mage.cards.s.SunblastAngel.class)); cards.add(new SetCardInfo("Sungrass Prairie", 264, Rarity.RARE, mage.cards.s.SungrassPrairie.class)); From 8981bb8ad096f27a15f59ca0c90536de430b7830 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 18 Jul 2021 11:16:07 -0400 Subject: [PATCH 35/48] [AFC] Implemented Underdark Rift --- Mage.Sets/src/mage/cards/u/UnderdarkRift.java | 90 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 91 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/u/UnderdarkRift.java diff --git a/Mage.Sets/src/mage/cards/u/UnderdarkRift.java b/Mage.Sets/src/mage/cards/u/UnderdarkRift.java new file mode 100644 index 00000000000..ef8ac9232e8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnderdarkRift.java @@ -0,0 +1,90 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnderdarkRift extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact, creature, or planeswalker"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate() + )); + } + + public UnderdarkRift(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {5}, {T}, Exile Underdark Rift: Roll a d10. Put a target artifact, creature, or planeswalker into its owner's library just beneath the top X cards of that library, where X is the result. Activate only has a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new UnderdarkRiftEffect(), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private UnderdarkRift(final UnderdarkRift card) { + super(card); + } + + @Override + public UnderdarkRift copy() { + return new UnderdarkRift(this); + } +} + +class UnderdarkRiftEffect extends OneShotEffect { + + UnderdarkRiftEffect() { + super(Outcome.Benefit); + staticText = "roll a d10. Put a target artifact, creature, or planeswalker into its owner's library " + + "just beneath the top X cards of that library, where X is the result"; + } + + private UnderdarkRiftEffect(final UnderdarkRiftEffect effect) { + super(effect); + } + + @Override + public UnderdarkRiftEffect copy() { + return new UnderdarkRiftEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (player == null || permanent == null) { + return false; + } + int result = player.rollDice(source, game, 10); + player.putCardOnTopXOfLibrary(permanent, game, source, result + 1, true); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 14ecf66b1ab..cdc992aecce 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -232,6 +232,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Throes of Chaos", 146, Rarity.UNCOMMON, mage.cards.t.ThroesOfChaos.class)); cards.add(new SetCardInfo("Thunderbreak Regent", 147, Rarity.RARE, mage.cards.t.ThunderbreakRegent.class)); cards.add(new SetCardInfo("Unburial Rites", 111, Rarity.UNCOMMON, mage.cards.u.UnburialRites.class)); + cards.add(new SetCardInfo("Underdark Rift", 62, Rarity.UNCOMMON, mage.cards.u.UnderdarkRift.class)); cards.add(new SetCardInfo("Unstable Obelisk", 220, Rarity.UNCOMMON, mage.cards.u.UnstableObelisk.class)); cards.add(new SetCardInfo("Utopia Sprawl", 172, Rarity.UNCOMMON, mage.cards.u.UtopiaSprawl.class)); cards.add(new SetCardInfo("Utter End", 195, Rarity.RARE, mage.cards.u.UtterEnd.class)); From c0b4790c4dbb778af0a07b76bc97855beb940bb5 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 18 Jul 2021 11:41:00 -0400 Subject: [PATCH 36/48] [AFC] Implemented Grim Hireling --- Mage.Sets/src/mage/cards/g/GrimHireling.java | 85 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 86 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GrimHireling.java diff --git a/Mage.Sets/src/mage/cards/g/GrimHireling.java b/Mage.Sets/src/mage/cards/g/GrimHireling.java new file mode 100644 index 00000000000..0df709311e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrimHireling.java @@ -0,0 +1,85 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility; +import mage.abilities.costs.common.SacrificeXTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GrimHireling extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent(SubType.TREASURE, "Treasures"); + + public GrimHireling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.TIEFLING); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever one or more creatures you control deal combat damage to a player, create two Treasure tokens. + this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility( + new CreateTokenEffect(new TreasureToken(), 2) + )); + + // {B}, Sacrifice X Treasures: Target creature gets -X/-X until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new GrimHirelingEffect(), new ManaCostsImpl<>("{B}")); + ability.addCost(new SacrificeXTargetCost(filter)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private GrimHireling(final GrimHireling card) { + super(card); + } + + @Override + public GrimHireling copy() { + return new GrimHireling(this); + } +} + +class GrimHirelingEffect extends OneShotEffect { + + GrimHirelingEffect() { + super(Outcome.Benefit); + staticText = "target creature gets -X/-X until end of turn"; + } + + private GrimHirelingEffect(final GrimHirelingEffect effect) { + super(effect); + } + + @Override + public GrimHirelingEffect copy() { + return new GrimHirelingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = GetXValue.instance.calculate(game, source, this); + game.addEffect(new BoostTargetEffect(-xValue, -xValue), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index cdc992aecce..9eb47ee64cb 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -111,6 +111,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Grasslands", 242, Rarity.UNCOMMON, mage.cards.g.Grasslands.class)); cards.add(new SetCardInfo("Gratuitous Violence", 127, Rarity.RARE, mage.cards.g.GratuitousViolence.class)); cards.add(new SetCardInfo("Greater Good", 160, Rarity.RARE, mage.cards.g.GreaterGood.class)); + cards.add(new SetCardInfo("Grim Hireling", 25, Rarity.RARE, mage.cards.g.GrimHireling.class)); cards.add(new SetCardInfo("Gruul Signet", 207, Rarity.UNCOMMON, mage.cards.g.GruulSignet.class)); cards.add(new SetCardInfo("Gruul Turf", 243, Rarity.UNCOMMON, mage.cards.g.GruulTurf.class)); cards.add(new SetCardInfo("Gryff's Boon", 67, Rarity.UNCOMMON, mage.cards.g.GryffsBoon.class)); From 12aa3b82b8d992a4009fde54536c2cd465a1578c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 17 Jul 2021 20:15:21 +0400 Subject: [PATCH 37/48] * GUI: added card icon for face down permanents; --- .../icons/original/bootstrap/reply-fill.svg | 3 ++ .../card/icons/prepared/reply-fill.svg | 7 +++++ .../src/main/java/mage/view/CardView.java | 7 +++++ .../mage/abilities/icon/CardIconCategory.java | 2 ++ .../mage/abilities/icon/CardIconType.java | 2 ++ .../icon/other/FaceDownStatusIcon.java | 31 +++++++++++++++++++ 6 files changed, 52 insertions(+) create mode 100644 Mage.Client/src/main/resources/card/icons/original/bootstrap/reply-fill.svg create mode 100644 Mage.Client/src/main/resources/card/icons/prepared/reply-fill.svg create mode 100644 Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java diff --git a/Mage.Client/src/main/resources/card/icons/original/bootstrap/reply-fill.svg b/Mage.Client/src/main/resources/card/icons/original/bootstrap/reply-fill.svg new file mode 100644 index 00000000000..b5a87228d89 --- /dev/null +++ b/Mage.Client/src/main/resources/card/icons/original/bootstrap/reply-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Mage.Client/src/main/resources/card/icons/prepared/reply-fill.svg b/Mage.Client/src/main/resources/card/icons/prepared/reply-fill.svg new file mode 100644 index 00000000000..daa1b3d23e0 --- /dev/null +++ b/Mage.Client/src/main/resources/card/icons/prepared/reply-fill.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index c76b68a24db..b4347a29094 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -10,6 +10,7 @@ import mage.abilities.SpellAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.icon.CardIcon; +import mage.abilities.icon.other.FaceDownStatusIcon; import mage.abilities.keyword.AftermathAbility; import mage.cards.*; import mage.cards.mock.MockCard; @@ -415,9 +416,15 @@ public class CardView extends SimpleCardView { } // card icons for permanents on battlefield + // abilities permanent.getAbilities(game).forEach(ability -> { this.cardIcons.addAll(ability.getIcons(game)); }); + // other + if (permanent.isFaceDown(game)) { + this.cardIcons.add(FaceDownStatusIcon.instance); + } + } else { if (card.isCopy()) { this.mageObjectType = MageObjectType.COPY_CARD; diff --git a/Mage/src/main/java/mage/abilities/icon/CardIconCategory.java b/Mage/src/main/java/mage/abilities/icon/CardIconCategory.java index fa1f4ad2f52..0f35c5f0d05 100644 --- a/Mage/src/main/java/mage/abilities/icon/CardIconCategory.java +++ b/Mage/src/main/java/mage/abilities/icon/CardIconCategory.java @@ -1,6 +1,8 @@ package mage.abilities.icon; /** + * For GUI: different icons category can go to different position/panels on the card + * * @author JayDi85 */ public enum CardIconCategory { diff --git a/Mage/src/main/java/mage/abilities/icon/CardIconType.java b/Mage/src/main/java/mage/abilities/icon/CardIconType.java index 42e3b0160ea..483ebf5f911 100644 --- a/Mage/src/main/java/mage/abilities/icon/CardIconType.java +++ b/Mage/src/main/java/mage/abilities/icon/CardIconType.java @@ -31,6 +31,8 @@ public enum CardIconType { ABILITY_VIGILANCE("prepared/eye.svg", CardIconCategory.ABILITY, 100), ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100), // + OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100), + // SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog diff --git a/Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java b/Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java new file mode 100644 index 00000000000..e6f0d6a2eed --- /dev/null +++ b/Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java @@ -0,0 +1,31 @@ +package mage.abilities.icon.other; + +import mage.abilities.icon.CardIcon; +import mage.abilities.icon.CardIconType; + +/** + * @author JayDi85 + */ +public enum FaceDownStatusIcon implements CardIcon { + instance; + + @Override + public CardIconType getIconType() { + return CardIconType.OTHER_FACEDOWN; + } + + @Override + public String getText() { + return ""; + } + + @Override + public String getHint() { + return "Card is face down"; + } + + @Override + public CardIcon copy() { + return instance; + } +} From 26fea5d07b8dab067aae1261e68fe2bfbcf55acb Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 18 Jul 2021 21:17:35 +0400 Subject: [PATCH 38/48] * Dreadhorde Arcanist - fixed wrong target cards for trigger; --- Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java index 5c1f0edb6df..8ba2508b57d 100644 --- a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java +++ b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java @@ -76,7 +76,7 @@ enum DreadhordeArcanistPredicate implements ObjectSourcePlayerPredicate input, Game game) { Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(input.getSourceId()); return sourcePermanent != null - & input.getObject().getManaValue() <= sourcePermanent.getPower().getValue(); + && input.getObject().getManaValue() <= sourcePermanent.getPower().getValue(); } } From fc0ff6c22d82be2ea0bc3d3cee73d5d568efdcc4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 19 Jul 2021 13:07:09 +0400 Subject: [PATCH 39/48] Test framework: added support to test client side data in unit tests (getGameView -> CardView, etc); --- .../mage/server/game/GameSessionPlayer.java | 22 ++++++++++++++----- .../mage/server/game/GameSessionWatcher.java | 4 ++-- .../java/mage/server/util/SystemUtil.java | 20 ++++++++++++----- .../serverside/base/MageTestPlayerBase.java | 3 +++ .../base/impl/CardTestPlayerAPIImpl.java | 17 ++++++++++---- .../performance/SerializationTest.java | 9 ++++---- Mage/src/main/java/mage/game/GameImpl.java | 6 +++-- 7 files changed, 59 insertions(+), 22 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index 0939c26eb6a..dfa6bf947fa 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -11,8 +11,8 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.Player; import mage.server.User; -import mage.server.managers.UserManager; import mage.server.managers.ManagerFactory; +import mage.server.managers.UserManager; import mage.view.*; import org.apache.log4j.Logger; @@ -195,6 +195,18 @@ public class GameSessionPlayer extends GameSessionWatcher { @Override public GameView getGameView() { + return prepareGameView(game, playerId, userId); + } + + /** + * Prepare client-server data. Can be used in real games or in unit tests + * + * @param game + * @param playerId + * @param userId can be null for tests + * @return + */ + public static GameView prepareGameView(Game game, UUID playerId, UUID userId) { Player player = game.getPlayer(playerId); GameView gameView = new GameView(game.getState(), game, playerId, null); gameView.setHand(new CardsView(game, player.getHand().getCards(game))); @@ -202,8 +214,8 @@ public class GameSessionPlayer extends GameSessionWatcher { gameView.setCanPlayObjects(player.getPlayableObjects(game, Zone.ALL)); } - processControlledPlayers(player, gameView); - processWatchedHands(userId, gameView); + processControlledPlayers(game, player, gameView); + processWatchedHands(game, userId, gameView); //TODO: should player who controls another player's turn be able to look at all these cards? List list = new ArrayList<>(); @@ -215,7 +227,7 @@ public class GameSessionPlayer extends GameSessionWatcher { return gameView; } - private void processControlledPlayers(Player player, GameView gameView) { + private static void processControlledPlayers(Game game, Player player, GameView gameView) { if (!player.getPlayersUnderYourControl().isEmpty()) { Map handCards = new HashMap<>(); for (UUID controlledPlayerId : player.getPlayersUnderYourControl()) { @@ -258,7 +270,7 @@ public class GameSessionPlayer extends GameSessionWatcher { if (ex.getCause() != null) { logger.debug("- Cause: " + (ex.getCause().getMessage() == null ? "null" : ex.getCause().getMessage()), ex); } else { - logger.debug("- ex: " + ex.toString(), ex); + logger.debug("- ex: " + ex, ex); } } else { logger.fatal("Game session game quit exception - null gameId:" + game.getId() + " playerId: " + playerId); diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index 6aafddbf672..4bcd69d57c9 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -99,11 +99,11 @@ public class GameSessionWatcher { public GameView getGameView() { GameView gameView = new GameView(game.getState(), game, null, userId); - processWatchedHands(userId, gameView); + processWatchedHands(game, userId, gameView); return gameView; } - protected void processWatchedHands(UUID userId, GameView gameView) { + protected static void processWatchedHands(Game game, UUID userId, GameView gameView) { Map handCards = new HashMap<>(); for (Player player : game.getPlayers().values()) { if (player.hasUserPermissionToSeeHand(userId)) { diff --git a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java index 1f8d5988b80..38cef7431f1 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -23,6 +23,7 @@ import mage.game.GameCommanderImpl; import mage.game.command.CommandObject; import mage.game.command.Plane; import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; import mage.players.Player; import mage.util.CardUtil; import mage.util.RandomUtil; @@ -267,8 +268,8 @@ public final class SystemUtil { public static void addCardsForTesting(Game game, String fileSource, Player feedbackPlayer) { // fake test ability for triggers and events - Ability fakeSourceAbility = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); - fakeSourceAbility.setControllerId(feedbackPlayer.getId()); + Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); + fakeSourceAbilityTemplate.setControllerId(feedbackPlayer.getId()); try { String fileName = fileSource; @@ -496,9 +497,12 @@ public final class SystemUtil { // eg: token:Human:HippoToken:1 Class c = Class.forName("mage.game.permanent.token." + command.cardName); Constructor cons = c.getConstructor(); - Object token = cons.newInstance(); - if (token instanceof mage.game.permanent.token.Token) { - ((mage.game.permanent.token.Token) token).putOntoBattlefield(command.Amount, game, fakeSourceAbility, player.getId(), false, false); + Object obj = cons.newInstance(); + if (obj instanceof Token) { + Token token = (Token) obj; + Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); + fakeSourceAbility.setSourceId(token.getId()); + token.putOntoBattlefield(command.Amount, game, fakeSourceAbility, player.getId(), false, false); continue; } } else if ("emblem".equalsIgnoreCase(command.zone)) { @@ -518,6 +522,8 @@ public final class SystemUtil { } else if ("loyalty".equalsIgnoreCase(command.zone)) { for (Permanent perm : game.getBattlefield().getAllActivePermanents(player.getId())) { if (perm.getName().equals(command.cardName) && perm.getCardType(game).contains(CardType.PLANESWALKER)) { + Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); + fakeSourceAbility.setSourceId(perm.getId()); perm.addCounters(CounterType.LOYALTY.createInstance(command.Amount), fakeSourceAbility.getControllerId(), fakeSourceAbility, game); } } @@ -541,6 +547,8 @@ public final class SystemUtil { // move card from exile to stack for (Card card : cardsToLoad) { + Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); + fakeSourceAbility.setSourceId(card.getId()); putCardToZone(fakeSourceAbility, game, player, card, Zone.STACK); } @@ -616,6 +624,8 @@ public final class SystemUtil { } else { // as other card for (Card card : cardsToLoad) { + Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); + fakeSourceAbility.setSourceId(card.getId()); putCardToZone(fakeSourceAbility, game, player, card, gameZone); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 533367ec58e..2db1d1b67fa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -25,6 +25,7 @@ import mage.cards.repository.CardRepository; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; +import mage.game.match.Match; import mage.game.match.MatchType; import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; @@ -92,6 +93,8 @@ public abstract class MageTestPlayerBase { */ protected static Game currentGame = null; + protected static Match currentMatch = null; + /** * Player thats starts the game first. By default, it is ComputerA. */ diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 0e9e6a09eff..55b6d09d812 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -16,19 +16,19 @@ import mage.counters.CounterType; import mage.filter.Filter; import mage.filter.FilterCard; import mage.filter.predicate.mageobject.NamePredicate; -import mage.game.ExileZone; -import mage.game.Game; -import mage.game.GameException; -import mage.game.GameOptions; +import mage.game.*; import mage.game.command.CommandObject; +import mage.game.match.MatchOptions; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.player.ai.ComputerPlayer7; import mage.player.ai.ComputerPlayerMCTS; import mage.players.ManaPool; import mage.players.Player; +import mage.server.game.GameSessionPlayer; import mage.server.util.SystemUtil; import mage.util.CardUtil; +import mage.view.GameView; import org.junit.Assert; import org.junit.Before; import org.mage.test.player.PlayerAction; @@ -216,6 +216,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement currentGame = null; } + // prepare fake match (needs for testing some client-server code) + // always 4 seats + MatchOptions matchOptions = new MatchOptions("test match", "test game type", true, 4); + currentMatch = new FreeForAllMatch(matchOptions); currentGame = createNewGameAndPlayers(); activePlayer = playerA; @@ -267,6 +271,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement game.loadCards(deck.getCards(), player.getId()); game.loadCards(deck.getSideboard(), player.getId()); game.addPlayer(player, deck); + currentMatch.addPlayer(player, deck); // fake match return player; } @@ -2116,4 +2121,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement Assert.assertFalse(player.getName() + " has lost the game.", player.hasLost()); } + public GameView getGameView(Player player) { + // prepare client-server data for tests + return GameSessionPlayer.prepareGameView(currentGame, player.getId(), null); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java index 75f77d6bff6..4234a680ca2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java @@ -7,13 +7,13 @@ import mage.cards.repository.CardRepository; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; import mage.game.mulligan.LondonMulligan; import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentImpl; import mage.remote.traffic.ZippedObjectImpl; import mage.util.CardUtil; import mage.utils.CompressUtil; +import mage.view.GameView; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -76,9 +76,10 @@ public class SerializationTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - Object compressed = CompressUtil.compress(currentGame); + GameView gameView = getGameView(playerA); + Object compressed = CompressUtil.compress(gameView); Assert.assertTrue("Must be zip", compressed instanceof ZippedObjectImpl); - Game uncompressed = (Game) CompressUtil.decompress(compressed); - Assert.assertEquals("Must be same", 1, uncompressed.getBattlefield().getAllActivePermanents().size()); + GameView uncompressed = (GameView) CompressUtil.decompress(compressed); + Assert.assertEquals("Must be same", 1, uncompressed.getPlayers().get(0).getBattlefield().size()); } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 57031170bdd..e6b60b5d74c 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -3185,8 +3185,8 @@ public abstract class GameImpl implements Game, Serializable { @Override public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command) { // fake test ability for triggers and events - Ability fakeSourceAbility = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); - fakeSourceAbility.setControllerId(ownerId); + Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); + fakeSourceAbilityTemplate.setControllerId(ownerId); Player player = getPlayer(ownerId); if (player != null) { @@ -3221,6 +3221,8 @@ public abstract class GameImpl implements Game, Serializable { } for (PermanentCard permanentCard : battlefield) { + Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); + fakeSourceAbility.setSourceId(permanentCard.getId()); CardUtil.putCardOntoBattlefieldWithEffects(fakeSourceAbility, this, permanentCard, player); } From 76082e1d7a4cc5b2065b584d277fcbf50cdd1cf7 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 19 Jul 2021 13:40:21 +0400 Subject: [PATCH 40/48] * GUI: added card icon for announced X value (card cast); --- .../src/main/java/mage/view/CardView.java | 30 +++- .../mage/test/cards/copy/CopySpellTest.java | 135 ++++++++++++++- .../mage/test/serverside/CardIconsTest.java | 155 ++++++++++++++++++ .../common/ManacostVariableValue.java | 9 +- .../mage/abilities/icon/CardIconType.java | 1 + ...nStatusIcon.java => FaceDownCardIcon.java} | 2 +- .../icon/other/VariableCostCardIcon.java | 16 ++ .../util/functions/CopyTokenFunction.java | 6 +- .../common/ManaSpentToCastWatcher.java | 11 +- 9 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java rename Mage/src/main/java/mage/abilities/icon/other/{FaceDownStatusIcon.java => FaceDownCardIcon.java} (90%) create mode 100644 Mage/src/main/java/mage/abilities/icon/other/VariableCostCardIcon.java diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index b4347a29094..dca13d2c2c0 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -7,10 +7,12 @@ import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.SpellAbility; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.icon.CardIcon; -import mage.abilities.icon.other.FaceDownStatusIcon; +import mage.abilities.icon.other.FaceDownCardIcon; +import mage.abilities.icon.other.VariableCostCardIcon; import mage.abilities.keyword.AftermathAbility; import mage.cards.*; import mage.cards.mock.MockCard; @@ -369,7 +371,7 @@ public class CardView extends SimpleCardView { this.manaCostRightStr = String.join("", mainCard.getRightHalfCard().getManaCostSymbols()); } else if (card instanceof AdventureCard) { AdventureCard adventureCard = ((AdventureCard) card); - AdventureCardSpell adventureCardSpell = ((AdventureCardSpell) adventureCard.getSpellCard()); + AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName(); this.manaCostLeftStr = String.join("", adventureCardSpell.getManaCostSymbols()); this.manaCostRightStr = String.join("", adventureCard.getManaCostSymbols()); @@ -420,11 +422,10 @@ public class CardView extends SimpleCardView { permanent.getAbilities(game).forEach(ability -> { this.cardIcons.addAll(ability.getIcons(game)); }); - // other + // face down if (permanent.isFaceDown(game)) { - this.cardIcons.add(FaceDownStatusIcon.instance); + this.cardIcons.add(FaceDownCardIcon.instance); } - } else { if (card.isCopy()) { this.mageObjectType = MageObjectType.COPY_CARD; @@ -439,6 +440,25 @@ public class CardView extends SimpleCardView { } } } + + // card icons for any permanents and cards + if (game != null) { + // x cost + Zone cardZone = game.getState().getZone(card.getId()); + if (card.getManaCost().containsX() + && (cardZone.match(Zone.BATTLEFIELD) || cardZone.match(Zone.STACK))) { + int costX; + if (card instanceof Permanent) { + // permanent on battlefield + costX = ManacostVariableValue.ETB.calculate(game, card.getSpellAbility(), null); + } else { + // other like Stack + costX = ManacostVariableValue.REGULAR.calculate(game, card.getSpellAbility(), null); + } + this.cardIcons.add(new VariableCostCardIcon(costX)); + } + } + this.power = Integer.toString(card.getPower().getValue()); this.toughness = Integer.toString(card.getToughness().getValue()); this.cardTypes = card.getCardType(game); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index 4dc44d1b3c8..835bcfdd927 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -9,6 +9,7 @@ import mage.cards.SplitCard; import mage.cards.repository.CardRepository; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import mage.util.CardUtil; import org.junit.Assert; import org.junit.Test; @@ -522,6 +523,138 @@ public class CopySpellTest extends CardTestPlayerBase { assertAllCommandsUsed(); } + @Test + public void test_CopiedSpellsAndX_1() { + // testing: + // 1. x in copied instant spell (copy X) + // 2. x in copied creature (X=0) + + // test use case with rules: + // https://tappedout.net/mtg-questions/copying-a-creature-with-x-in-its-mana-cost/#c3561513 + // 107.3f If a card in any zone other than the stack has an {X} in its mana cost, the value of {X} is + // treated as 0, even if the value of X is defined somewhere within its text. + + // Whenever you cast an instant or sorcery spell, you may pay {U}{R}. If you do, copy that spell. You may choose new targets for the copy. + // Whenever another nontoken creature enters the battlefield under your control, you may pay {G}{U}. If you do, create a token that’s a copy of that creature. + addCard(Zone.BATTLEFIELD, playerA, "Riku of Two Reflections", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // Banefire deals X damage to any target. + addCard(Zone.HAND, playerA, "Banefire", 1); // {X}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + // 0/0 + // Capricopian enters the battlefield with X +1/+1 counters on it. + addCard(Zone.HAND, playerA, "Capricopian", 1); // {X}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + // 1 + // cast banefire and make copy + // announced X=2 must be copied + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banefire", playerB); + setChoice(playerA, "X=2"); + setChoice(playerA, "Yes"); // make copy + setChoice(playerA, "No"); // keep target same + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkLife("after spell copy", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 2 * 2); + + // 2 + // cast creature and copy it as token + // token must have x=0 (dies) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian"); + setChoice(playerA, "X=1"); + setChoice(playerA, "Yes"); // make copy + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after creature copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_CopiedSpellsHasntETB() { + // testing: + // - x in copied creature spell (copy x) + // - copied spells enters as tokens and it hasn't ETB, see rules below + + // 0/0 + // Capricopian enters the battlefield with X +1/+1 counters on it. + addCard(Zone.HAND, playerA, "Capricopian", 1); // {X}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Grenzo, Dungeon Warden enters the battlefield with X +1/+1 counters on it. + addCard(Zone.HAND, playerA, "Grenzo, Dungeon Warden", 1);// {X}{B}{R} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // Copy target creature spell you control, except it isn't legendary if the spell is legendary. + // (A copy of a creature spell becomes a token.) + addCard(Zone.HAND, playerA, "Double Major", 2); // {G}{U} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // 1. Capricopian + // cast and put on stack + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian"); + setChoice(playerA, "X=2"); + // copy of spell + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Capricopian", "Capricopian"); + + // 2. Grenzo, Dungeon Warden + // cast and put on stack + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 1); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden"); + setChoice(playerA, "X=2"); + // copy of spell + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Grenzo, Dungeon Warden", "Grenzo, Dungeon Warden"); + + // ETB triggers will not trigger here due not normal cast. From rules: + // - The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2021-04-16) + // - A nontoken permanent “enters the battlefield” when it’s moved onto the battlefield from another zone. + // A token “enters the battlefield” when it’s created. See rules 403.3, 603.6a, 603.6d, and 614.12. + // + // So both copies enters without counters: + // - Capricopian copy must die + // - Grenzo, Dungeon Warden must have default PT + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian", 1); // copy dies + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden", 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // counters checks + int originalCounters = currentGame.getBattlefield().getAllActivePermanents().stream() + .filter(p -> p.getName().equals("Grenzo, Dungeon Warden")) + .filter(p -> !p.isCopy()) + .mapToInt(p -> p.getCounters(currentGame).getCount(CounterType.P1P1)) + .sum(); + int copyCounters = currentGame.getBattlefield().getAllActivePermanents().stream() + .filter(p -> p.getName().equals("Grenzo, Dungeon Warden")) + .filter(p -> p.isCopy()) + .mapToInt(p -> p.getCounters(currentGame).getCount(CounterType.P1P1)) + .sum(); + Assert.assertEquals("original grenzo must have 2x counters", 2, originalCounters); + Assert.assertEquals("copied grenzo must have 0x counters", 0, copyCounters); + } + @Test public void test_SimpleCopy_Card() { Card sourceCard = CardRepository.instance.findCard("Grizzly Bears").getCard(); @@ -628,7 +761,7 @@ public class CopySpellTest extends CardTestPlayerBase { return; } - Assert.fail(infoPrefix + " - " + "ability source must be same: " + ability.toString()); + Assert.fail(infoPrefix + " - " + "ability source must be same: " + ability); } }); } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java new file mode 100644 index 00000000000..37b7d33efaf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java @@ -0,0 +1,155 @@ +package org.mage.test.serverside; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.view.CardView; +import mage.view.GameView; +import mage.view.PlayerView; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * GUI tests: card icons for cards + * + * @author JayDi85 + */ +public class CardIconsTest extends CardTestPlayerBase { + + @Test + public void test_CostX_Spells() { + // Chalice of the Void enters the battlefield with X charge counters on it. + // Whenever a player casts a spell with converted mana cost equal to the number of charge counters on Chalice of the Void, counter that spell. + addCard(Zone.HAND, playerA, "Chalice of the Void", 1); // {X}{X} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + // hand (not visible) + runCode("card icons in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("must have 1 card in hand", 1, gameView.getHand().values().size()); + CardView cardView = gameView.getHand().values().stream().findFirst().get(); + Assert.assertEquals("must have non x cost card icons in hand", 0, cardView.getCardIcons().size()); + }); + + // cast and put on stack + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chalice of the Void"); + setChoice(playerA, "X=2"); + + // stack (visible) + runCode("card icons on stack (spell)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("must have 1 card in stack", 1, gameView.getStack().values().size()); + CardView cardView = gameView.getStack().values().stream().findFirst().get(); + Assert.assertEquals("must have x cost card icons in stack", 1, cardView.getCardIcons().size()); + Assert.assertEquals("x cost text", "x=2", cardView.getCardIcons().get(0).getText()); + }); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chalice of the Void", 1); + + // battlefield (card, not visible) + runCode("card icons in battlefield (card)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + PlayerView playerView = gameView.getPlayers().get(0); + Assert.assertEquals("player", player.getName(), playerView.getName()); + CardView cardView = playerView.getBattlefield().values().stream().filter(p -> p.getName().equals("Chalice of the Void")).findFirst().orElse(null); + Assert.assertNotNull("must have 1 chalice in battlefield", cardView); + Assert.assertEquals("must have x cost card icons in battlefield (card)", 1, cardView.getCardIcons().size()); + Assert.assertEquals("x cost text", "x=2", cardView.getCardIcons().get(0).getText()); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_CostX_Copies() { + // Grenzo, Dungeon Warden enters the battlefield with X +1/+1 counters on it. + addCard(Zone.HAND, playerA, "Grenzo, Dungeon Warden", 1);// {X}{B}{R} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // Copy target creature spell you control, except it isn't legendary if the spell is legendary. + addCard(Zone.HAND, playerA, "Double Major", 1); // {G}{U} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + // cast and put on stack + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 1); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden"); + setChoice(playerA, "X=2"); + + // prepare copy of spell + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Grenzo, Dungeon Warden", "Grenzo, Dungeon Warden"); + checkStackSize("before copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + checkStackSize("after copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + + // stack (copied spell) + runCode("card icons on stack (copied spell)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("must have 2 cards in stack", 2, gameView.getStack().values().size()); + CardView originalCardView = gameView.getStack().values() + .stream() + .filter(c -> !c.getOriginalCard().isCopy()) + .findFirst() + .get(); + CardView copiedCardView = gameView.getStack().values() + .stream() + .filter(c -> c.getOriginalCard().isCopy()) + .findFirst() + .get(); + Assert.assertNotNull("stack must have original spell", originalCardView); + Assert.assertNotNull("stack must have copied spell", copiedCardView); + Assert.assertNotEquals("must find two spells on stack", originalCardView.getId(), copiedCardView.getId()); + Assert.assertEquals("original spell must have x cost card icons", 1, originalCardView.getCardIcons().size()); + Assert.assertEquals("copied spell must have x cost card icons", 1, copiedCardView.getCardIcons().size()); + Assert.assertEquals("original x cost text", "x=2", originalCardView.getCardIcons().get(0).getText()); + Assert.assertEquals("copied x cost text", "x=2", copiedCardView.getCardIcons().get(0).getText()); + }); + + // must resolve copied creature spell as a token + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden", 2); + + // battlefield (card and copied card as token) + runCode("card icons in battlefield (copied)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + PlayerView playerView = gameView.getPlayers().get(0); + Assert.assertEquals("player", player.getName(), playerView.getName()); + // copied spell goes as token to battlefield, not copied card - so must check isToken + // original + CardView originalCardView = playerView.getBattlefield().values() + .stream() + .filter(p -> p.getName().equals("Grenzo, Dungeon Warden")) + .filter(p -> !p.isToken()) + .findFirst() + .orElse(null); + Assert.assertNotNull("original card must be in battlefield", originalCardView); + Assert.assertEquals("original must have x cost card icons", 1, originalCardView.getCardIcons().size()); + Assert.assertEquals("original x cost text", "x=2", originalCardView.getCardIcons().get(0).getText()); + // + CardView copiedCardView = playerView.getBattlefield().values() + .stream() + .filter(p -> p.getName().equals("Grenzo, Dungeon Warden")) + .filter(p -> p.isToken()) + .findFirst() + .orElse(null); + Assert.assertNotNull("copied card must be in battlefield", copiedCardView); + Assert.assertEquals("copied must have x cost card icons", 1, copiedCardView.getCardIcons().size()); + Assert.assertEquals("copied x cost text", "x=0", copiedCardView.getCardIcons().get(0).getText()); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java index 9f77849d4d1..293b9ddcecb 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java @@ -7,7 +7,9 @@ import mage.game.Game; import mage.watchers.common.ManaSpentToCastWatcher; public enum ManacostVariableValue implements DynamicValue { - REGULAR, ETB; + + REGULAR, // if you need X on cast/activate (in stack) + ETB; // if you need X after ETB (in battlefield) @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { @@ -15,7 +17,10 @@ public enum ManacostVariableValue implements DynamicValue { return sourceAbility.getManaCostsToPay().getX(); } ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class); - return watcher != null ? watcher.getAndResetLastXValue(sourceAbility.getSourceId()) : sourceAbility.getManaCostsToPay().getX(); + if (watcher != null) { + return watcher.getAndResetLastXValue(sourceAbility); + } + return 0; } @Override diff --git a/Mage/src/main/java/mage/abilities/icon/CardIconType.java b/Mage/src/main/java/mage/abilities/icon/CardIconType.java index 483ebf5f911..f9657726322 100644 --- a/Mage/src/main/java/mage/abilities/icon/CardIconType.java +++ b/Mage/src/main/java/mage/abilities/icon/CardIconType.java @@ -32,6 +32,7 @@ public enum CardIconType { ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100), // OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100), + OTHER_COST_X("prepared/square-fill.svg", CardIconCategory.ABILITY, 100), // SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog diff --git a/Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java b/Mage/src/main/java/mage/abilities/icon/other/FaceDownCardIcon.java similarity index 90% rename from Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java rename to Mage/src/main/java/mage/abilities/icon/other/FaceDownCardIcon.java index e6f0d6a2eed..07f59fe8636 100644 --- a/Mage/src/main/java/mage/abilities/icon/other/FaceDownStatusIcon.java +++ b/Mage/src/main/java/mage/abilities/icon/other/FaceDownCardIcon.java @@ -6,7 +6,7 @@ import mage.abilities.icon.CardIconType; /** * @author JayDi85 */ -public enum FaceDownStatusIcon implements CardIcon { +public enum FaceDownCardIcon implements CardIcon { instance; @Override diff --git a/Mage/src/main/java/mage/abilities/icon/other/VariableCostCardIcon.java b/Mage/src/main/java/mage/abilities/icon/other/VariableCostCardIcon.java new file mode 100644 index 00000000000..40b4f23e016 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/icon/other/VariableCostCardIcon.java @@ -0,0 +1,16 @@ +package mage.abilities.icon.other; + +import mage.abilities.icon.CardIconImpl; +import mage.abilities.icon.CardIconType; + +/** + * Showing x cost value + * + * @author JayDi85 + */ +public class VariableCostCardIcon extends CardIconImpl { + + public VariableCostCardIcon(int costX) { + super(CardIconType.OTHER_COST_X, "Announced X = " + costX, "x=" + costX); + } +} diff --git a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java index 452c0cca071..c335c43a0fc 100644 --- a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java +++ b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java @@ -81,7 +81,11 @@ public class CopyTokenFunction implements Function { for (Ability ability0 : sourceObj.getAbilities()) { Ability ability = ability0.copy(); - ability.newOriginalId(); // The token is independant from the copy from object so it need a new original Id, otherwise there are problems to check for created continuous effects to check if the source (the Token) has still this ability + + // The token is independant from the copy from object so it need a new original Id, + // otherwise there are problems to check for created continuous effects to check if + // the source (the Token) has still this ability + ability.newOriginalId(); target.addAbility(ability); } diff --git a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java index 67c13a35678..7295a7e4621 100644 --- a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java @@ -1,6 +1,7 @@ package mage.watchers.common; import mage.Mana; +import mage.abilities.Ability; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; @@ -51,8 +52,14 @@ public class ManaSpentToCastWatcher extends Watcher { return manaMap.getOrDefault(sourceId, null); } - public int getAndResetLastXValue(UUID sourceId) { - return xValueMap.getOrDefault(sourceId, 0); + public int getAndResetLastXValue(Ability source) { + if (xValueMap.containsKey(source.getSourceId())) { + // cast normal way + return xValueMap.get(source.getSourceId()); + } else { + // put to battlefield without cast (example: copied spell must keep announced X) + return source.getManaCostsToPay().getX(); + } } @Override From 7ccb390e4db65fa1b4bdf07a2aae4e90a71ecb53 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 19 Jul 2021 14:25:51 +0400 Subject: [PATCH 41/48] * GUI: added card icon for announced X value (activate ability); --- .../main/java/mage/client/cards/Cards.java | 4 ++ .../main/java/mage/view/StackAbilityView.java | 13 +++- .../mage/test/serverside/CardIconsTest.java | 69 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index d79ed7c1e9d..cff58b56142 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -183,6 +183,7 @@ if (card instanceof StackAbilityView) { // replace ability by original card CardView tmp = ((StackAbilityView) card).getSourceCard(); + // sync settings tmp.overrideRules(card.getRules()); tmp.setChoosable(card.isChoosable()); tmp.setPlayableStats(card.getPlayableStats().copy()); @@ -191,6 +192,9 @@ tmp.overrideTargets(card.getTargets()); tmp.overrideId(card.getId()); tmp.setAbilityType(card.getAbilityType()); + // sync card icons + tmp.getCardIcons().clear(); + tmp.getCardIcons().addAll(card.getCardIcons()); card = tmp; } else { card.setAbilityType(null); diff --git a/Mage.Common/src/main/java/mage/view/StackAbilityView.java b/Mage.Common/src/main/java/mage/view/StackAbilityView.java index c9de06d7f6f..74142d11d94 100644 --- a/Mage.Common/src/main/java/mage/view/StackAbilityView.java +++ b/Mage.Common/src/main/java/mage/view/StackAbilityView.java @@ -3,9 +3,11 @@ package mage.view; import mage.MageObject; import mage.abilities.Mode; import mage.abilities.Modes; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.Effect; import mage.abilities.hint.Hint; import mage.abilities.hint.HintUtils; +import mage.abilities.icon.other.VariableCostCardIcon; import mage.cards.Card; import mage.constants.AbilityType; import mage.constants.CardType; @@ -28,7 +30,7 @@ public class StackAbilityView extends CardView { private static final long serialVersionUID = 1L; // in GUI: that's view will be replaced by sourceCard, so don't forget to sync settings like - // selectable, chooseable, etc. Search by getSourceCard + // selectable, chooseable, card icons etc. Search by getSourceCard private final CardView sourceCard; public StackAbilityView(Game game, StackAbility ability, String sourceName, CardView sourceCard) { @@ -73,6 +75,13 @@ public class StackAbilityView extends CardView { this.counters = sourceCard.getCounters(); updateTargets(game, ability); + + // card icons (warning, it must be synced in gui dialogs with replaced card, see comments at the start of the file) + // cost x + if (ability.getManaCostsToPay().containsX()) { + int costX = ManacostVariableValue.REGULAR.calculate(game, ability, null); + this.cardIcons.add(new VariableCostCardIcon(costX)); + } } private void updateTargets(Game game, StackAbility ability) { @@ -108,7 +117,7 @@ public class StackAbilityView extends CardView { } } if (!names.isEmpty()) { - getRules().add("Related objects: " + names.toString() + ""); + getRules().add("Related objects: " + names + ""); } // show for modal ability, which mode was choosen diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java index 37b7d33efaf..dae5855e90b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/CardIconsTest.java @@ -152,4 +152,73 @@ public class CardIconsTest extends CardTestPlayerBase { execute(); assertAllCommandsUsed(); } + + @Test + public void test_CostX_Abilities() { + // X icon must be visible only for activated ability, not spell cast + + // {X}{R}, {tap}, Sacrifice Cinder Elemental: Cinder Elemental deals X damage to any target. + addCard(Zone.HAND, playerA, "Cinder Elemental", 1); // {3}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + // hand (not visible) + runCode("card icons in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("must have 1 card in hand", 1, gameView.getHand().values().size()); + CardView cardView = gameView.getHand().values().stream().findFirst().get(); + Assert.assertEquals("must have non x cost card icons in hand", 0, cardView.getCardIcons().size()); + }); + + // spell cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cinder Elemental"); + + // stack (spell cast - not visible) + runCode("card icons on stack (spell cast - not visible)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("must have 1 card in stack", 1, gameView.getStack().values().size()); + CardView cardView = gameView.getStack().values().stream().findFirst().get(); + Assert.assertEquals("must have not x cost card icons in stack", 0, cardView.getCardIcons().size()); + }); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cinder Elemental", 1); + + // battlefield (card, not visible) + runCode("card icons in battlefield (card)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + PlayerView playerView = gameView.getPlayers().get(0); + Assert.assertEquals("player", player.getName(), playerView.getName()); + CardView cardView = playerView.getBattlefield().values().stream().filter(p -> p.getName().equals("Cinder Elemental")).findFirst().orElse(null); + Assert.assertNotNull("must have Cinder Elemental in battlefield", cardView); + Assert.assertEquals("must have not x cost card icons in battlefield (card)", 0, cardView.getCardIcons().size()); + }); + + // ACTIVATE ABILITY (x must be visible in stack, but not visible after resolve) + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}{R}"); + setChoice(playerA, "X=2"); + addTarget(playerA, playerB); + + // stack (ability activated - visible) + runCode("card icons on stack (ability activated - visible)", 3, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + Assert.assertEquals("ability activated - must have 1 card in stack", 1, gameView.getStack().values().size()); + CardView cardView = gameView.getStack().values().stream().findFirst().get(); + Assert.assertEquals("ability activated - must have x cost card icons in stack", 1, cardView.getCardIcons().size()); + }); + + // battlefield (ability activated, not visible) + runCode("card icons in battlefield (ability activated)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(player); + PlayerView playerView = gameView.getPlayers().get(0); + Assert.assertEquals("player", player.getName(), playerView.getName()); + CardView cardView = playerView.getBattlefield().values().stream().filter(p -> p.getName().equals("Cinder Elemental")).findFirst().orElse(null); + Assert.assertNotNull("ability activated - must have Cinder Elemental in battlefield", cardView); + Assert.assertEquals("ability activated - must have not x cost card icons in battlefield", 0, cardView.getCardIcons().size()); + }); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } From 34a0e9546a94ef3bb9f9b779e1c680db8006030e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 12:50:18 -0400 Subject: [PATCH 42/48] [AFC] Implemented Sefris of the Hidden Ways --- .../mage/cards/s/SefrisOfTheHiddenWays.java | 57 +++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + ...oGraveFromAnywhereAllTriggeredAbility.java | 6 +- 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java diff --git a/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java b/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java new file mode 100644 index 00000000000..6ddb6ca649c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CompletedDungeonTriggeredAbility; +import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SefrisOfTheHiddenWays extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("one or more creature cards"); + + public SefrisOfTheHiddenWays(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever one or more creature cards are put into your graveyard from anywhere, venture into the dungeon. This ability triggers only once each turn. + this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility( + new VentureIntoTheDungeonEffect(), false, filter, TargetController.YOU + ).setTriggersOnce(true)); + + // Create Undead — Whenever you complete a dungeon, return target creature card from your graveyard to the battlefield. + Ability ability = new CompletedDungeonTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability.withFlavorWord("Create Undead")); + } + + private SefrisOfTheHiddenWays(final SefrisOfTheHiddenWays card) { + super(card); + } + + @Override + public SefrisOfTheHiddenWays copy() { + return new SefrisOfTheHiddenWays(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 9eb47ee64cb..18484e3f9ab 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -191,6 +191,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Savage Ventmaw", 191, Rarity.UNCOMMON, mage.cards.s.SavageVentmaw.class)); cards.add(new SetCardInfo("Scourge of Valkas", 137, Rarity.RARE, mage.cards.s.ScourgeOfValkas.class)); cards.add(new SetCardInfo("Seaside Citadel", 258, Rarity.UNCOMMON, mage.cards.s.SeasideCitadel.class)); + cards.add(new SetCardInfo("Sefris of the Hidden Ways", 3, Rarity.MYTHIC, mage.cards.s.SefrisOfTheHiddenWays.class)); cards.add(new SetCardInfo("Serum Visions", 94, Rarity.UNCOMMON, mage.cards.s.SerumVisions.class)); cards.add(new SetCardInfo("Shadowblood Ridge", 259, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); cards.add(new SetCardInfo("Shamanic Revelation", 171, Rarity.RARE, mage.cards.s.ShamanicRevelation.class)); diff --git a/Mage/src/main/java/mage/abilities/common/PutCardIntoGraveFromAnywhereAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutCardIntoGraveFromAnywhereAllTriggeredAbility.java index 7cbafb00ad2..c6f29a79277 100644 --- a/Mage/src/main/java/mage/abilities/common/PutCardIntoGraveFromAnywhereAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutCardIntoGraveFromAnywhereAllTriggeredAbility.java @@ -14,7 +14,6 @@ import mage.game.events.ZoneChangeEvent; import mage.target.targetpointer.FixedTarget; /** - * * @author LevelX2 */ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAbilityImpl { @@ -42,7 +41,8 @@ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAb this.filter.add(targetController.getOwnerPredicate()); StringBuilder sb = new StringBuilder("Whenever "); sb.append(filter.getMessage()); - sb.append(" is put into "); + sb.append(filter.getMessage().startsWith("one or more") ? " are" : "is"); + sb.append(" put into "); switch (targetController) { case OPPONENT: sb.append("an opponent's"); @@ -103,6 +103,6 @@ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAb @Override public String getTriggerPhrase() { - return ruleText ; + return ruleText; } } From d2540eab91ddcda192c67e7cf8a3ec49e0d0b737 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 16:15:38 -0400 Subject: [PATCH 43/48] [AFR] fixed Volo, Guide to Monsters comparing names instead of creature types --- Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java b/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java index 93a6ede3e28..35cc5c51bba 100644 --- a/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java +++ b/Mage.Sets/src/mage/cards/v/VoloGuideToMonsters.java @@ -15,7 +15,6 @@ import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.stack.StackObject; import mage.players.Player; -import mage.util.CardUtil; import java.util.UUID; @@ -71,13 +70,13 @@ enum VoloGuideToMonstersPredicate implements Predicate { .getGraveyard() .getCards(StaticFilters.FILTER_CARD_CREATURE, game) .stream() - .anyMatch(card -> CardUtil.haveSameNames(card, input))) { + .anyMatch(card -> input.shareCreatureTypes(game, card))) { return false; } return game .getBattlefield() .getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, input.getControllerId(), game) .stream() - .noneMatch(permanent -> CardUtil.haveSameNames(permanent, input)); + .noneMatch(permanent -> input.shareCreatureTypes(game, permanent)); } } From aa3254f09f0e7e059acc08a0b9375e8f178008e5 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 16:17:06 -0400 Subject: [PATCH 44/48] [AFR] fixed Trickster's Talisman missing ability --- Mage.Sets/src/mage/cards/t/TrickstersTalisman.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java index 50b169b5338..785ae3d926e 100644 --- a/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java +++ b/Mage.Sets/src/mage/cards/t/TrickstersTalisman.java @@ -31,6 +31,7 @@ public final class TrickstersTalisman extends CardImpl { // Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature." Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); ability.addEffect(new TrickstersTalismanEffect()); + this.addAbility(ability); // Equip {2} this.addAbility(new EquipAbility(2)); From 66cebe64b095183fc7ad17d623dcaf4459a7d97a Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 16:35:39 -0400 Subject: [PATCH 45/48] [AFC] Implemented Clay Golem --- Mage.Sets/src/mage/cards/c/ClayGolem.java | 140 ++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + .../abilities/keyword/MonstrosityAbility.java | 72 ++++----- 3 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/c/ClayGolem.java diff --git a/Mage.Sets/src/mage/cards/c/ClayGolem.java b/Mage.Sets/src/mage/cards/c/ClayGolem.java new file mode 100644 index 00000000000..84ba183bfe8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ClayGolem.java @@ -0,0 +1,140 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesMonstrousSourceTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.hint.common.MonstrousHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ClayGolem extends CardImpl { + + public ClayGolem(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); + + this.subtype.add(SubType.GOLEM); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // {6}, Roll a d8: Monstrosity X, where X is the result. + Ability ability = new SimpleActivatedAbility(new ClayGolemEffect(), new GenericManaCost(6)); + ability.addCost(new ClayGolemCost()); + this.addAbility(ability.addHint(MonstrousHint.instance)); + + // Berserk — When Clay Golem becomes monstrous, destroy target permanent. + ability = new BecomesMonstrousSourceTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability.withFlavorWord("Berserk")); + } + + private ClayGolem(final ClayGolem card) { + super(card); + } + + @Override + public ClayGolem copy() { + return new ClayGolem(this); + } +} + +class ClayGolemCost extends CostImpl { + + private int lastRoll = 0; + + ClayGolemCost() { + super(); + text = "roll a d8"; + } + + private ClayGolemCost(final ClayGolemCost cost) { + super(cost); + this.lastRoll = cost.lastRoll; + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return true; + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + Player player = game.getPlayer(controllerId); + if (player == null) { + return paid; + } + lastRoll = player.rollDice(source, game, 8); + paid = true; + return paid; + } + + @Override + public ClayGolemCost copy() { + return new ClayGolemCost(this); + } + + public int getLastRoll() { + return lastRoll; + } +} + +class ClayGolemEffect extends OneShotEffect { + ClayGolemEffect() { + super(Outcome.Benefit); + staticText = "monstrosity X, where X is the result"; + } + + private ClayGolemEffect(final ClayGolemEffect effect) { + super(effect); + } + + @Override + public ClayGolemEffect copy() { + return new ClayGolemEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || permanent.isMonstrous()) { + return false; + } + int monstrosityValue = source + .getCosts() + .stream() + .filter(ClayGolemCost.class::isInstance) + .map(ClayGolemCost.class::cast) + .mapToInt(ClayGolemCost::getLastRoll) + .findFirst() + .orElse(0); + permanent.addCounters( + CounterType.P1P1.createInstance(monstrosityValue), + source.getControllerId(), source, game + ); + permanent.setMonstrous(true); + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.BECOMES_MONSTROUS, source.getSourceId(), + source, source.getControllerId(), monstrosityValue + )); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 18484e3f9ab..74c7d308771 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -56,6 +56,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Chittering Witch", 95, Rarity.RARE, mage.cards.c.ChitteringWitch.class)); cards.add(new SetCardInfo("Choked Estuary", 228, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); cards.add(new SetCardInfo("Cinder Glade", 229, Rarity.RARE, mage.cards.c.CinderGlade.class)); + cards.add(new SetCardInfo("Clay Golem", 58, Rarity.UNCOMMON, mage.cards.c.ClayGolem.class)); cards.add(new SetCardInfo("Cloudblazer", 182, Rarity.UNCOMMON, mage.cards.c.Cloudblazer.class)); cards.add(new SetCardInfo("Cold-Eyed Selkie", 183, Rarity.RARE, mage.cards.c.ColdEyedSelkie.class)); cards.add(new SetCardInfo("Colossal Majesty", 154, Rarity.UNCOMMON, mage.cards.c.ColossalMajesty.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/MonstrosityAbility.java b/Mage/src/main/java/mage/abilities/keyword/MonstrosityAbility.java index 43d4f9b5394..8dfd4d7d0c1 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MonstrosityAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MonstrosityAbility.java @@ -1,12 +1,9 @@ - - package mage.abilities.keyword; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.hint.common.MonstrousHint; import mage.constants.Outcome; import mage.constants.Zone; @@ -19,41 +16,40 @@ import mage.util.CardUtil; /** * Monstrosity - * + *

* 701.28. Monstrosity - * + *

* 701.28a “Monstrosity N” means “If this permanent isn't monstrous, put N +1/+1 counters on it - * and it becomes monstrous.” Monstrous is a condition of that permanent that can be - * referred to by other abilities. - * + * and it becomes monstrous.” Monstrous is a condition of that permanent that can be + * referred to by other abilities. + *

* 701.28b If a permanent's ability instructs a player to “monstrosity X,” other abilities of - * that permanent may also refer to X. The value of X in those abilities is equal to - * the value of X as that permanent became monstrous. - * + * that permanent may also refer to X. The value of X in those abilities is equal to + * the value of X as that permanent became monstrous. + *

* * Once a creature becomes monstrous, it can't become monstrous again. If the creature - * is already monstrous when the monstrosity ability resolves, nothing happens. - * + * is already monstrous when the monstrosity ability resolves, nothing happens. + *

* * Monstrous isn't an ability that a creature has. It's just something true about that - * creature. If the creature stops being a creature or loses its abilities, it will - * continue to be monstrous. - * + * creature. If the creature stops being a creature or loses its abilities, it will + * continue to be monstrous. + *

* * An ability that triggers when a creature becomes monstrous won't trigger if that creature - * isn't on the battlefield when its monstrosity ability resolves. + * isn't on the battlefield when its monstrosity ability resolves. * * @author LevelX2 */ public class MonstrosityAbility extends ActivatedAbilityImpl { - private int monstrosityValue; + private final int monstrosityValue; /** - * * @param manaString * @param monstrosityValue use Integer.MAX_VALUE for monstrosity X. */ public MonstrosityAbility(String manaString, int monstrosityValue) { - super(Zone.BATTLEFIELD, new BecomeMonstrousSourceEffect(monstrosityValue),new ManaCostsImpl(manaString)); + super(Zone.BATTLEFIELD, new BecomeMonstrousSourceEffect(monstrosityValue), new ManaCostsImpl<>(manaString)); this.monstrosityValue = monstrosityValue; this.addHint(MonstrousHint.instance); @@ -72,7 +68,6 @@ public class MonstrosityAbility extends ActivatedAbilityImpl { public int getMonstrosityValue() { return monstrosityValue; } - } @@ -94,28 +89,33 @@ class BecomeMonstrousSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null && !permanent.isMonstrous() && source instanceof MonstrosityAbility) { - int monstrosityValue = ((MonstrosityAbility) source).getMonstrosityValue(); - // handle monstrosity = X - if (monstrosityValue == Integer.MAX_VALUE) { - monstrosityValue = source.getManaCostsToPay().getX(); - } - new AddCountersSourceEffect(CounterType.P1P1.createInstance(monstrosityValue)).apply(game, source); - permanent.setMonstrous(true); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BECOMES_MONSTROUS, source.getSourceId(), source, source.getControllerId(), monstrosityValue)); - return true; + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || permanent.isMonstrous()) { + return false; } - return false; + int monstrosityValue = ((MonstrosityAbility) source).getMonstrosityValue(); + // handle monstrosity = X + if (monstrosityValue == Integer.MAX_VALUE) { + monstrosityValue = source.getManaCostsToPay().getX(); + } + permanent.addCounters( + CounterType.P1P1.createInstance(monstrosityValue), + source.getControllerId(), source, game + ); + permanent.setMonstrous(true); + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.BECOMES_MONSTROUS, source.getSourceId(), + source, source.getControllerId(), monstrosityValue + )); + return true; } private String setText(int monstrosityValue) { StringBuilder sb = new StringBuilder("Monstrosity "); - sb.append(monstrosityValue == Integer.MAX_VALUE ? "X":monstrosityValue) + sb.append(monstrosityValue == Integer.MAX_VALUE ? "X" : monstrosityValue) .append(". (If this creature isn't monstrous, put ") - .append(monstrosityValue == Integer.MAX_VALUE ? "X":CardUtil.numberToText(monstrosityValue)) + .append(monstrosityValue == Integer.MAX_VALUE ? "X" : CardUtil.numberToText(monstrosityValue)) .append(" +1/+1 counters on it and it becomes monstrous.)").toString(); return sb.toString(); } - } From a0f736ccf4e22a1cb6b9f62a81c6e98cecd6bd77 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 16:44:34 -0400 Subject: [PATCH 46/48] [AFC] Implemented Ebony Fly --- Mage.Sets/src/mage/cards/e/EbonyFly.java | 110 ++++++++++++++++++ .../mage/sets/ForgottenRealmsCommander.java | 1 + 2 files changed, 111 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EbonyFly.java diff --git a/Mage.Sets/src/mage/cards/e/EbonyFly.java b/Mage.Sets/src/mage/cards/e/EbonyFly.java new file mode 100644 index 00000000000..8949eb3277d --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EbonyFly.java @@ -0,0 +1,110 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.custom.CreatureToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EbonyFly extends CardImpl { + + private static final FilterPermanent filter + = new FilterAttackingCreature("another target attacking creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public EbonyFly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // Ebony Fly enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {4}: Roll a d6. Until end of turn, you may have Ebony Fly become an X/X Insect artifact creature with flying, where X is the result. + this.addAbility(new SimpleActivatedAbility(new EbonyFlyEffect(), new GenericManaCost(4))); + + // Whenever Ebony Fly attacks, another target attacking creature gains flying until end of turn. + Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn + )); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private EbonyFly(final EbonyFly card) { + super(card); + } + + @Override + public EbonyFly copy() { + return new EbonyFly(this); + } +} + +class EbonyFlyEffect extends OneShotEffect { + + EbonyFlyEffect() { + super(Outcome.Benefit); + staticText = "roll a d6. Until end of turn, you may have {this} " + + "become an X/X Insect artifact creature with flying, where X is the result"; + } + + private EbonyFlyEffect(final EbonyFlyEffect effect) { + super(effect); + } + + @Override + public EbonyFlyEffect copy() { + return new EbonyFlyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int result = player.rollDice(source, game, 6); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || !player.chooseUse( + outcome, "Have " + permanent.getName() + " become a " + + result + '/' + result + " creature until end of turn?", source, game + )) { + return true; + } + game.addEffect(new BecomesCreatureSourceEffect( + new CreatureToken(result, result) + .withType(CardType.ARTIFACT) + .withAbility(FlyingAbility.getInstance()), + "", Duration.EndOfTurn, false, false + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index 74c7d308771..b6a7c190cc3 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -88,6 +88,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Dragonmaster Outcast", 124, Rarity.MYTHIC, mage.cards.d.DragonmasterOutcast.class)); cards.add(new SetCardInfo("Dragonspeaker Shaman", 330, Rarity.UNCOMMON, mage.cards.d.DragonspeakerShaman.class)); cards.add(new SetCardInfo("Dream Pillager", 125, Rarity.RARE, mage.cards.d.DreamPillager.class)); + cards.add(new SetCardInfo("Ebony Fly", 60, Rarity.UNCOMMON, mage.cards.e.EbonyFly.class)); cards.add(new SetCardInfo("Eel Umbra", 83, Rarity.COMMON, mage.cards.e.EelUmbra.class)); cards.add(new SetCardInfo("Esper Panorama", 235, Rarity.COMMON, mage.cards.e.EsperPanorama.class)); cards.add(new SetCardInfo("Etali, Primal Storm", 126, Rarity.RARE, mage.cards.e.EtaliPrimalStorm.class)); From 19693c94f7faf578a9c79cbaaea1bec782b7875a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 20 Jul 2021 01:01:16 +0400 Subject: [PATCH 47/48] * Path of Ancestry - fixed that it doesn't triggers a scry on commander cast (#7917, #7538, #7750); --- .../src/mage/cards/p/PathOfAncestry.java | 3 +- Mage.Sets/src/mage/cards/s/StingingStudy.java | 3 +- Mage/src/main/java/mage/game/Game.java | 72 ++++++++++++++++--- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PathOfAncestry.java b/Mage.Sets/src/mage/cards/p/PathOfAncestry.java index aea19fa06c9..6350f97c25c 100644 --- a/Mage.Sets/src/mage/cards/p/PathOfAncestry.java +++ b/Mage.Sets/src/mage/cards/p/PathOfAncestry.java @@ -12,6 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.CommanderCardType; import mage.constants.Duration; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -93,7 +94,7 @@ class PathOfAncestryTriggeredAbility extends DelayedTriggeredAbility { } // share creature type with commander - for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { + for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, Zone.ALL)) { if (spell.getCard().shareCreatureTypes(game, commander)) { return true; } diff --git a/Mage.Sets/src/mage/cards/s/StingingStudy.java b/Mage.Sets/src/mage/cards/s/StingingStudy.java index 250ed39c8cd..ccee4735dec 100644 --- a/Mage.Sets/src/mage/cards/s/StingingStudy.java +++ b/Mage.Sets/src/mage/cards/s/StingingStudy.java @@ -10,6 +10,7 @@ import mage.choices.ChoiceImpl; import mage.constants.CardType; import mage.constants.CommanderCardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.game.Game; import mage.players.Player; @@ -64,7 +65,7 @@ class StingingStudyEffect extends OneShotEffect { return false; } Set manaValues = new HashSet<>(); - for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.ANY)) { + for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.ANY, Zone.BATTLEFIELD, Zone.COMMAND)) { manaValues.add(commander.getManaValue()); } int chosenValue = 0; diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index d4d83971fc8..1e17fcaf0cc 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -542,15 +542,71 @@ public interface Game extends MageItem, Serializable { * @param commanderCardType commander or signature spell * @return */ - default Set getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType) { - // from command zone - Set res = getCommanderCardsFromCommandZone(player, commanderCardType); - - // from battlefield - this.getCommandersIds(player, commanderCardType, true).stream() - .map(this::getPermanent) + default Set getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType, Zone... searchZones) { + Set needZones = Arrays.stream(searchZones).collect(Collectors.toSet()); + if (needZones.isEmpty()) { + throw new IllegalArgumentException("Empty zones list in searching commanders"); + } + Set needCommandersIds = this.getCommandersIds(player, commanderCardType, true); + Set needCommandersCards = needCommandersIds.stream() + .map(this::getCard) .filter(Objects::nonNull) - .forEach(res::add); + .collect(Collectors.toSet()); + Set res = new HashSet<>(); + + // hand + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.HAND)) { + needCommandersCards.stream() + .filter(card -> Zone.HAND.equals(this.getState().getZone(card.getId()))) + .forEach(res::add); + } + + // graveyard + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.GRAVEYARD)) { + needCommandersCards.stream() + .filter(card -> Zone.GRAVEYARD.equals(this.getState().getZone(card.getId()))) + .forEach(res::add); + } + + // library + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.LIBRARY)) { + needCommandersCards.stream() + .filter(card -> Zone.LIBRARY.equals(this.getState().getZone(card.getId()))) + .forEach(res::add); + } + + // battlefield (need permanent card) + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.BATTLEFIELD)) { + needCommandersIds.stream() + .map(this::getPermanent) + .filter(Objects::nonNull) + .forEach(res::add); + } + + // stack + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.STACK)) { + needCommandersCards.stream() + .filter(card -> Zone.STACK.equals(this.getState().getZone(card.getId()))) + .forEach(res::add); + } + + // exiled + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.EXILED)) { + needCommandersCards.stream() + .filter(card -> Zone.EXILED.equals(this.getState().getZone(card.getId()))) + .forEach(res::add); + } + + // command + if (needZones.contains(Zone.ALL) || needZones.contains(Zone.COMMAND)) { + res.addAll(getCommanderCardsFromCommandZone(player, commanderCardType)); + } + + // outside must be ignored (example: second side of MDFC commander after cast) + if (needZones.contains(Zone.OUTSIDE)) { + throw new IllegalArgumentException("Outside zone doesn't supported in searching commanders"); + } + return res; } From cde5e2c4c9d390e472f5c93071189022b04511da Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 19 Jul 2021 22:44:36 -0400 Subject: [PATCH 48/48] added temporary test skips, removed others --- .../src/test/java/mage/verify/VerifyCardDataTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 68e4551cc9f..ce0a8efc0c4 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -102,14 +102,12 @@ public class VerifyCardDataTest { skipListAddName(SKIP_LIST_TYPE, "UNH", "Old Fogey"); // uses summon word as a joke card skipListAddName(SKIP_LIST_TYPE, "UND", "Old Fogey"); skipListAddName(SKIP_LIST_TYPE, "UST", "capital offense"); // uses "instant" instead "Instant" as a joke card - skipListAddName(SKIP_LIST_TYPE, "AFR", "Iron Golem"); // temporary - skipListAddName(SKIP_LIST_TYPE, "AFR", "Silver Raven"); // temporary // subtype + subtypesToIgnore.add("Bard"); // until errata is implemented and on mtgjson + subtypesToIgnore.add("Ranger"); // until errata is implemented and on mtgjson skipListCreate(SKIP_LIST_SUBTYPE); skipListAddName(SKIP_LIST_SUBTYPE, "UGL", "Miss Demeanor"); // uses multiple types as a joke card: Lady, of, Proper, Etiquette - skipListAddName(SKIP_LIST_SUBTYPE, "AFR", "Iron Golem"); // temporary - skipListAddName(SKIP_LIST_SUBTYPE, "AFR", "Silver Raven"); // temporary // number skipListCreate(SKIP_LIST_NUMBER);