[SPM] Implement Spider-Punk

This commit is contained in:
jmlundeen 2025-08-30 15:08:57 -05:00
parent 642362e99d
commit 8e0a222f9b
3 changed files with 251 additions and 0 deletions

View file

@ -0,0 +1,157 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.DamageCantBePreventedEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.RiotAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class SpiderPunk extends CardImpl {
static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.SPIDER, "Spiders you control");
public SpiderPunk(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIDER);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.HERO);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// Riot
this.addAbility(new RiotAbility());
// Other Spiders you control have riot.
Ability ability = new SimpleStaticAbility(new SpiderPunkRiotETBEffect());
ability.addEffect(new GainAbilityControlledEffect(new RiotAbility(), Duration.WhileOnBattlefield, filter, true)
.setText(""));
this.addAbility(ability);
// Spells and abilities can't be countered.
this.addAbility(new SimpleStaticAbility(new SpiderPunkCantCounterEffect()));
// Damage can't be prevented.
this.addAbility(new SimpleStaticAbility(new DamageCantBePreventedEffect(Duration.WhileOnBattlefield)));
}
private SpiderPunk(final SpiderPunk card) {
super(card);
}
@Override
public SpiderPunk copy() {
return new SpiderPunk(this);
}
}
class SpiderPunkCantCounterEffect extends ContinuousRuleModifyingEffectImpl {
SpiderPunkCantCounterEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "Spells and abilities can't be countered";
}
private SpiderPunkCantCounterEffect(final SpiderPunkCantCounterEffect effect) {
super(effect);
}
@Override
public SpiderPunkCantCounterEffect copy() {
return new SpiderPunkCantCounterEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.COUNTER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Object object = game.getObject(event.getTargetId());
return object instanceof Spell || object instanceof StackAbility;
}
}
//TODO: Remove after fixing continuous effects working on entering permanents
class SpiderPunkRiotETBEffect extends ReplacementEffectImpl {
SpiderPunkRiotETBEffect() {
super(Duration.WhileOnBattlefield, Outcome.BoostCreature);
staticText = "Other Spiders you control have riot";
}
private SpiderPunkRiotETBEffect(SpiderPunkRiotETBEffect effect) {
super(effect);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
return creature != null
&& creature.getId() != source.getSourceId()
&& creature.isControlledBy(source.getControllerId())
&& creature.isCreature(game)
&& !(creature instanceof PermanentToken);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
Player player = game.getPlayer(source.getControllerId());
if (creature == null || player == null) {
return false;
}
if (player.chooseUse(
outcome, "Have " + creature.getLogName() + " enter the battlefield with a +1/+1 counter on it or with haste?",
null, "+1/+1 counter", "Haste", source, game
)) {
game.informPlayers(player.getLogName() + " choose to put a +1/+1 counter on " + creature.getName());
creature.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects());
} else {
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
effect.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game) + 1));
game.addEffect(effect, source);
}
return false;
}
@Override
public SpiderPunkRiotETBEffect copy() {
return new SpiderPunkRiotETBEffect(this);
}
}

View file

@ -109,6 +109,9 @@ public final class MarvelsSpiderMan extends ExpansionSet {
cards.add(new SetCardInfo("Spider-Man Noir", 67, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Spider-Man, Brooklyn Visionary", 115, Rarity.COMMON, mage.cards.s.SpiderManBrooklynVisionary.class));
cards.add(new SetCardInfo("Spider-Man, Web-Slinger", 16, Rarity.COMMON, mage.cards.s.SpiderManWebSlinger.class));
cards.add(new SetCardInfo("Spider-Punk", 207, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Spider-Punk", 210, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Spider-Punk", 92, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Spider-Rex, Daring Dino", 116, Rarity.COMMON, mage.cards.s.SpiderRexDaringDino.class));
cards.add(new SetCardInfo("Spider-Suit", 176, Rarity.UNCOMMON, mage.cards.s.SpiderSuit.class));
cards.add(new SetCardInfo("Starling, Aerial Ally", 18, Rarity.COMMON, mage.cards.s.StarlingAerialAlly.class));

View file

@ -0,0 +1,91 @@
package org.mage.test.cards.single.spm;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.keyword.RiotAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.target.TargetPlayer;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author Jmlundeen
*/
public class SpiderPunkTest extends CardTestPlayerBase {
/*
Spider-Punk
{1}{R}
Legendary Creature - Spider Human Hero
Riot
Other Spiders you control have riot.
Spells and abilities can't be countered.
Damage can't be prevented.
2/1
*/
private static final String spiderPunk = "Spider-Punk";
/*
Counterspell
{U}{U}
Instant
Counter target spell.
*/
private static final String counterspell = "Counterspell";
/*
Disallow
{1}{U}{U}
Instant
Counter target spell, activated ability, or triggered ability.
*/
private static final String disallow = "Disallow";
/*
Giant Spider
{3}{G}
Creature - Spider
Reach <i>(This creature can block creatures with flying.)</i>
2/4
*/
private static final String giantSpider = "Giant Spider";
@Test
public void testSpiderPunk() {
setStrictChooseMode(true);
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(2), new TapSourceCost());
ability.addTarget(new TargetPlayer(1));
addCustomCardWithAbility("{T} deal damage", playerA, ability);
addCard(Zone.BATTLEFIELD, playerA, spiderPunk);
setChoice(playerA, "No"); // Haste - Spider-Punk
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
addCard(Zone.HAND, playerA, giantSpider);
addCard(Zone.HAND, playerB, counterspell);
addCard(Zone.HAND, playerB, disallow);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, giantSpider);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, counterspell, giantSpider);
setChoice(playerA, "No"); // Haste
attack(1, playerA, giantSpider);
attack(1, playerA, spiderPunk);
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: {this} deals 2 damage", playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, disallow);
addTarget(playerB, "stack ability");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 20 - 2 - 2 - 2);
assertAbilityCount(playerA, giantSpider, RiotAbility.class, 1);
}
}