From bf528dfc59ca82ab4c8216d83cb466589b22e2aa Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 26 May 2019 17:18:18 -0400 Subject: [PATCH] Implemented Splicer's Skill --- Mage.Sets/src/mage/cards/s/SplicersSkill.java | 35 ++++ Mage.Sets/src/mage/sets/ModernHorizons.java | 1 + .../SpliceOntoInstantOrSorceryAbility.java | 182 ++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SplicersSkill.java create mode 100644 Mage/src/main/java/mage/abilities/keyword/SpliceOntoInstantOrSorceryAbility.java diff --git a/Mage.Sets/src/mage/cards/s/SplicersSkill.java b/Mage.Sets/src/mage/cards/s/SplicersSkill.java new file mode 100644 index 0000000000..36522d00df --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SplicersSkill.java @@ -0,0 +1,35 @@ +package mage.cards.s; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.SpliceOntoInstantOrSorceryAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.GolemToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SplicersSkill extends CardImpl { + + public SplicersSkill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}"); + + // Create a 3/3 colorless Golem artifact creature token. + this.getSpellAbility().addEffect(new CreateTokenEffect(new GolemToken())); + + // Splice onto instant or sorcery {3}{W} + this.addAbility(new SpliceOntoInstantOrSorceryAbility("{3}{W}")); + } + + private SplicersSkill(final SplicersSkill card) { + super(card); + } + + @Override + public SplicersSkill copy() { + return new SplicersSkill(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ModernHorizons.java b/Mage.Sets/src/mage/sets/ModernHorizons.java index e2be95ef4a..208398cc2f 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons.java @@ -117,6 +117,7 @@ public final class ModernHorizons extends ExpansionSet { cards.add(new SetCardInfo("Snow-Covered Mountain", 253, Rarity.LAND, mage.cards.s.SnowCoveredMountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Snow-Covered Plains", 250, Rarity.LAND, mage.cards.s.SnowCoveredPlains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Snow-Covered Swamp", 252, Rarity.LAND, mage.cards.s.SnowCoveredSwamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Splicer's Skill", 31, Rarity.UNCOMMON, mage.cards.s.SplicersSkill.class)); cards.add(new SetCardInfo("Spore Frog", 180, Rarity.COMMON, mage.cards.s.SporeFrog.class)); cards.add(new SetCardInfo("Springbloom Druid", 181, Rarity.COMMON, mage.cards.s.SpringbloomDruid.class)); cards.add(new SetCardInfo("Squirrel Nest", 182, Rarity.UNCOMMON, mage.cards.s.SquirrelNest.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/SpliceOntoInstantOrSorceryAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpliceOntoInstantOrSorceryAbility.java new file mode 100644 index 0000000000..52136ebe7f --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/SpliceOntoInstantOrSorceryAbility.java @@ -0,0 +1,182 @@ +package mage.abilities.keyword; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.SpliceCardEffectImpl; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SpellAbilityType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; + +import java.util.Iterator; + +/** + * 702.45. Splice + *

+ * 702.45a Splice is a static ability that functions while a card is in your + * hand. "Splice onto [subtype] [cost]" means "You may reveal this card from + * your hand as you cast a [subtype] spell. If you do, copy this card's text box + * onto that spell and pay [cost] as an additional cost to cast that spell." + * Paying a card's splice cost follows the rules for paying additional costs in + * rules 601.2b and 601.2e-g. + *

+ * Example: Since the card with splice remains in the player's hand, it can + * later be cast normally or spliced onto another spell. It can even be + * discarded to pay a "discard a card" cost of the spell it's spliced onto. + *

+ * 702.45b You can't choose to use a splice ability if you can't make the + * required choices (targets, etc.) for that card's instructions. You can't + * splice any one card onto the same spell more than once. If you're splicing + * more than one card onto a spell, reveal them all at once and choose the order + * in which their instructions will be followed. The instructions on the main + * spell have to be followed first. + *

+ * 702.45c The spell has the characteristics of the main spell, plus the text + * boxes of each of the spliced cards. The spell doesn't gain any other + * characteristics (name, mana cost, color, supertypes, card types, subtypes, + * etc.) of the spliced cards. Text copied onto the spell that refers to a card + * by name refers to the spell on the stack, not the card from which the text + * was copied. + *

+ * Example: Glacial Ray is a red card with splice onto Arcane that reads, + * "Glacial Ray deals 2 damage to any target." Suppose Glacial Ray is spliced + * onto Reach Through Mists, a blue spell. The spell is still blue, and Reach + * Through Mists deals the damage. This means that the ability can target a + * creature with protection from red and deal 2 damage to that creature. + *

+ * 702.45d Choose targets for the added text normally (see rule 601.2c). Note + * that a spell with one or more targets will be countered if all of its targets + * are illegal on resolution. + *

+ * 702.45e The spell loses any splice changes once it leaves the stack (for + * example, when it's countered, it's exiled, or it resolves). + *

+ * Rulings + *

+ * You must reveal all of the cards you intend to splice at the same time. Each + * individual card can only be spliced once onto a spell. If you have more than + * one card with the same name in your hand, you may splice both of them onto + * the spell. A card with a splice ability can't be spliced onto itself because + * the spell is on the stack (and not in your hand) when you reveal the cards + * you want to splice onto it. The target for a card that's spliced onto a spell + * may be the same as the target chosen for the original spell or for another + * spliced-on card. (A recent change to the targeting rules allows this, but + * most other cards are unaffected by the change.) If you splice a targeted card + * onto an untargeted spell, the entire spell will be countered if the target + * isn't legal when the spell resolves. If you splice an untargeted card onto a + * targeted spell, the entire spell will be countered if the target isn't legal + * when the spell resolves. A spell is countered on resolution only if *all* of + * its targets are illegal (or the spell is countered by an effect). + * + * @author LevelX2 + */ +public class SpliceOntoInstantOrSorceryAbility extends SimpleStaticAbility { + + private static final String KEYWORD_TEXT = "Splice onto instant or sorcery"; + private Costs spliceCosts = new CostsImpl<>(); + private boolean nonManaCosts = false; + + public SpliceOntoInstantOrSorceryAbility(String manaString) { + super(Zone.HAND, new SpliceOntoInstantOrSorceryEffect()); + spliceCosts.add(new ManaCostsImpl<>(manaString)); + } + + public SpliceOntoInstantOrSorceryAbility(Cost cost) { + super(Zone.HAND, new SpliceOntoInstantOrSorceryEffect()); + spliceCosts.add(cost); + nonManaCosts = true; + } + + private SpliceOntoInstantOrSorceryAbility(final SpliceOntoInstantOrSorceryAbility ability) { + super(ability); + this.spliceCosts = ability.spliceCosts.copy(); + this.nonManaCosts = ability.nonManaCosts; + } + + @Override + public SimpleStaticAbility copy() { + return new SpliceOntoInstantOrSorceryAbility(this); + } + + Costs getSpliceCosts() { + return spliceCosts; + } + + @Override + public String getRule() { + StringBuilder sb = new StringBuilder(); + sb.append(KEYWORD_TEXT).append(nonManaCosts ? "—" : " "); + sb.append(spliceCosts.getText()).append(nonManaCosts ? ". " : " "); + sb.append("(As you cast an instant or sorcery spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell.)"); + return sb.toString(); + } +} + +class SpliceOntoInstantOrSorceryEffect extends SpliceCardEffectImpl { + + SpliceOntoInstantOrSorceryEffect() { + super(Duration.WhileOnBattlefield, Outcome.Copy); + staticText = "Splice onto Instant or Sorcery"; + } + + private SpliceOntoInstantOrSorceryEffect(final SpliceOntoInstantOrSorceryEffect effect) { + super(effect); + } + + @Override + public SpliceOntoInstantOrSorceryEffect copy() { + return new SpliceOntoInstantOrSorceryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Player controller = game.getPlayer(source.getControllerId()); + Card spliceCard = game.getCard(source.getSourceId()); + if (spliceCard != null && controller != null) { + Spell spell = game.getStack().getSpell(abilityToModify.getId()); + if (spell != null) { + SpellAbility splicedAbility = spliceCard.getSpellAbility().copy(); + splicedAbility.setSpellAbilityType(SpellAbilityType.SPLICE); + splicedAbility.setSourceId(abilityToModify.getSourceId()); + spell.addSpellAbility(splicedAbility); + for (Iterator it = ((SpliceOntoInstantOrSorceryAbility) source).getSpliceCosts().iterator(); it.hasNext(); ) { + spell.getSpellAbility().getCosts().add(((Cost) it.next()).copy()); + } + } + return true; + } + return false; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + MageObject object = game.getObject(abilityToModify.getSourceId()); + if (object != null && object.isInstantOrSorcery()) { + return spliceSpellCanBeActivated(source, game); + } + return false; + } + + private boolean spliceSpellCanBeActivated(Ability source, Game game) { + // check if spell can be activated (protection problem not solved because effect will be used from the base spell?) + Card card = game.getCard(source.getSourceId()); + if (card != null) { + if (card.getManaCost().isEmpty()) { // e.g. Evermind + return card.getSpellAbility().spellCanBeActivatedRegularlyNow(source.getControllerId(), game); + } else { + return card.getSpellAbility().canActivate(source.getControllerId(), game).canActivate(); + } + } + return false; + } +}