mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[OTJ] Implementing "spree" mechanic (#12018)
* [OTJ] Implement Unfortunate Accident * fix errors * a few more things * [OTJ] Implement Three Steps Ahead * [OTJ] Implement Caught in the Crossfire * [OTJ] Implement Insatiable Avarice * add test * [OTJ] Implement Explosive Derailment * [OTJ] Implement Requisition Raid * [OTJ] Implement Rustler Rampage * add comment to test * [OTJ] Implement Metamorphic Blast * [OTJ] Implement Final Showdown * rework cost addition, add test * move cost application to its own loop
This commit is contained in:
parent
fa67b0450f
commit
ba20e97b71
18 changed files with 740 additions and 2 deletions
53
Mage.Sets/src/mage/cards/c/CaughtInTheCrossfire.java
Normal file
53
Mage.Sets/src/mage/cards/c/CaughtInTheCrossfire.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.DamageAllEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.OutlawPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class CaughtInTheCrossfire extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterCreaturePermanent("outlaw creature");
|
||||
private static final FilterPermanent filter2 = new FilterCreaturePermanent("non-outlaw creature");
|
||||
|
||||
static {
|
||||
filter.add(OutlawPredicate.instance);
|
||||
filter.add(Predicates.not(OutlawPredicate.instance));
|
||||
}
|
||||
|
||||
public CaughtInTheCrossfire(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{R}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {1} -- Caught in the Crossfire deals 2 damage to each outlaw creature.
|
||||
this.getSpellAbility().addEffect(new DamageAllEffect(2, filter));
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(1));
|
||||
|
||||
// + {1} -- Caught in the Crossfire deals 2 damage to each non-outlaw creature.
|
||||
this.getSpellAbility().addMode(new Mode(new DamageAllEffect(2, filter2))
|
||||
.withCost(new GenericManaCost(1)));
|
||||
}
|
||||
|
||||
private CaughtInTheCrossfire(final CaughtInTheCrossfire card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaughtInTheCrossfire copy() {
|
||||
return new CaughtInTheCrossfire(this);
|
||||
}
|
||||
}
|
||||
46
Mage.Sets/src/mage/cards/e/ExplosiveDerailment.java
Normal file
46
Mage.Sets/src/mage/cards/e/ExplosiveDerailment.java
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package mage.cards.e;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.target.common.TargetArtifactPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ExplosiveDerailment extends CardImpl {
|
||||
|
||||
public ExplosiveDerailment(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {2} -- Explosive Derailment deals 4 damage to target creature.
|
||||
this.getSpellAbility().addEffect(new DamageTargetEffect(4));
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(2));
|
||||
|
||||
// + {2} -- Destroy target artifact.
|
||||
this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect())
|
||||
.addTarget(new TargetArtifactPermanent())
|
||||
.withCost(new GenericManaCost(2)));
|
||||
}
|
||||
|
||||
private ExplosiveDerailment(final ExplosiveDerailment card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplosiveDerailment copy() {
|
||||
return new ExplosiveDerailment(this);
|
||||
}
|
||||
}
|
||||
94
Mage.Sets/src/mage/cards/f/FinalShowdown.java
Normal file
94
Mage.Sets/src/mage/cards/f/FinalShowdown.java
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DestroyAllEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.LoseAllAbilitiesAllEffect;
|
||||
import mage.abilities.keyword.IndestructibleAbility;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class FinalShowdown extends CardImpl {
|
||||
|
||||
public FinalShowdown(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {1} -- All creatures lose all abilities until end of turn.
|
||||
this.getSpellAbility().addEffect(new LoseAllAbilitiesAllEffect(
|
||||
StaticFilters.FILTER_PERMANENT_CREATURES, Duration.EndOfTurn
|
||||
).setText("all creatures lose all abilities until end of turn"));
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(1));
|
||||
|
||||
// + {1} -- Choose a creature you control. It gains indestructible until end of turn.
|
||||
this.getSpellAbility().addMode(new Mode(new FinalShowdownEffect()).withCost(new GenericManaCost(1)));
|
||||
|
||||
// + {3}{W}{W} -- Destroy all creatures.
|
||||
this.getSpellAbility().addMode(new Mode(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES))
|
||||
.withCost(new ManaCostsImpl<>("{3}{W}{W}")));
|
||||
}
|
||||
|
||||
private FinalShowdown(final FinalShowdown card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinalShowdown copy() {
|
||||
return new FinalShowdown(this);
|
||||
}
|
||||
}
|
||||
|
||||
class FinalShowdownEffect extends OneShotEffect {
|
||||
|
||||
FinalShowdownEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "choose a creature you control. It gains indestructible until end of turn";
|
||||
}
|
||||
|
||||
private FinalShowdownEffect(final FinalShowdownEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinalShowdownEffect copy() {
|
||||
return new FinalShowdownEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null || !game.getBattlefield().contains(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE, source, game, 1
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
TargetPermanent target = new TargetControlledCreaturePermanent();
|
||||
target.withNotTarget(true);
|
||||
player.choose(outcome, target, source, game);
|
||||
game.addEffect(new GainAbilityTargetEffect(
|
||||
IndestructibleAbility.getInstance(), Duration.EndOfTurn
|
||||
).setTargetPointer(new FixedTarget(target.getFirstTarget(), game)), source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
48
Mage.Sets/src/mage/cards/i/InsatiableAvarice.java
Normal file
48
Mage.Sets/src/mage/cards/i/InsatiableAvarice.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package mage.cards.i;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.DrawCardTargetEffect;
|
||||
import mage.abilities.effects.common.LoseLifeTargetEffect;
|
||||
import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class InsatiableAvarice extends CardImpl {
|
||||
|
||||
public InsatiableAvarice(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {2} -- Search your library for a card, then shuffle and put that card on top.
|
||||
this.getSpellAbility().addEffect(new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(), false));
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(2));
|
||||
|
||||
// + {B}{B} -- Target player draws three cards and loses 3 life.
|
||||
this.getSpellAbility().addMode(new Mode(new DrawCardTargetEffect(3))
|
||||
.addEffect(new LoseLifeTargetEffect(3).setText("and loses 3 life"))
|
||||
.addTarget(new TargetPlayer())
|
||||
.withCost(new ManaCostsImpl<>("{B}{B}")));
|
||||
}
|
||||
|
||||
private InsatiableAvarice(final InsatiableAvarice card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsatiableAvarice copy() {
|
||||
return new InsatiableAvarice(this);
|
||||
}
|
||||
}
|
||||
48
Mage.Sets/src/mage/cards/m/MetamorphicBlast.java
Normal file
48
Mage.Sets/src/mage/cards/m/MetamorphicBlast.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.DrawCardTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.game.permanent.token.custom.CreatureToken;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class MetamorphicBlast extends CardImpl {
|
||||
|
||||
public MetamorphicBlast(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
|
||||
|
||||
// Spree
|
||||
// + {1} -- Until end of turn, target creature becomes a white Rabbit with base power and toughness 0/1.
|
||||
this.getSpellAbility().addEffect(new BecomesCreatureTargetEffect(new CreatureToken(
|
||||
0, 1, "white Rabbit with base power and toughness 0/1"
|
||||
).withSubType(SubType.RABBIT).withColor("W"), false, false, Duration.EndOfTurn));
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(1));
|
||||
|
||||
// + {3} -- Target player draws two cards.
|
||||
this.getSpellAbility().addMode(new Mode(new DrawCardTargetEffect(2))
|
||||
.addTarget(new TargetPlayer())
|
||||
.withCost(new GenericManaCost(3)));
|
||||
}
|
||||
|
||||
private MetamorphicBlast(final MetamorphicBlast card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetamorphicBlast copy() {
|
||||
return new MetamorphicBlast(this);
|
||||
}
|
||||
}
|
||||
86
Mage.Sets/src/mage/cards/r/RequisitionRaid.java
Normal file
86
Mage.Sets/src/mage/cards/r/RequisitionRaid.java
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetArtifactPermanent;
|
||||
import mage.target.common.TargetEnchantmentPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class RequisitionRaid extends CardImpl {
|
||||
|
||||
public RequisitionRaid(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {1} -- Destroy target artifact.
|
||||
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
||||
this.getSpellAbility().addTarget(new TargetArtifactPermanent());
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(1));
|
||||
|
||||
// + {1} -- Destroy target enchantment.
|
||||
this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect())
|
||||
.addTarget(new TargetEnchantmentPermanent())
|
||||
.withCost(new GenericManaCost(1)));
|
||||
|
||||
// + {1} -- Put a +1/+1 counter on each creature target player controls.
|
||||
this.getSpellAbility().addMode(new Mode(new RequisitionRaidEffect())
|
||||
.addTarget(new TargetPlayer())
|
||||
.withCost(new GenericManaCost(1)));
|
||||
}
|
||||
|
||||
private RequisitionRaid(final RequisitionRaid card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequisitionRaid copy() {
|
||||
return new RequisitionRaid(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RequisitionRaidEffect extends OneShotEffect {
|
||||
|
||||
RequisitionRaidEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "put a +1/+1 counter on each creature target player controls";
|
||||
}
|
||||
|
||||
private RequisitionRaidEffect(final RequisitionRaidEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequisitionRaidEffect copy() {
|
||||
return new RequisitionRaidEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE,
|
||||
getTargetPointer().getFirst(game, source), source, game
|
||||
)) {
|
||||
permanent.addCounters(CounterType.P1P1.createInstance(), source, game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
80
Mage.Sets/src/mage/cards/r/RustlerRampage.java
Normal file
80
Mage.Sets/src/mage/cards/r/RustlerRampage.java
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class RustlerRampage extends CardImpl {
|
||||
|
||||
public RustlerRampage(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {1} -- Untap all creatures target player controls.
|
||||
this.getSpellAbility().addEffect(new RustlerRampageEffect());
|
||||
this.getSpellAbility().addTarget(new TargetPlayer());
|
||||
this.getSpellAbility().withFirstModeCost(new GenericManaCost(1));
|
||||
|
||||
// + {1} -- Target creature gains double strike until end of turn.
|
||||
this.getSpellAbility().addMode(new Mode(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance()))
|
||||
.addTarget(new TargetCreaturePermanent())
|
||||
.withCost(new GenericManaCost(1)));
|
||||
}
|
||||
|
||||
private RustlerRampage(final RustlerRampage card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RustlerRampage copy() {
|
||||
return new RustlerRampage(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RustlerRampageEffect extends OneShotEffect {
|
||||
|
||||
RustlerRampageEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "untap all creatures target player controls";
|
||||
}
|
||||
|
||||
private RustlerRampageEffect(final RustlerRampageEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RustlerRampageEffect copy() {
|
||||
return new RustlerRampageEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE,
|
||||
getTargetPointer().getFirst(game, source), source, game
|
||||
)) {
|
||||
permanent.untap(game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
53
Mage.Sets/src/mage/cards/t/ThreeStepsAhead.java
Normal file
53
Mage.Sets/src/mage/cards/t/ThreeStepsAhead.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.CounterTargetEffect;
|
||||
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
|
||||
import mage.abilities.effects.common.DrawDiscardControllerEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.TargetSpell;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ThreeStepsAhead extends CardImpl {
|
||||
|
||||
public ThreeStepsAhead(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {1}{U} -- Counter target spell.
|
||||
this.getSpellAbility().addEffect(new CounterTargetEffect());
|
||||
this.getSpellAbility().addTarget(new TargetSpell());
|
||||
this.getSpellAbility().withFirstModeCost(new ManaCostsImpl<>("{1}{U}"));
|
||||
|
||||
// + {3} -- Create a token that's a copy of target artifact or creature you control.
|
||||
this.getSpellAbility().addMode(new Mode(new CreateTokenCopyTargetEffect())
|
||||
.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE))
|
||||
.withCost(new GenericManaCost(3)));
|
||||
|
||||
// + {2} -- Draw two cards, then discard a card.
|
||||
this.getSpellAbility().addMode(new Mode(new DrawDiscardControllerEffect(2, 1))
|
||||
.withCost(new GenericManaCost(2)));
|
||||
}
|
||||
|
||||
private ThreeStepsAhead(final ThreeStepsAhead card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreeStepsAhead copy() {
|
||||
return new ThreeStepsAhead(this);
|
||||
}
|
||||
}
|
||||
46
Mage.Sets/src/mage/cards/u/UnfortunateAccident.java
Normal file
46
Mage.Sets/src/mage/cards/u/UnfortunateAccident.java
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package mage.cards.u;
|
||||
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
import mage.abilities.keyword.SpreeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.game.permanent.token.MercenaryToken;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class UnfortunateAccident extends CardImpl {
|
||||
|
||||
public UnfortunateAccident(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}");
|
||||
|
||||
// Spree
|
||||
this.addAbility(new SpreeAbility(this));
|
||||
|
||||
// + {2}{B} -- Destroy target creature.
|
||||
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||
this.getSpellAbility().withFirstModeCost(new ManaCostsImpl<>("{2}{B}"));
|
||||
|
||||
// + {1} -- Create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."
|
||||
this.getSpellAbility().addMode(new Mode(new CreateTokenEffect(new MercenaryToken()))
|
||||
.withCost(new GenericManaCost(1)));
|
||||
}
|
||||
|
||||
private UnfortunateAccident(final UnfortunateAccident card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnfortunateAccident copy() {
|
||||
return new UnfortunateAccident(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Cactusfolk Sureshot", 199, Rarity.UNCOMMON, mage.cards.c.CactusfolkSureshot.class));
|
||||
cards.add(new SetCardInfo("Calamity, Galloping Inferno", 116, Rarity.RARE, mage.cards.c.CalamityGallopingInferno.class));
|
||||
cards.add(new SetCardInfo("Canyon Crab", 40, Rarity.UNCOMMON, mage.cards.c.CanyonCrab.class));
|
||||
cards.add(new SetCardInfo("Caught in the Crossfire", 117, Rarity.UNCOMMON, mage.cards.c.CaughtInTheCrossfire.class));
|
||||
cards.add(new SetCardInfo("Caustic Bronco", 82, Rarity.RARE, mage.cards.c.CausticBronco.class));
|
||||
cards.add(new SetCardInfo("Colossal Rattlewurm", 159, Rarity.RARE, mage.cards.c.ColossalRattlewurm.class));
|
||||
cards.add(new SetCardInfo("Concealed Courtyard", 268, Rarity.RARE, mage.cards.c.ConcealedCourtyard.class));
|
||||
|
|
@ -77,10 +78,12 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Eriette's Lullaby", 10, Rarity.COMMON, mage.cards.e.EriettesLullaby.class));
|
||||
cards.add(new SetCardInfo("Eroded Canyon", 256, Rarity.COMMON, mage.cards.e.ErodedCanyon.class));
|
||||
cards.add(new SetCardInfo("Ertha Jo, Frontier Mentor", 203, Rarity.UNCOMMON, mage.cards.e.ErthaJoFrontierMentor.class));
|
||||
cards.add(new SetCardInfo("Explosive Derailment", 122, Rarity.COMMON, mage.cards.e.ExplosiveDerailment.class));
|
||||
cards.add(new SetCardInfo("Failed Fording", 47, Rarity.COMMON, mage.cards.f.FailedFording.class));
|
||||
cards.add(new SetCardInfo("Fake Your Own Death", 87, Rarity.COMMON, mage.cards.f.FakeYourOwnDeath.class));
|
||||
cards.add(new SetCardInfo("Ferocification", 123, Rarity.UNCOMMON, mage.cards.f.Ferocification.class));
|
||||
cards.add(new SetCardInfo("Festering Gulch", 257, Rarity.COMMON, mage.cards.f.FesteringGulch.class));
|
||||
cards.add(new SetCardInfo("Final Showdown", 11, Rarity.MYTHIC, mage.cards.f.FinalShowdown.class));
|
||||
cards.add(new SetCardInfo("Fleeting Reflection", 49, Rarity.UNCOMMON, mage.cards.f.FleetingReflection.class));
|
||||
cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Forlorn Flats", 258, Rarity.COMMON, mage.cards.f.ForlornFlats.class));
|
||||
|
|
@ -104,6 +107,7 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("High Noon", 15, Rarity.RARE, mage.cards.h.HighNoon.class));
|
||||
cards.add(new SetCardInfo("Holy Cow", 16, Rarity.COMMON, mage.cards.h.HolyCow.class));
|
||||
cards.add(new SetCardInfo("Honest Rutstein", 207, Rarity.UNCOMMON, mage.cards.h.HonestRutstein.class));
|
||||
cards.add(new SetCardInfo("Insatiable Avarice", 91, Rarity.RARE, mage.cards.i.InsatiableAvarice.class));
|
||||
cards.add(new SetCardInfo("Inspiring Vantage", 269, Rarity.RARE, mage.cards.i.InspiringVantage.class));
|
||||
cards.add(new SetCardInfo("Intimidation Campaign", 208, Rarity.UNCOMMON, mage.cards.i.IntimidationCampaign.class));
|
||||
cards.add(new SetCardInfo("Intrepid Stablemaster", 169, Rarity.UNCOMMON, mage.cards.i.IntrepidStablemaster.class));
|
||||
|
|
@ -131,6 +135,7 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Map the Frontier", 170, Rarity.UNCOMMON, mage.cards.m.MapTheFrontier.class));
|
||||
cards.add(new SetCardInfo("Marauding Sphinx", 56, Rarity.UNCOMMON, mage.cards.m.MaraudingSphinx.class));
|
||||
cards.add(new SetCardInfo("Marchesa, Dealer of Death", 220, Rarity.RARE, mage.cards.m.MarchesaDealerOfDeath.class));
|
||||
cards.add(new SetCardInfo("Metamorphic Blast", 57, Rarity.UNCOMMON, mage.cards.m.MetamorphicBlast.class));
|
||||
cards.add(new SetCardInfo("Mine Raider", 135, Rarity.COMMON, mage.cards.m.MineRaider.class));
|
||||
cards.add(new SetCardInfo("Miriam, Herd Whisperer", 221, Rarity.UNCOMMON, mage.cards.m.MiriamHerdWhisperer.class));
|
||||
cards.add(new SetCardInfo("Mobile Homestead", 245, Rarity.UNCOMMON, mage.cards.m.MobileHomestead.class));
|
||||
|
|
@ -163,12 +168,14 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Reach for the Sky", 178, Rarity.COMMON, mage.cards.r.ReachForTheSky.class));
|
||||
cards.add(new SetCardInfo("Reckless Lackey", 140, Rarity.COMMON, mage.cards.r.RecklessLackey.class));
|
||||
cards.add(new SetCardInfo("Redrock Sentinel", 247, Rarity.UNCOMMON, mage.cards.r.RedrockSentinel.class));
|
||||
cards.add(new SetCardInfo("Requisition Raid", 26, Rarity.UNCOMMON, mage.cards.r.RequisitionRaid.class));
|
||||
cards.add(new SetCardInfo("Resilient Roadrunner", 141, Rarity.UNCOMMON, mage.cards.r.ResilientRoadrunner.class));
|
||||
cards.add(new SetCardInfo("Rictus Robber", 102, Rarity.UNCOMMON, mage.cards.r.RictusRobber.class));
|
||||
cards.add(new SetCardInfo("Rise of the Varmints", 179, Rarity.UNCOMMON, mage.cards.r.RiseOfTheVarmints.class));
|
||||
cards.add(new SetCardInfo("Rodeo Pyromancers", 143, Rarity.COMMON, mage.cards.r.RodeoPyromancers.class));
|
||||
cards.add(new SetCardInfo("Rooftop Assassin", 103, Rarity.COMMON, mage.cards.r.RooftopAssassin.class));
|
||||
cards.add(new SetCardInfo("Roxanne, Starfall Savant", 228, Rarity.RARE, mage.cards.r.RoxanneStarfallSavant.class));
|
||||
cards.add(new SetCardInfo("Rustler Rampage", 27, Rarity.UNCOMMON, mage.cards.r.RustlerRampage.class));
|
||||
cards.add(new SetCardInfo("Ruthless Lawbringer", 229, Rarity.UNCOMMON, mage.cards.r.RuthlessLawbringer.class));
|
||||
cards.add(new SetCardInfo("Sandstorm Verge", 263, Rarity.UNCOMMON, mage.cards.s.SandstormVerge.class));
|
||||
cards.add(new SetCardInfo("Scalestorm Summoner", 144, Rarity.UNCOMMON, mage.cards.s.ScalestormSummoner.class));
|
||||
|
|
@ -195,11 +202,13 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Terror of the Peaks", 149, Rarity.MYTHIC, mage.cards.t.TerrorOfThePeaks.class));
|
||||
cards.add(new SetCardInfo("This Town Ain't Big Enough", 74, Rarity.UNCOMMON, mage.cards.t.ThisTownAintBigEnough.class));
|
||||
cards.add(new SetCardInfo("Three Steps Ahead", 75, Rarity.RARE, mage.cards.t.ThreeStepsAhead.class));
|
||||
cards.add(new SetCardInfo("Thunder Lasso", 35, Rarity.UNCOMMON, mage.cards.t.ThunderLasso.class));
|
||||
cards.add(new SetCardInfo("Tomb Trawler", 250, Rarity.UNCOMMON, mage.cards.t.TombTrawler.class));
|
||||
cards.add(new SetCardInfo("Trained Arynx", 36, Rarity.COMMON, mage.cards.t.TrainedArynx.class));
|
||||
cards.add(new SetCardInfo("Treasure Dredger", 110, Rarity.UNCOMMON, mage.cards.t.TreasureDredger.class));
|
||||
cards.add(new SetCardInfo("Tumbleweed Rising", 187, Rarity.COMMON, mage.cards.t.TumbleweedRising.class));
|
||||
cards.add(new SetCardInfo("Unfortunate Accident", 111, Rarity.UNCOMMON, mage.cards.u.UnfortunateAccident.class));
|
||||
cards.add(new SetCardInfo("Unscrupulous Contractor", 112, Rarity.UNCOMMON, mage.cards.u.UnscrupulousContractor.class));
|
||||
cards.add(new SetCardInfo("Vengeful Townsfolk", 37, Rarity.COMMON, mage.cards.v.VengefulTownsfolk.class));
|
||||
cards.add(new SetCardInfo("Vial Smasher, Gleeful Grenadier", 235, Rarity.UNCOMMON, mage.cards.v.VialSmasherGleefulGrenadier.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package org.mage.test.cards.abilities.keywords;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class SpreeTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String accident = "Unfortunate Accident";
|
||||
// Instant {B}
|
||||
// Spree
|
||||
// + {2}{B} -- Destroy target creature.
|
||||
// + {1} -- Create a 1/1 Mercenary token
|
||||
|
||||
private static final String bear = "Grizzly Bears";
|
||||
|
||||
@Test
|
||||
public void testFirstMode() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, bear, 1);
|
||||
addCard(Zone.HAND, playerA, accident);
|
||||
|
||||
setModeChoice(playerA, "1");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, bear, 1);
|
||||
assertPermanentCount(playerA, "Mercenary Token", 0);
|
||||
assertTappedCount("Swamp", true, 1 + 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondMode() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 1);
|
||||
addCard(Zone.HAND, playerA, accident);
|
||||
|
||||
setModeChoice(playerA, "2");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Mercenary Token", 1);
|
||||
assertTappedCount("Swamp", true, 1 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBothModes() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3 + 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, bear, 1);
|
||||
addCard(Zone.HAND, playerA, accident);
|
||||
|
||||
setModeChoice(playerA, "1");
|
||||
setModeChoice(playerA, "2");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, bear, 1);
|
||||
assertPermanentCount(playerA, "Mercenary Token", 1);
|
||||
assertTappedCount("Swamp", true, 1 + 3 + 1);
|
||||
}
|
||||
|
||||
private static final String electromancer = "Goblin Electromancer";
|
||||
|
||||
@Test
|
||||
public void testReduction() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3 + 1 - 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, bear, 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, electromancer, 1);
|
||||
addCard(Zone.HAND, playerA, accident);
|
||||
|
||||
setModeChoice(playerA, "1");
|
||||
setModeChoice(playerA, "2");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, bear, 1);
|
||||
assertPermanentCount(playerA, "Mercenary Token", 1);
|
||||
assertTappedCount("Swamp", true, 1 + 3 + 1 - 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -514,6 +514,14 @@ public interface Ability extends Controllable, Serializable {
|
|||
*/
|
||||
Ability withFirstModeFlavorWord(String flavorWord);
|
||||
|
||||
/**
|
||||
* Sets cost word for first mode
|
||||
*
|
||||
* @param cost
|
||||
* @return
|
||||
*/
|
||||
Ability withFirstModeCost(Cost cost);
|
||||
|
||||
/**
|
||||
* Creates the message about the ability casting/triggering/activating to
|
||||
* post in the game log before the ability resolves.
|
||||
|
|
|
|||
|
|
@ -311,6 +311,16 @@ public abstract class AbilityImpl implements Ability {
|
|||
return false;
|
||||
}
|
||||
|
||||
// apply mode costs if they have them
|
||||
for (UUID modeId : this.getModes().getSelectedModes()) {
|
||||
Cost cost = this.getModes().get(modeId).getCost();
|
||||
if (cost instanceof ManaCost) {
|
||||
this.addManaCostsToPay((ManaCost) cost.copy());
|
||||
} else if (cost != null) {
|
||||
this.costs.add(cost.copy());
|
||||
}
|
||||
}
|
||||
|
||||
// unit tests only: it allows to add targets/choices by two ways:
|
||||
// 1. From cast/activate command params (process it here)
|
||||
// 2. From single addTarget/setChoice, it's a preffered method for tests (process it in normal choose dialogs like human player)
|
||||
|
|
@ -1141,6 +1151,12 @@ public abstract class AbilityImpl implements Ability {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ability withFirstModeCost(Cost cost) {
|
||||
this.modes.getMode().withCost(cost);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGameLogMessage(Game game) {
|
||||
if (game.isSimulation()) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities;
|
||||
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.Effects;
|
||||
import mage.target.Target;
|
||||
|
|
@ -17,6 +18,7 @@ public class Mode implements Serializable {
|
|||
protected final Targets targets;
|
||||
protected final Effects effects;
|
||||
protected String flavorWord;
|
||||
protected Cost cost = null;
|
||||
/**
|
||||
* Optional Tag to distinguish this mode from others.
|
||||
* In the case of modes that players can only choose once,
|
||||
|
|
@ -39,6 +41,7 @@ public class Mode implements Serializable {
|
|||
this.effects = mode.effects.copy();
|
||||
this.flavorWord = mode.flavorWord;
|
||||
this.modeTag = mode.modeTag;
|
||||
this.cost = mode.cost != null ? mode.cost.copy() : null;
|
||||
}
|
||||
|
||||
public UUID setRandomId() {
|
||||
|
|
@ -107,4 +110,13 @@ public class Mode implements Serializable {
|
|||
this.flavorWord = flavorWord;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Mode withCost(Cost cost) {
|
||||
this.cost = cost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Cost getCost() {
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -584,7 +584,14 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
|
|||
sb.append("<br>");
|
||||
|
||||
for (Mode mode : this.values()) {
|
||||
sb.append("&bull ");
|
||||
if (mode.getCost() != null) {
|
||||
// for Spree
|
||||
sb.append("+ ");
|
||||
sb.append(mode.getCost().getText());
|
||||
sb.append(" — ");
|
||||
} else {
|
||||
sb.append("&bull ");
|
||||
}
|
||||
sb.append(mode.getEffects().getTextStartingUpperCase(mode));
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
|
|
|||
28
Mage/src/main/java/mage/abilities/keyword/SpreeAbility.java
Normal file
28
Mage/src/main/java/mage/abilities/keyword/SpreeAbility.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class SpreeAbility extends StaticAbility {
|
||||
|
||||
public SpreeAbility(Card card) {
|
||||
super(Zone.ALL, null);
|
||||
this.setRuleVisible(false);
|
||||
card.getSpellAbility().getModes().setChooseText("Spree <i>(Choose one or more additional costs.)</i>");
|
||||
card.getSpellAbility().getModes().setMinModes(1);
|
||||
card.getSpellAbility().getModes().setMaxModes(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private SpreeAbility(final SpreeAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpreeAbility copy() {
|
||||
return new SpreeAbility(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -414,14 +414,17 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
public void addManaCostsToPay(ManaCost manaCost) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getCostsTagMap() {
|
||||
return ability.getCostsTagMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCostsTag(String tag, Object value){
|
||||
public void setCostsTag(String tag, Object value) {
|
||||
ability.setCostsTag(tag, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbilityType getAbilityType() {
|
||||
return ability.getAbilityType();
|
||||
|
|
@ -539,6 +542,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ability withFirstModeCost(Cost cost) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ Soulbond|new|
|
|||
Soulshift|number|
|
||||
Skulk|new|
|
||||
Spectacle|card, cost|
|
||||
Spree|card|
|
||||
Squad|new|
|
||||
Storm|new|
|
||||
Sunburst|new|
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue