[MKM] Implement Fugitive Codebreaker

This commit is contained in:
Susucre 2024-04-05 20:16:17 +02:00
parent acf5f47fe7
commit 1ff8cf01cf
5 changed files with 153 additions and 4 deletions

View file

@ -0,0 +1,98 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.discard.DiscardHandControllerEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.DisguiseAbility;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.ProwessAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Susucr
*/
public final class FugitiveCodebreaker extends CardImpl {
public FugitiveCodebreaker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}");
this.subtype.add(SubType.GOBLIN);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// Prowess
this.addAbility(new ProwessAbility());
// Haste
this.addAbility(HasteAbility.getInstance());
// Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard.
this.addAbility(new FugitiveCodebreakerDisguiseAbility(this));
// When Fugitive Codebreaker is turned face up, discard your hand, then draw three cards.
Ability ability = new TurnedFaceUpSourceTriggeredAbility(new DiscardHandControllerEffect());
ability.addEffect(new DrawCardSourceControllerEffect(3).concatBy(", then"));
this.addAbility(ability);
}
private FugitiveCodebreaker(final FugitiveCodebreaker card) {
super(card);
}
@Override
public FugitiveCodebreaker copy() {
return new FugitiveCodebreaker(this);
}
}
class FugitiveCodebreakerDisguiseAbility extends DisguiseAbility {
static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD);
private static final Hint hint = new ValueHint("instant and/or sorcery in your graveyard", xValue);
FugitiveCodebreakerDisguiseAbility(Card card) {
super(card, new ManaCostsImpl<>("{5}{R}"), FugitiveCodebreakerAdjuster.instance);
addHint(hint);
}
private FugitiveCodebreakerDisguiseAbility(final FugitiveCodebreakerDisguiseAbility ability) {
super(ability);
}
@Override
public FugitiveCodebreakerDisguiseAbility copy() {
return new FugitiveCodebreakerDisguiseAbility(this);
}
@Override
public String getRule() {
return "Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard.";
}
}
enum FugitiveCodebreakerAdjuster implements CostAdjuster {
instance;
@Override
public void adjustCosts(Ability ability, Game game) {
CardUtil.reduceCost(ability, FugitiveCodebreakerDisguiseAbility.xValue.calculate(game, ability, null));
}
}

View file

@ -126,6 +126,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Forum Familiar", 16, Rarity.UNCOMMON, mage.cards.f.ForumFamiliar.class)); cards.add(new SetCardInfo("Forum Familiar", 16, Rarity.UNCOMMON, mage.cards.f.ForumFamiliar.class));
cards.add(new SetCardInfo("Frantic Scapegoat", 126, Rarity.UNCOMMON, mage.cards.f.FranticScapegoat.class)); cards.add(new SetCardInfo("Frantic Scapegoat", 126, Rarity.UNCOMMON, mage.cards.f.FranticScapegoat.class));
cards.add(new SetCardInfo("Fugitive Codebreaker", 127, Rarity.RARE, mage.cards.f.FugitiveCodebreaker.class));
cards.add(new SetCardInfo("Furtive Courier", 59, Rarity.UNCOMMON, mage.cards.f.FurtiveCourier.class)); cards.add(new SetCardInfo("Furtive Courier", 59, Rarity.UNCOMMON, mage.cards.f.FurtiveCourier.class));
cards.add(new SetCardInfo("Fuss // Bother", 248, Rarity.UNCOMMON, mage.cards.f.FussBother.class)); cards.add(new SetCardInfo("Fuss // Bother", 248, Rarity.UNCOMMON, mage.cards.f.FussBother.class));
cards.add(new SetCardInfo("Gadget Technician", 204, Rarity.COMMON, mage.cards.g.GadgetTechnician.class)); cards.add(new SetCardInfo("Gadget Technician", 204, Rarity.COMMON, mage.cards.g.GadgetTechnician.class));

View file

@ -197,4 +197,27 @@ public class DisguiseTest extends CardTestPlayerBase {
assertLife(playerA, 20); assertLife(playerA, 20);
} }
@Test
public void testCostAdjuster() {
/* Fugitive Codebreaker {1}{R}
* Creature Goblin Rogue
* Prowess, haste
* Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard.
* When Fugitive Codebreaker is turned face up, discard your hand, then draw three cards.
*/
String codebreaker = "Fugitive Codebreaker";
addCard(Zone.HAND, playerA, codebreaker);
addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, codebreaker + " using Disguise", true);
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{5}{R}:");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertHandCount(playerA, 3);
}
} }

View file

@ -7,6 +7,7 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.TurnFaceUpAbility; import mage.abilities.common.TurnFaceUpAbility;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.Costs; import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl; import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
@ -83,7 +84,22 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
this(createCosts(cost), objectReference, duration, faceDownType); this(createCosts(cost), objectReference, duration, faceDownType);
} }
public BecomesFaceDownCreatureEffect(Costs<Cost> turnFaceUpCosts, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType) { public BecomesFaceDownCreatureEffect(Costs<Cost> cost, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType) {
this(createCosts(cost), objectReference, duration, faceDownType, null);
}
public BecomesFaceDownCreatureEffect(Cost cost, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType, CostAdjuster costAdjuster) {
this(createCosts(cost), objectReference, duration, faceDownType, costAdjuster);
}
/**
* @param turnFaceUpCosts costs for the turn face up ability
* @param objectReference
* @param duration
* @param faceDownType type of face down (morph, disguise, manifest, etc...)
* @param costAdjuster optional costAdjuster for the turn face up ability
*/
public BecomesFaceDownCreatureEffect(Costs<Cost> turnFaceUpCosts, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType, CostAdjuster costAdjuster) {
super(duration, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.BecomeCreature); super(duration, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.BecomeCreature);
this.objectReference = objectReference; this.objectReference = objectReference;
this.zoneChangeCounter = Integer.MIN_VALUE; this.zoneChangeCounter = Integer.MIN_VALUE;
@ -91,7 +107,10 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
// add additional face up and information abilities // add additional face up and information abilities
if (turnFaceUpCosts != null) { if (turnFaceUpCosts != null) {
// face up for all // face up for all
this.additionalAbilities.add(new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)); this.additionalAbilities.add(
new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)
.setCostAdjuster(costAdjuster)
);
switch (faceDownType) { switch (faceDownType) {
case MORPHED: case MORPHED:

View file

@ -4,12 +4,14 @@ import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.Costs; import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl; import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.SpellAbilityCastMode; import mage.constants.SpellAbilityCastMode;
import mage.constants.SpellAbilityType; import mage.constants.SpellAbilityType;
import mage.constants.TimingRule; import mage.constants.TimingRule;
@ -68,6 +70,10 @@ public class DisguiseAbility extends SpellAbility {
protected Costs<Cost> disguiseCosts; protected Costs<Cost> disguiseCosts;
public DisguiseAbility(Card card, Cost disguiseCost) { public DisguiseAbility(Card card, Cost disguiseCost) {
this(card, disguiseCost, null);
}
public DisguiseAbility(Card card, Cost disguiseCost, CostAdjuster costAdjuster) {
super(new GenericManaCost(3), card.getName()); super(new GenericManaCost(3), card.getName());
this.timing = TimingRule.SORCERY; this.timing = TimingRule.SORCERY;
this.disguiseCosts = new CostsImpl<>(); this.disguiseCosts = new CostsImpl<>();
@ -77,13 +83,15 @@ public class DisguiseAbility extends SpellAbility {
// face down effect (hidden by default, visible in face down objects) // face down effect (hidden by default, visible in face down objects)
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect( Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
this.disguiseCosts, BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED)); this.disguiseCosts, null, Duration.WhileOnBattlefield,
BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED, costAdjuster
));
ability.setWorksFaceDown(true); ability.setWorksFaceDown(true);
ability.setRuleVisible(false); ability.setRuleVisible(false);
addSubAbility(ability); addSubAbility(ability);
} }
private DisguiseAbility(final DisguiseAbility ability) { protected DisguiseAbility(final DisguiseAbility ability) {
super(ability); super(ability);
this.disguiseCosts = ability.disguiseCosts; // can't be changed TODO: looks buggy, need research this.disguiseCosts = ability.disguiseCosts; // can't be changed TODO: looks buggy, need research
} }