From 91f5f85bf4f65efe6483e74ce25e44bd27dabfd5 Mon Sep 17 00:00:00 2001 From: jackd149 <67557084+jackd149@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:46:07 -0500 Subject: [PATCH 1/3] [DSK] Implement Kaito, Bane of Monsters --- .../sources/ScryfallImageSupportTokens.java | 3 + .../mage/cards/k/KaitoBaneOfNightmares.java | 135 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 4 + .../emblems/KaitoBaneOfNightmaresEmblem.java | 28 ++++ .../common/PlayerLostLifeWatcher.java | 13 ++ Mage/src/main/resources/tokens-database.txt | 1 + 6 files changed, 184 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java create mode 100644 Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 0b091bd16fbf..573b54094e27 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -2457,6 +2457,9 @@ public class ScryfallImageSupportTokens { // BLC put("BLC/Raccoon", "https://api.scryfall.com/cards/tblc/29/en?format=image"); + // DSK + put("DSK/Emblem Kaito", "https://api.scryfall.com/cards/tdsk/17/en?format=image"); + // FDN put("FDN/Beast/1", "https://api.scryfall.com/cards/tfdn/32/en?format=image"); put("FDN/Beast/2", "https://api.scryfall.com/cards/tfdn/33/en?format=image"); diff --git a/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java b/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java new file mode 100644 index 000000000000..94eafc562c55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java @@ -0,0 +1,135 @@ +package mage.cards.k; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.hint.common.MyTurnHint; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.NinjutsuAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.command.emblems.KaitoBaneOfNightmaresEmblem; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.common.TargetCreaturePermanent; +import mage.watchers.common.PlayerLostLifeWatcher; + +import java.util.UUID; + +/** + * + * @author jackd149 + */ +public final class KaitoBaneOfNightmares extends CardImpl { + + public KaitoBaneOfNightmares(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.KAITO); + this.setStartingLoyalty(4); + + // Ninjutsu {1}{U}{B} + this.addAbility(new NinjutsuAbility("{1}{U}{B}")); + + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BecomesCreatureSourceEffect( + new CreatureToken(3, 4, "3/4 Ninja creature") + .withSubType(SubType.NINJA) + .withAbility(HexproofAbility.getInstance()), null, Duration.WhileOnBattlefield + ), KaitoBaneOfNightmaresCondition.instance, "During your turn, as long as {this} has one or more loyalty counters on him, " + + "he's a 3/4 Ninja creature and has hexproof." + )).addHint(MyTurnHint.instance)); + + // +1: You get an emblem with "Ninjas you control get +1/+1." + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new KaitoBaneOfNightmaresEmblem()), 1)); + + // 0: Surveil 2. Then draw a card for each opponent who lost life this turn. + Ability ability = new LoyaltyAbility(new SurveilEffect(2), 0); + ability.addEffect(new DrawCardSourceControllerEffect(NumberOfOpponentsWhoLostLife.instance)); + this.addAbility(ability, new PlayerLostLifeWatcher()); + + // -2: Tap target creature. Put two stun counters on it. + Ability minusTwoAbility = new LoyaltyAbility(new TapTargetEffect(), -2); + minusTwoAbility.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance(2)) + .setText("Put two stun counters on it")); + minusTwoAbility.addTarget(new TargetCreaturePermanent()); + this.addAbility(minusTwoAbility); + } + + private KaitoBaneOfNightmares(final KaitoBaneOfNightmares card) { + super(card); + } + + @Override + public KaitoBaneOfNightmares copy() { + return new KaitoBaneOfNightmares(this); + } +} + +enum KaitoBaneOfNightmaresCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (!MyTurnCondition.instance.apply(game, source)){ + return false; + } + + Permanent permanent = game.getPermanent(source.getSourceId()); + + if (permanent == null) { + return false; + } + + int loyaltyCount = permanent.getCounters(game).getCount(CounterType.LOYALTY); + return loyaltyCount > 0; + + } +} + +enum NumberOfOpponentsWhoLostLife implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return this.calculate(game, sourceAbility.getControllerId()); + } + + public int calculate(Game game, UUID controllerId) { + PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class); + if (watcher != null) { + return watcher.getNumberOfOpponentsWhoLostLife(controllerId, game); + } + return 0; + } + + @Override + public NumberOfOpponentsWhoLostLife copy() { + return instance; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "opponent who lost life this turn."; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 28e8282694e8..08420c6897e7 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -131,6 +131,10 @@ private DuskmournHouseOfHorror() { cards.add(new SetCardInfo("Irreverent Gremlin", 142, Rarity.UNCOMMON, mage.cards.i.IrreverentGremlin.class)); cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Jump Scare", 17, Rarity.COMMON, mage.cards.j.JumpScare.class)); + cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 220, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 328, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 354, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 409, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Killer's Mask", 104, Rarity.UNCOMMON, mage.cards.k.KillersMask.class)); cards.add(new SetCardInfo("Kona, Rescue Beastie", 187, Rarity.RARE, mage.cards.k.KonaRescueBeastie.class)); cards.add(new SetCardInfo("Lakeside Shack", 262, Rarity.COMMON, mage.cards.l.LakesideShack.class)); diff --git a/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java b/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java new file mode 100644 index 000000000000..40af6bce99ed --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java @@ -0,0 +1,28 @@ +package mage.game.command.emblems; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.command.Emblem; + +public final class KaitoBaneOfNightmaresEmblem extends Emblem { + + public KaitoBaneOfNightmaresEmblem() { + super("Emblem Kaito"); + FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.NINJA, "Ninjas you control"); + this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, new BoostControlledEffect(1, 1, Duration.EndOfGame, filter, false))); + } + + private KaitoBaneOfNightmaresEmblem(final KaitoBaneOfNightmaresEmblem card) { + super(card); + } + + @Override + public KaitoBaneOfNightmaresEmblem copy() { + return new KaitoBaneOfNightmaresEmblem(this); + } + +} diff --git a/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java b/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java index 89c03aeda2a6..ad1551295d7e 100644 --- a/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java @@ -59,6 +59,19 @@ public int getAllOppLifeLost(UUID playerId, Game game) { return amount; } + public int getNumberOfOpponentsWhoLostLife(UUID playerId, Game game) { + int numPlayersLostLife = 0; + for (UUID opponentId : this.amountOfLifeLostThisTurn.keySet()) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null && opponent.hasOpponent(playerId, game)) { + if (this.amountOfLifeLostThisTurn.getOrDefault(opponentId, 0) > 0) { + numPlayersLostLife++; + } + } + } + return numPlayersLostLife; + } + public int getLifeLostLastTurn(UUID playerId) { return amountOfLifeLostLastTurn.getOrDefault(playerId, 0); } diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 643a4eefd12f..a842d1c56003 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -136,6 +136,7 @@ |Generate|EMBLEM:M3C|Emblem Vivien|||VivienReidEmblem| |Generate|EMBLEM:ACR|Emblem Capitoline Triad|||TheCapitolineTriadEmblem| |Generate|EMBLEM:BLB|Emblem Ral|||RalCracklingWitEmblem| +|Generate|EMBLEM:DSK|Emblem Kaito|||KaitoBaneOfNightmaresEmblem| |Generate|EMBLEM:FDN|Emblem Kaito|||KaitoCunningInfiltratorEmblem| |Generate|EMBLEM:FDN|Emblem Vivien|||VivienReidEmblem| From 4025ad7060f1f5e6e8d57150f7f1a1e4f7538758 Mon Sep 17 00:00:00 2001 From: jackd149 <67557084+jackd149@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:50:45 -0500 Subject: [PATCH 2/3] add author to emblem --- .../mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java b/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java index 40af6bce99ed..ac6fa47d0c4a 100644 --- a/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/KaitoBaneOfNightmaresEmblem.java @@ -8,6 +8,9 @@ import mage.filter.common.FilterCreaturePermanent; import mage.game.command.Emblem; +/** + * @author jackd149 + */ public final class KaitoBaneOfNightmaresEmblem extends Emblem { public KaitoBaneOfNightmaresEmblem() { From 96782fbb76fe827c069f6df9e53cd178b94cee9f Mon Sep 17 00:00:00 2001 From: jackd149 <67557084+jackd149@users.noreply.github.com> Date: Thu, 2 Jan 2025 11:47:50 +0300 Subject: [PATCH 3/3] Fixes for review --- .../src/mage/cards/k/KaitoBaneOfNightmares.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java b/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java index 94eafc562c55..f947283d7f04 100644 --- a/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java +++ b/Mage.Sets/src/mage/cards/k/KaitoBaneOfNightmares.java @@ -46,6 +46,7 @@ public KaitoBaneOfNightmares(UUID ownerId, CardSetInfo setInfo) { // Ninjutsu {1}{U}{B} this.addAbility(new NinjutsuAbility("{1}{U}{B}")); + // During your turn, as long as Kaito has one or more loyalty counters on him, he's a 3/4 Ninja creature and has hexproof. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BecomesCreatureSourceEffect( new CreatureToken(3, 4, "3/4 Ninja creature") @@ -60,7 +61,7 @@ public KaitoBaneOfNightmares(UUID ownerId, CardSetInfo setInfo) { // 0: Surveil 2. Then draw a card for each opponent who lost life this turn. Ability ability = new LoyaltyAbility(new SurveilEffect(2), 0); - ability.addEffect(new DrawCardSourceControllerEffect(NumberOfOpponentsWhoLostLife.instance)); + ability.addEffect(new DrawCardSourceControllerEffect(KaitoBaneOfNightmaresCount.instance)); this.addAbility(ability, new PlayerLostLifeWatcher()); // -2: Tap target creature. Put two stun counters on it. @@ -102,24 +103,20 @@ public boolean apply(Game game, Ability source) { } } -enum NumberOfOpponentsWhoLostLife implements DynamicValue { +enum KaitoBaneOfNightmaresCount implements DynamicValue { instance; @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return this.calculate(game, sourceAbility.getControllerId()); - } - - public int calculate(Game game, UUID controllerId) { PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class); if (watcher != null) { - return watcher.getNumberOfOpponentsWhoLostLife(controllerId, game); + return watcher.getNumberOfOpponentsWhoLostLife(sourceAbility.getControllerId(), game); } return 0; } @Override - public NumberOfOpponentsWhoLostLife copy() { + public KaitoBaneOfNightmaresCount copy() { return instance; }