forked from External/mage
Merge pull request 'master' (#45) from External/mage:master into season3
Reviewed-on: #45
This commit is contained in:
commit
ca9f3f3e1f
109 changed files with 4417 additions and 391 deletions
|
|
@ -39,10 +39,9 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
private static final Logger logger = Logger.getLogger(BracketLegalityLabel.class);
|
||||
|
||||
private static final String GROUP_GAME_CHANGES = "Game Changers";
|
||||
private static final String GROUP_INFINITE_COMBOS = "Infinite Combos";
|
||||
private static final String GROUP_INFINITE_COMBOS = "Early-game 2-Card Combos";
|
||||
private static final String GROUP_MASS_LAND_DESTRUCTION = "Mass Land Destruction";
|
||||
private static final String GROUP_EXTRA_TURN = "Extra Turns";
|
||||
private static final String GROUP_TUTORS = "Tutors";
|
||||
|
||||
private static final Map<String, List<Integer>> MAX_GROUP_LIMITS = new LinkedHashMap<>();
|
||||
|
||||
|
|
@ -78,8 +77,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
Arrays.asList(0, 0, 0, 0, 99, 99));
|
||||
MAX_GROUP_LIMITS.put(GROUP_EXTRA_TURN,
|
||||
Arrays.asList(0, 0, 0, 3, 99, 99));
|
||||
MAX_GROUP_LIMITS.put(GROUP_TUTORS,
|
||||
Arrays.asList(0, 3, 3, 99, 99, 99));
|
||||
}
|
||||
|
||||
private static final String RESOURCE_INFINITE_COMBOS = "brackets/infinite-combos.txt";
|
||||
|
|
@ -92,7 +89,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
private final List<String> foundInfiniteCombos = new ArrayList<>();
|
||||
private final List<String> foundMassLandDestruction = new ArrayList<>();
|
||||
private final List<String> foundExtraTurn = new ArrayList<>();
|
||||
private final List<String> foundTutors = new ArrayList<>();
|
||||
|
||||
private final List<String> badCards = new ArrayList<>();
|
||||
private final List<String> fullGameChanges = new ArrayList<>();
|
||||
|
|
@ -126,9 +122,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
if (this.foundExtraTurn.size() > getMaxCardsLimit(GROUP_EXTRA_TURN)) {
|
||||
this.badCards.addAll(this.foundExtraTurn);
|
||||
}
|
||||
if (this.foundTutors.size() > getMaxCardsLimit(GROUP_TUTORS)) {
|
||||
this.badCards.addAll(this.foundTutors);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getMaxCardsLimit(String groupName) {
|
||||
|
|
@ -165,7 +158,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
groups.put(GROUP_INFINITE_COMBOS + getStats(GROUP_INFINITE_COMBOS), this.foundInfiniteCombos);
|
||||
groups.put(GROUP_MASS_LAND_DESTRUCTION + getStats(GROUP_MASS_LAND_DESTRUCTION), this.foundMassLandDestruction);
|
||||
groups.put(GROUP_EXTRA_TURN + getStats(GROUP_EXTRA_TURN), this.foundExtraTurn);
|
||||
groups.put(GROUP_TUTORS + getStats(GROUP_TUTORS), this.foundTutors);
|
||||
groups.forEach((group, cards) -> {
|
||||
showInfo.add("<br>");
|
||||
showInfo.add("<span style='font-weight:bold;font-size: " + infoFontTextSize + "px;'>" + group + "</span>");
|
||||
|
|
@ -199,9 +191,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
case GROUP_EXTRA_TURN:
|
||||
currentAmount = this.foundExtraTurn.size();
|
||||
break;
|
||||
case GROUP_TUTORS:
|
||||
currentAmount = this.foundTutors.size();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown group " + groupName);
|
||||
}
|
||||
|
|
@ -222,7 +211,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
collectInfiniteCombos(deck);
|
||||
collectMassLandDestruction(deck);
|
||||
collectExtraTurn(deck);
|
||||
collectTutors(deck);
|
||||
}
|
||||
|
||||
private void collectGameChangers(Deck deck) {
|
||||
|
|
@ -244,12 +232,9 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
"Consecrated Sphinx",
|
||||
"Crop Rotation",
|
||||
"Cyclonic Rift",
|
||||
"Deflecting Swat",
|
||||
"Enlightened Tutor",
|
||||
"Expropriate",
|
||||
"Field of the Dead",
|
||||
"Fierce Guardianship",
|
||||
"Food Chain",
|
||||
"Force of Will",
|
||||
"Gaea's Cradle",
|
||||
"Gamble",
|
||||
|
|
@ -261,8 +246,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
"Imperial Seal",
|
||||
"Intuition",
|
||||
"Jeska's Will",
|
||||
"Jin-Gitaxias, Core Augur",
|
||||
"Kinnan, Bonder Prodigy",
|
||||
"Lion's Eye Diamond",
|
||||
"Mana Vault",
|
||||
"Mishra's Workshop",
|
||||
|
|
@ -280,18 +263,13 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
"Serra's Sanctum",
|
||||
"Smothering Tithe",
|
||||
"Survival of the Fittest",
|
||||
"Sway of the Stars",
|
||||
"Teferi's Protection",
|
||||
"Tergrid, God of Fright",
|
||||
"Thassa's Oracle",
|
||||
"The One Ring",
|
||||
"The Tabernacle at Pendrell Vale",
|
||||
"Underworld Breach",
|
||||
"Urza, Lord High Artificer",
|
||||
"Vampiric Tutor",
|
||||
"Vorinclex, Voice of Hunger",
|
||||
"Yuriko, the Tiger's Shadow",
|
||||
"Winota, Joiner of Forces",
|
||||
"Worldly Tutor"
|
||||
));
|
||||
}
|
||||
|
|
@ -393,19 +371,6 @@ public class BracketLegalityLabel extends LegalityLabel {
|
|||
.forEach(this.foundExtraTurn::add);
|
||||
}
|
||||
|
||||
private void collectTutors(Deck deck) {
|
||||
// edh power level uses search for land and non-land card, but bracket need only non-land cards searching
|
||||
this.foundTutors.clear();
|
||||
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
|
||||
.filter(card -> card.getRules().stream()
|
||||
.map(s -> s.toLowerCase(Locale.ENGLISH))
|
||||
.anyMatch(s -> s.contains("search your library") && !isTextContainsLandCard(s))
|
||||
)
|
||||
.map(Card::getName)
|
||||
.sorted()
|
||||
.forEach(this.foundTutors::add);
|
||||
}
|
||||
|
||||
private boolean isTextContainsLandCard(String lowerText) {
|
||||
// TODO: share code with AbstractCommander and edh power level
|
||||
// TODO: add tests
|
||||
|
|
|
|||
|
|
@ -622,6 +622,8 @@ public class ScryfallImageSupportCards {
|
|||
add("TLA"); // Avatar: The Last Airbender
|
||||
add("TLE"); // Avatar: The Last Airbender Eternal
|
||||
add("ECL"); // Lorwyn Eclipsed
|
||||
add("TMT"); // Teenage Mutant Ninja Turtles
|
||||
add("TMC"); // Teenage Mutant Ninja Turtles Eternal
|
||||
|
||||
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
|
||||
add("CALC"); // Custom Alchemized versions of existing cards
|
||||
|
|
|
|||
|
|
@ -2546,6 +2546,7 @@ public class ScryfallImageSupportTokens {
|
|||
|
||||
// DSK
|
||||
put("DSK/Beast", "https://api.scryfall.com/cards/tdsk/3?format=image");
|
||||
put("DSK/Demon", "https://api.scryfall.com/cards/tdsk/9?format=image");
|
||||
put("DSK/Emblem Kaito", "https://api.scryfall.com/cards/tdsk/17/en?format=image");
|
||||
put("DSK/Everywhere", "https://api.scryfall.com/cards/tdsk/16?format=image");
|
||||
put("DSK/Glimmer", "https://api.scryfall.com/cards/tdsk/4?format=image");
|
||||
|
|
@ -2558,6 +2559,7 @@ public class ScryfallImageSupportTokens {
|
|||
put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?format=image");
|
||||
put("DSK/Spirit/1", "https://api.scryfall.com/cards/tdsk/6?format=image");
|
||||
put("DSK/Spirit/2", "https://api.scryfall.com/cards/tdsk/8?format=image");
|
||||
put("DSK/Toy", "https://api.scryfall.com/cards/tdsk/7?format=image");
|
||||
put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image");
|
||||
|
||||
// DSC
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ public abstract class AbstractCommander extends Constructed {
|
|||
|
||||
private static List<CommanderValidator> validators = Arrays.asList(
|
||||
PartnerValidator.instance,
|
||||
PartnerSurvivorsValidator.instance,
|
||||
PartnerFatherAndSonValidator.instance,
|
||||
PartnerVariantValidator.instance,
|
||||
FriendsForeverValidator.instance,
|
||||
PartnerWithValidator.instance,
|
||||
ChooseABackgroundValidator.instance,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import mage.abilities.dynamicvalue.common.ManaSpentToCastCount;
|
|||
import mage.abilities.effects.common.CastSourceTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.EntersBattlefieldUnderControlOfOpponentOfChoiceEffect;
|
||||
import mage.abilities.keyword.PartnerSurvivorsAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PartnerVariantType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.game.permanent.token.CordycepsInfectedToken;
|
||||
|
|
@ -41,7 +41,7 @@ public final class AbbyMercilessSoldier extends CardImpl {
|
|||
this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldUnderControlOfOpponentOfChoiceEffect()));
|
||||
|
||||
// Partner--Survivors
|
||||
this.addAbility(PartnerSurvivorsAbility.getInstance());
|
||||
this.addAbility(PartnerVariantType.SURVIVORS.makeAbility());
|
||||
}
|
||||
|
||||
private AbbyMercilessSoldier(final AbbyMercilessSoldier card) {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.TapTargetEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.token.HeroToken;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -28,8 +39,7 @@ public final class AerithRescueMission extends CardImpl {
|
|||
|
||||
// * Take 59 Flights of Stairs -- Tap up to three target creatures. Put a stun counter on one of them.
|
||||
this.getSpellAbility().addMode(new Mode(new TapTargetEffect())
|
||||
.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance())
|
||||
.setText("Put a stun counter on one of them"))
|
||||
.addEffect(new AerithRescueMissionStunEffect())
|
||||
.addTarget(new TargetCreaturePermanent(0, 3))
|
||||
.withFlavorWord("Take 59 Flights of Stairs"));
|
||||
}
|
||||
|
|
@ -43,3 +53,34 @@ public final class AerithRescueMission extends CardImpl {
|
|||
return new AerithRescueMission(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AerithRescueMissionStunEffect extends OneShotEffect {
|
||||
|
||||
AerithRescueMissionStunEffect() {
|
||||
super(Outcome.Detriment);
|
||||
staticText = "Put a stun counter on one of them";
|
||||
}
|
||||
|
||||
private AerithRescueMissionStunEffect(final AerithRescueMissionStunEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AerithRescueMissionStunEffect copy() {
|
||||
return new AerithRescueMissionStunEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
FilterPermanent filter = new FilterPermanent("creature to put a stun counter on");
|
||||
filter.add(new PermanentReferenceInCollectionPredicate(this.getTargetPointer().getTargets(game, source).stream()
|
||||
.map(game::getPermanent).collect(Collectors.toList()), game));
|
||||
Target target = new TargetPermanent(filter).withNotTarget(true);
|
||||
if (target.choose(Outcome.UnboostCreature, source.getControllerId(), source, game)) {
|
||||
Effect eff = new AddCountersTargetEffect(CounterType.STUN.createInstance());
|
||||
eff.setTargetPointer(new FixedTarget(target.getFirstTarget(), game));
|
||||
return eff.apply(game, source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public final class AgentVenom extends CardImpl {
|
|||
this.addAbility(FlashAbility.getInstance());
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility());
|
||||
this.addAbility(new MenaceAbility(false));
|
||||
|
||||
// Whenever another nontoken creature you control dies, you draw a card and lose 1 life.
|
||||
Ability ability = new DiesCreatureTriggeredAbility(
|
||||
|
|
|
|||
132
Mage.Sets/src/mage/cards/a/AprilONeilHacktivist.java
Normal file
132
Mage.Sets/src/mage/cards/a/AprilONeilHacktivist.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AprilONeilHacktivist extends CardImpl {
|
||||
|
||||
public AprilONeilHacktivist(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.SCIENTIST);
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// At the beginning of your end step, draw a card for each card type among spells you've cast this turn.
|
||||
this.addAbility(new BeginningOfEndStepTriggeredAbility(
|
||||
new DrawCardSourceControllerEffect(AprilONeilHacktivistValue.instance)
|
||||
).addHint(AprilONeilHacktivistHint.instance), new AprilONeilHacktivistWatcher());
|
||||
}
|
||||
|
||||
private AprilONeilHacktivist(final AprilONeilHacktivist card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AprilONeilHacktivist copy() {
|
||||
return new AprilONeilHacktivist(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum AprilONeilHacktivistValue implements DynamicValue {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
return AprilONeilHacktivistWatcher.getCardTypesCast(sourceAbility.getControllerId(), game).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AprilONeilHacktivistValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "card type among spells you've cast this turn";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
enum AprilONeilHacktivistHint implements Hint {
|
||||
instance;
|
||||
|
||||
|
||||
@Override
|
||||
public String getText(Game game, Ability ability) {
|
||||
List<String> types = AprilONeilHacktivistWatcher
|
||||
.getCardTypesCast(ability.getControllerId(), game)
|
||||
.stream()
|
||||
.map(CardType::toString)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
return "Card types among spells you've cast this turn: " + types.size()
|
||||
+ (types.size() > 0 ? " (" + String.join(", ", types) + ')' : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hint copy() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class AprilONeilHacktivistWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, Set<CardType>> map = new HashMap<>();
|
||||
|
||||
AprilONeilHacktivistWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() != GameEvent.EventType.SPELL_CAST) {
|
||||
return;
|
||||
}
|
||||
Spell spell = game.getSpell(event.getTargetId());
|
||||
if (spell != null) {
|
||||
map.computeIfAbsent(spell.getControllerId(), x -> new HashSet<>()).addAll(spell.getCardType(game));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
map.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
static Set<CardType> getCardTypesCast(UUID playerId, Game game) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(AprilONeilHacktivistWatcher.class)
|
||||
.map
|
||||
.getOrDefault(playerId, Collections.emptySet());
|
||||
}
|
||||
}
|
||||
|
|
@ -10,14 +10,10 @@ import mage.abilities.dynamicvalue.common.CountersControllerCount;
|
|||
import mage.abilities.effects.common.DamagePlayersEffect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.discard.DiscardControllerEffect;
|
||||
import mage.abilities.keyword.PartnerFatherAndSonAbility;
|
||||
import mage.abilities.keyword.ReachAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -50,7 +46,7 @@ public final class AtreusImpulsiveSon extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Partner--Father & son
|
||||
this.addAbility(PartnerFatherAndSonAbility.getInstance());
|
||||
this.addAbility(PartnerVariantType.FATHER_AND_SON.makeAbility());
|
||||
}
|
||||
|
||||
private AtreusImpulsiveSon(final AtreusImpulsiveSon card) {
|
||||
|
|
|
|||
47
Mage.Sets/src/mage/cards/b/BebopAndRocksteady.java
Normal file
47
Mage.Sets/src/mage/cards/b/BebopAndRocksteady.java
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.AttacksOrBlocksTriggeredAbility;
|
||||
import mage.abilities.costs.common.DiscardCardCost;
|
||||
import mage.abilities.effects.common.DoIfCostPaid;
|
||||
import mage.abilities.effects.common.SacrificeControllerEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class BebopAndRocksteady extends CardImpl {
|
||||
|
||||
public BebopAndRocksteady(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B/G}{B/G}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.BOAR);
|
||||
this.subtype.add(SubType.RHINO);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.power = new MageInt(7);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// Whenever Bebop & Rocksteady attacks or blocks, sacrifice a permanent unless you discard a card.
|
||||
this.addAbility(new AttacksOrBlocksTriggeredAbility(new DoIfCostPaid(
|
||||
null, new SacrificeControllerEffect(StaticFilters.FILTER_PERMANENT, 1, null),
|
||||
new DiscardCardCost(), false), false
|
||||
));
|
||||
}
|
||||
|
||||
private BebopAndRocksteady(final BebopAndRocksteady card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BebopAndRocksteady copy() {
|
||||
return new BebopAndRocksteady(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
package mage.cards.b;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -12,17 +10,19 @@ import mage.cards.CardSetInfo;
|
|||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.ColorPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author North
|
||||
*/
|
||||
public final class BloodmarkMentor extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("Red creatures");
|
||||
private static final FilterPermanent filter = new FilterCreaturePermanent("Red creatures");
|
||||
|
||||
static {
|
||||
filter.add(new ColorPredicate(ObjectColor.RED));
|
||||
|
|
|
|||
59
Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java
Normal file
59
Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SetTargetPointer;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
*/
|
||||
public final class BottomlessPoolLockerRoom extends RoomCard {
|
||||
|
||||
public BottomlessPoolLockerRoom(UUID ownerId, CardSetInfo setInfo) {
|
||||
// Bottomless Pool
|
||||
// {U}
|
||||
// When you unlock this door, return up to one target creature to its owner’s hand.
|
||||
// Locker Room
|
||||
// {4}{U}
|
||||
// Enchantment -- Room
|
||||
// Whenever one or more creatures you control deal combat damage to a player, draw a card.
|
||||
super(ownerId, setInfo,
|
||||
new CardType[] { CardType.ENCHANTMENT },
|
||||
"{U}", "{4}{U}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Left half ability - "When you unlock this door, return up to one target creature to its owner’s hand."
|
||||
UnlockThisDoorTriggeredAbility left = new UnlockThisDoorTriggeredAbility(
|
||||
new ReturnToHandTargetEffect(), false, true);
|
||||
left.addTarget(new TargetCreaturePermanent(0, 1));
|
||||
|
||||
// Right half ability - "Whenever one or more creatures you control deal combat damage to a player, draw a card."
|
||||
DealsDamageToAPlayerAllTriggeredAbility right = new DealsDamageToAPlayerAllTriggeredAbility(
|
||||
new DrawCardSourceControllerEffect(1),
|
||||
StaticFilters.FILTER_CONTROLLED_A_CREATURE,
|
||||
false, SetTargetPointer.PLAYER, true, true, TargetController.OPPONENT);
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private BottomlessPoolLockerRoom(final BottomlessPoolLockerRoom card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BottomlessPoolLockerRoom copy() {
|
||||
return new BottomlessPoolLockerRoom(this);
|
||||
}
|
||||
}
|
||||
48
Mage.Sets/src/mage/cards/c/CaseyJonesJuryRigJusticiar.java
Normal file
48
Mage.Sets/src/mage/cards/c/CaseyJonesJuryRigJusticiar.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PutCards;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class CaseyJonesJuryRigJusticiar extends CardImpl {
|
||||
|
||||
public CaseyJonesJuryRigJusticiar(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.BERSERKER);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// Haste
|
||||
this.addAbility(HasteAbility.getInstance());
|
||||
|
||||
// When Casey Jones enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new LookLibraryAndPickControllerEffect(
|
||||
4, 1, StaticFilters.FILTER_CARD_ARTIFACT_AN, PutCards.HAND, PutCards.BOTTOM_RANDOM
|
||||
)));
|
||||
}
|
||||
|
||||
private CaseyJonesJuryRigJusticiar(final CaseyJonesJuryRigJusticiar card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaseyJonesJuryRigJusticiar copy() {
|
||||
return new CaseyJonesJuryRigJusticiar(this);
|
||||
}
|
||||
}
|
||||
53
Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java
Normal file
53
Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect;
|
||||
import mage.abilities.effects.common.continuous.UntapAllDuringEachOtherPlayersUntapStepEffect;
|
||||
import mage.abilities.keyword.ConvokeAbility;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterNonlandCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class DazzlingTheaterPropRoom extends RoomCard {
|
||||
|
||||
private static final FilterNonlandCard filter = new FilterNonlandCard("creature spells you cast");
|
||||
|
||||
static {
|
||||
filter.add(CardType.CREATURE.getPredicate());
|
||||
filter.add(Predicates.not(new AbilityPredicate(ConvokeAbility.class)));
|
||||
}
|
||||
|
||||
public DazzlingTheaterPropRoom(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}", "{2}{W}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Dazzling Theater: Creature spells you cast have convoke.
|
||||
Ability left = new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new ConvokeAbility(), filter));
|
||||
|
||||
// Prop Room: Untap each creature you control during each other player's untap step.
|
||||
Ability right = new SimpleStaticAbility(new UntapAllDuringEachOtherPlayersUntapStepEffect(StaticFilters.FILTER_CONTROLLED_CREATURES));
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private DazzlingTheaterPropRoom(final DazzlingTheaterPropRoom card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DazzlingTheaterPropRoom copy() {
|
||||
return new DazzlingTheaterPropRoom(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.permanent.token.ToyToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class DollmakersShopPorcelainGallery extends RoomCard {
|
||||
|
||||
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("non-Toy creatures you control");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.not(SubType.TOY.getPredicate()));
|
||||
}
|
||||
|
||||
public DollmakersShopPorcelainGallery(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}", "{4}{W}{W}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Dollmaker's Shop: Whenever one or more non-Toy creatures you control attack a player, create a 1/1 white Toy artifact creature token.
|
||||
Ability left = new AttacksPlayerWithCreaturesTriggeredAbility(new CreateTokenEffect(new ToyToken()), filter, SetTargetPointer.NONE);
|
||||
|
||||
// Porcelain Gallery: Creatures you control have base power and toughness each equal to the number of creatures you control.
|
||||
Ability right = new SimpleStaticAbility(new SetBasePowerToughnessAllEffect(
|
||||
CreaturesYouControlCount.PLURAL, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURES
|
||||
).setText("Creatures you control have base power and toughness each equal to the number of creatures you control"));
|
||||
|
||||
this.addRoomAbilities(left, right.addHint(new ValueHint("Creatures you control", CreaturesYouControlCount.PLURAL)));
|
||||
}
|
||||
|
||||
private DollmakersShopPorcelainGallery (final DollmakersShopPorcelainGallery card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DollmakersShopPorcelainGallery copy() {
|
||||
return new DollmakersShopPorcelainGallery(this);
|
||||
}
|
||||
}
|
||||
52
Mage.Sets/src/mage/cards/d/DonatelloRadScientist.java
Normal file
52
Mage.Sets/src/mage/cards/d/DonatelloRadScientist.java
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.TapTargetEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.abilities.keyword.VigilanceAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.target.common.TargetOpponentsCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class DonatelloRadScientist extends CardImpl {
|
||||
|
||||
public DonatelloRadScientist(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(6);
|
||||
|
||||
// Vigilance
|
||||
this.addAbility(VigilanceAbility.getInstance());
|
||||
|
||||
// When Donatello enters, tap up to three target creatures your opponents control. Put a stun counter on each of them.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect());
|
||||
ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("Put a stun counter on each of them"));
|
||||
ability.addTarget(new TargetOpponentsCreaturePermanent(0, 3));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private DonatelloRadScientist(final DonatelloRadScientist card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DonatelloRadScientist copy() {
|
||||
return new DonatelloRadScientist(this);
|
||||
}
|
||||
}
|
||||
89
Mage.Sets/src/mage/cards/d/DonatelloTheBrains.java
Normal file
89
Mage.Sets/src/mage/cards/d/DonatelloTheBrains.java
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.CreateTokenEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.token.MutagenToken;
|
||||
import mage.game.permanent.token.Token;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class DonatelloTheBrains extends CardImpl {
|
||||
|
||||
public DonatelloTheBrains(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// If one or more tokens would be created under your control, those tokens plus a Mutagen token are created instead.
|
||||
this.addAbility(new SimpleStaticAbility(new DonatelloTheBrainsReplacementEffect()));
|
||||
|
||||
// Partner--Character select
|
||||
this.addAbility(PartnerVariantType.CHARACTER_SELECT.makeAbility());
|
||||
}
|
||||
|
||||
private DonatelloTheBrains(final DonatelloTheBrains card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DonatelloTheBrains copy() {
|
||||
return new DonatelloTheBrains(this);
|
||||
}
|
||||
}
|
||||
|
||||
class DonatelloTheBrainsReplacementEffect extends ReplacementEffectImpl {
|
||||
|
||||
DonatelloTheBrainsReplacementEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.staticText = "if one or more tokens would be created under your control, " +
|
||||
"those tokens plus a Mutagen token are created instead";
|
||||
}
|
||||
|
||||
private DonatelloTheBrainsReplacementEffect(final DonatelloTheBrainsReplacementEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DonatelloTheBrainsReplacementEffect copy() {
|
||||
return new DonatelloTheBrainsReplacementEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.CREATE_TOKEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
return source.isControlledBy(event.getPlayerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
Map<Token, Integer> tokens = ((CreateTokenEvent) event).getTokens();
|
||||
Token token = CardUtil
|
||||
.castStream(tokens.values(), MutagenToken.class)
|
||||
.findAny()
|
||||
.orElseGet(MutagenToken::new);
|
||||
tokens.compute(token, CardUtil::setOrIncrementValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
112
Mage.Sets/src/mage/cards/d/DonnieAndAprilAdorkableDuo.java
Normal file
112
Mage.Sets/src/mage/cards/d/DonnieAndAprilAdorkableDuo.java
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DrawCardTargetEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterPlayer;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.other.AnotherTargetPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class DonnieAndAprilAdorkableDuo extends CardImpl {
|
||||
|
||||
private static final FilterPlayer filter0 = new FilterPlayer("a different player");
|
||||
private static final FilterPlayer filter1 = new FilterPlayer();
|
||||
private static final FilterPlayer filter2 = new FilterPlayer();
|
||||
|
||||
static {
|
||||
filter1.add(new AnotherTargetPredicate(1, true));
|
||||
filter2.add(new AnotherTargetPredicate(2, true));
|
||||
}
|
||||
|
||||
public DonnieAndAprilAdorkableDuo(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// When Donnie & April enter, choose one or both. Each mode must target a different player.
|
||||
// * Target player draws two cards.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardTargetEffect(2));
|
||||
ability.addTarget(new TargetPlayer(filter1).withChooseHint("to draw a card"));
|
||||
ability.getModes().setMinModes(1);
|
||||
ability.getModes().setMaxModes(2);
|
||||
ability.getModes().setLimitUsageByOnce(false);
|
||||
ability.getModes().setMaxModesFilter(filter0);
|
||||
|
||||
// * Target player returns an artifact, instant, or sorcery card from their graveyard to their hand.
|
||||
ability.addMode(new Mode(new DonnieAndAprilAdorkableDuoEffect())
|
||||
.addTarget(new TargetPlayer(filter2).withChooseHint("to return a card from their graveyard to their hand")));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private DonnieAndAprilAdorkableDuo(final DonnieAndAprilAdorkableDuo card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DonnieAndAprilAdorkableDuo copy() {
|
||||
return new DonnieAndAprilAdorkableDuo(this);
|
||||
}
|
||||
}
|
||||
|
||||
class DonnieAndAprilAdorkableDuoEffect extends OneShotEffect {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("artifact, instant, or sorcery card");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.ARTIFACT.getPredicate(),
|
||||
CardType.INSTANT.getPredicate(),
|
||||
CardType.SORCERY.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
DonnieAndAprilAdorkableDuoEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "target player returns an artifact, instant, or sorcery card from their graveyard to their hand";
|
||||
}
|
||||
|
||||
private DonnieAndAprilAdorkableDuoEffect(final DonnieAndAprilAdorkableDuoEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DonnieAndAprilAdorkableDuoEffect copy() {
|
||||
return new DonnieAndAprilAdorkableDuoEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
|
||||
if (player == null || player.getGraveyard().count(filter, game) < 1) {
|
||||
return false;
|
||||
}
|
||||
TargetCard target = new TargetCardInYourGraveyard(filter);
|
||||
player.choose(Outcome.ReturnToHand, player.getGraveyard(), target, source, game);
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
return card != null && player.moveCards(card, Zone.HAND, source, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import mage.abilities.keyword.FlyingAbility;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
|
|
@ -42,7 +43,8 @@ public final class ElectroAssaultingBattery extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new YouDontLoseManaEffect(ManaType.RED)));
|
||||
|
||||
// Whenever you cast an instant or sorcery spell, add {R}.
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(new AddManaToManaPoolSourceControllerEffect(Mana.RedMana(1)), false));
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(new AddManaToManaPoolSourceControllerEffect(Mana.RedMana(1)),
|
||||
StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false));
|
||||
|
||||
// When Electro leaves the battlefield, you may pay x. When you do, he deals X damage to target player.
|
||||
this.addAbility(new LeavesBattlefieldTriggeredAbility(new ElectroAssaultingBatteryEffect()));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import mage.MageInt;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.PartnerSurvivorsAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
|
|
@ -34,7 +33,7 @@ public final class EllieBrickMaster extends CardImpl {
|
|||
this.addAbility(new EllieBrickMasterTriggeredAbility());
|
||||
|
||||
// Partner--Survivors
|
||||
this.addAbility(PartnerSurvivorsAbility.getInstance());
|
||||
this.addAbility(PartnerVariantType.SURVIVORS.makeAbility());
|
||||
}
|
||||
|
||||
private EllieBrickMaster(final EllieBrickMaster card) {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,9 @@ import mage.abilities.costs.common.SacrificeTargetCost;
|
|||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.keyword.IndestructibleAbility;
|
||||
import mage.abilities.keyword.PartnerSurvivorsAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
|
|
@ -44,7 +40,7 @@ public final class EllieVengefulHunter extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Partner--Survivors
|
||||
this.addAbility(PartnerSurvivorsAbility.getInstance());
|
||||
this.addAbility(PartnerVariantType.SURVIVORS.makeAbility());
|
||||
}
|
||||
|
||||
private EllieVengefulHunter(final EllieVengefulHunter card) {
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ import mage.constants.CardType;
|
|||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Controllable;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -70,14 +69,14 @@ class FallersFaithfulEffect extends OneShotEffect {
|
|||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
boolean flag = permanent.getDealtDamageByThisTurn().isEmpty();
|
||||
boolean notDamagedThisTurn = permanent.getDealtDamageByThisTurn().isEmpty();
|
||||
permanent.destroy(source, game);
|
||||
game.processAction();
|
||||
if (!flag) {
|
||||
Optional.ofNullable(permanent)
|
||||
.map(Controllable::getControllerId)
|
||||
.map(game::getPlayer)
|
||||
.ifPresent(player -> player.drawCards(2, source, game));
|
||||
if (notDamagedThisTurn) {
|
||||
game.processAction();
|
||||
Player player = game.getPlayer(permanent.getControllerId());
|
||||
if (player != null) {
|
||||
player.drawCards(2, source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
86
Mage.Sets/src/mage/cards/f/FishingGear.java
Normal file
86
Mage.Sets/src/mage/cards/f/FishingGear.java
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.EquipAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.token.FishNoAbilityToken;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class FishingGear extends CardImpl {
|
||||
|
||||
public FishingGear(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
|
||||
|
||||
this.subtype.add(SubType.EQUIPMENT);
|
||||
|
||||
// Whenever equipped creature deals combat damage to a player, exile the top card of that player's library. If it's a permanent card, you may put it onto the battlefield under your control. If you don't, create a 1/1 blue Fish creature token.
|
||||
this.addAbility(new DealsDamageToAPlayerAttachedTriggeredAbility(
|
||||
new FishingGearEffect(), "equipped", false, true, true
|
||||
));
|
||||
|
||||
// Equip {2}
|
||||
this.addAbility(new EquipAbility(2));
|
||||
}
|
||||
|
||||
private FishingGear(final FishingGear card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FishingGear copy() {
|
||||
return new FishingGear(this);
|
||||
}
|
||||
}
|
||||
|
||||
class FishingGearEffect extends OneShotEffect {
|
||||
|
||||
FishingGearEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "exile the top card of that player's library. " +
|
||||
"If it's a permanent card, you may put it onto the battlefield under your control. " +
|
||||
"If you don't, create a 1/1 blue Fish creature token";
|
||||
}
|
||||
|
||||
private FishingGearEffect(final FishingGearEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FishingGearEffect copy() {
|
||||
return new FishingGearEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
|
||||
if (controller == null || player == null) {
|
||||
return false;
|
||||
}
|
||||
Card card = player.getLibrary().getFromTop(game);
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
controller.moveCards(card, Zone.EXILED, source, game);
|
||||
if (card.isPermanent(game) && controller.chooseUse(outcome, "Put it onto the battlefield?", source, game)) {
|
||||
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
|
||||
} else {
|
||||
new FishNoAbilityToken().putOntoBattlefield(1, game, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
50
Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java
Normal file
50
Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DiesCreatureTriggeredAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.effects.common.GainLifeEffect;
|
||||
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
|
||||
import mage.abilities.effects.common.ReturnFromYourGraveyardToBattlefieldAllEffect;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class FuneralRoomAwakeningHall extends RoomCard {
|
||||
|
||||
public FuneralRoomAwakeningHall(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}", "{6}{B}{B}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Funeral Room: Whenever a creature you control dies, each opponent loses 1 life and you gain 1 life.
|
||||
Ability left = new DiesCreatureTriggeredAbility(
|
||||
new LoseLifeOpponentsEffect(1), false,
|
||||
StaticFilters.FILTER_CONTROLLED_A_CREATURE
|
||||
);
|
||||
left.addEffect(new GainLifeEffect(1).concatBy("and"));
|
||||
|
||||
// Awakening Hall: When you unlock this door, return all creature cards from your graveyard to the battlefield.
|
||||
Ability right = new UnlockThisDoorTriggeredAbility(
|
||||
new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_CREATURES), false, false
|
||||
);
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private FuneralRoomAwakeningHall(final FuneralRoomAwakeningHall card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FuneralRoomAwakeningHall copy() {
|
||||
return new FuneralRoomAwakeningHall(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,7 @@ package mage.cards.g;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SagaAbility;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.dynamicvalue.common.CountersSourceCount;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DestroyAllEffect;
|
||||
|
|
@ -19,7 +18,6 @@ import mage.filter.predicate.Predicates;
|
|||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.DalekToken;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetOpponent;
|
||||
|
|
@ -44,7 +42,7 @@ public final class GenesisOfTheDaleks extends CardImpl {
|
|||
// I, II, III -- Create a 3/3 black Dalek artifact creature token with menace for each lore counter on Genesis of the Daleks.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III,
|
||||
new CreateTokenEffect(new DalekToken(), GenesisOfTheDaleksValue.instance)
|
||||
new CreateTokenEffect(new DalekToken(), new CountersSourceCount(CounterType.LORE))
|
||||
);
|
||||
|
||||
// IV -- Target opponent faces a villainous choice -- Destroy all Dalek creatures and each of your opponents loses life equal to the total power of Daleks that died this turn, or destroy all non-Dalek creatures.
|
||||
|
|
@ -65,41 +63,6 @@ public final class GenesisOfTheDaleks extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
enum GenesisOfTheDaleksValue implements DynamicValue {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
Permanent permanent = sourceAbility.getSourcePermanentOrLKI(game);
|
||||
if (permanent != null) {
|
||||
return permanent
|
||||
.getCounters(game)
|
||||
.getCount(CounterType.LORE);
|
||||
}
|
||||
return Optional
|
||||
.ofNullable(sourceAbility)
|
||||
.map(Ability::getSourceId)
|
||||
.map(game::getPermanentOrLKIBattlefield)
|
||||
.map(p -> p.getCounters(game).getCount(CounterType.LORE))
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenesisOfTheDaleksValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "lore counter on {this}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
class GenesisOfTheDaleksEffect extends OneShotEffect {
|
||||
|
||||
private static final FaceVillainousChoice choice = new FaceVillainousChoice(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import mage.game.permanent.Permanent;
|
|||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
|
@ -81,11 +82,11 @@ class GlamerSpinnersEffect extends OneShotEffect {
|
|||
Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
if (targetPermanent == null
|
||||
|| targetPermanent
|
||||
.getAttachments()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(p -> p.hasSubtype(SubType.AURA, game))) {
|
||||
.getAttachments()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(p -> p.hasSubtype(SubType.AURA, game))) {
|
||||
return false;
|
||||
}
|
||||
FilterPermanent filter = new FilterPermanent(
|
||||
|
|
@ -94,7 +95,7 @@ class GlamerSpinnersEffect extends OneShotEffect {
|
|||
.map(Controllable::getControllerId)
|
||||
.map(game::getPlayer)
|
||||
.map(Player::getName)
|
||||
.map(s -> " controlled by" + s)
|
||||
.map(s -> " controlled by " + s)
|
||||
.orElse("")
|
||||
);
|
||||
filter.add(new ControllerIdPredicate(targetPermanent.getControllerId()));
|
||||
|
|
@ -102,18 +103,23 @@ class GlamerSpinnersEffect extends OneShotEffect {
|
|||
if (!game.getBattlefield().contains(filter, source.getControllerId(), source, game, 1)) {
|
||||
return false;
|
||||
}
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
TargetPermanent target = new TargetPermanent(filter);
|
||||
target.withNotTarget(true);
|
||||
Optional.ofNullable(source)
|
||||
.map(Controllable::getControllerId)
|
||||
.map(game::getPlayer)
|
||||
.ifPresent(player -> player.choose(outcome, target, source, game));
|
||||
player.choose(Outcome.AIDontUseIt, target, source, game);
|
||||
Permanent permanent = game.getPermanent(target.getFirstTarget());
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
for (UUID attachmentId : targetPermanent.getAttachments()) {
|
||||
permanent.addAttachment(attachmentId, source, game);
|
||||
// new list to avoid concurrent modification
|
||||
for (UUID attachmentId : new LinkedList<>(targetPermanent.getAttachments())) {
|
||||
Permanent attachment = game.getPermanent(attachmentId);
|
||||
if (attachment != null && attachment.hasSubtype(SubType.AURA, game)) {
|
||||
permanent.addAttachment(attachmentId, source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
80
Mage.Sets/src/mage/cards/h/HeroesInAHalfShell.java
Normal file
80
Mage.Sets/src/mage/cards/h/HeroesInAHalfShell.java
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package mage.cards.h;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.abilities.keyword.VigilanceAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SetTargetPointer;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class HeroesInAHalfShell extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterControlledPermanent("Mutants, Ninjas, and/or Turtles you control");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
SubType.MUTANT.getPredicate(),
|
||||
SubType.NINJA.getPredicate(),
|
||||
SubType.TURTLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public HeroesInAHalfShell(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// Vigilance
|
||||
this.addAbility(VigilanceAbility.getInstance());
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility());
|
||||
|
||||
// Trample
|
||||
this.addAbility(TrampleAbility.getInstance());
|
||||
|
||||
// Haste
|
||||
this.addAbility(HasteAbility.getInstance());
|
||||
|
||||
// Whenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card.
|
||||
Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(
|
||||
new AddCountersTargetEffect(CounterType.P1P1.createInstance())
|
||||
.setText("put a +1/+1 counter on each of those creatures"),
|
||||
SetTargetPointer.PERMANENT, filter, false
|
||||
);
|
||||
ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and"));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private HeroesInAHalfShell(final HeroesInAHalfShell card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeroesInAHalfShell copy() {
|
||||
return new HeroesInAHalfShell(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import mage.abilities.common.DiesCreatureTriggeredAbility;
|
|||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.keyword.PartnerSurvivorsAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PartnerVariantType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
|
|
@ -43,7 +43,7 @@ public final class JoelResoluteSurvivor extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Partner--Survivors
|
||||
this.addAbility(PartnerSurvivorsAbility.getInstance());
|
||||
this.addAbility(PartnerVariantType.SURVIVORS.makeAbility());
|
||||
}
|
||||
|
||||
private JoelResoluteSurvivor(final JoelResoluteSurvivor card) {
|
||||
|
|
|
|||
67
Mage.Sets/src/mage/cards/k/KrangMasterMind.java
Normal file
67
Mage.Sets/src/mage/cards/k/KrangMasterMind.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.condition.common.CardsInHandCondition;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostSourceEffect;
|
||||
import mage.abilities.keyword.AffinityForArtifactsAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterControlledArtifactPermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KrangMasterMind extends CardImpl {
|
||||
|
||||
private static final Condition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 4);
|
||||
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("other artifact you control");
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
}
|
||||
|
||||
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
|
||||
|
||||
public KrangMasterMind(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{6}{U}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.UTROM);
|
||||
this.subtype.add(SubType.WARRIOR);
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Affinity for artifacts
|
||||
this.addAbility(new AffinityForArtifactsAbility());
|
||||
|
||||
// When Krang enters, if you have fewer than four cards in hand, draw cards equal to the difference.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardsEqualToDifferenceEffect(4))
|
||||
.withInterveningIf(condition));
|
||||
|
||||
// Krang gets +1/+0 for each other artifact you control.
|
||||
this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(
|
||||
xValue, StaticValue.get(0), Duration.WhileOnBattlefield
|
||||
)));
|
||||
}
|
||||
|
||||
private KrangMasterMind(final KrangMasterMind card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KrangMasterMind copy() {
|
||||
return new KrangMasterMind(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -39,10 +39,13 @@ public final class KratosGodOfWar extends CardImpl {
|
|||
this.addAbility(DoubleStrikeAbility.getInstance());
|
||||
|
||||
// All creatures have haste.
|
||||
this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect(
|
||||
HasteAbility.getInstance(), Duration.WhileControlled,
|
||||
StaticFilters.FILTER_PERMANENT_CREATURE
|
||||
).setText("all creatures have haste")));
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(
|
||||
new GainAbilityAllEffect(
|
||||
HasteAbility.getInstance(), Duration.WhileOnBattlefield,
|
||||
StaticFilters.FILTER_PERMANENT_CREATURES
|
||||
).setText("all creatures have haste")
|
||||
));
|
||||
|
||||
// At the beginning of each player's end step, Kratos deals damage to that player equal to the number of creatures that player controls that didn't attack this turn.
|
||||
this.addAbility(new BeginningOfEndStepTriggeredAbility(
|
||||
|
|
@ -89,7 +92,7 @@ class KratosGodOfWarEffect extends OneShotEffect {
|
|||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
int count = game.getBattlefield().count(filter, source.getControllerId(), source, game);
|
||||
int count = game.getBattlefield().count(filter, game.getActivePlayerId(), source, game);
|
||||
return count > 0 && player.damage(count, source, game) > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
Mage.Sets/src/mage/cards/l/LeonardoTheBalance.java
Normal file
73
Mage.Sets/src/mage/cards/l/LeonardoTheBalance.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersAllEffect;
|
||||
import mage.abilities.keyword.LifelinkAbility;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class LeonardoTheBalance extends CardImpl {
|
||||
|
||||
public LeonardoTheBalance(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn.
|
||||
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(
|
||||
new AddCountersAllEffect(
|
||||
CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE
|
||||
), StaticFilters.FILTER_PERMANENT_TOKEN
|
||||
).setDoOnlyOnceEachTurn(true));
|
||||
|
||||
// {W}{U}{B}{R}{G}: Creatures you control gain menace, trample, and lifelink until end of turn.
|
||||
Ability ability = new SimpleActivatedAbility(
|
||||
new GainAbilityAllEffect(
|
||||
new MenaceAbility(false), Duration.EndOfTurn,
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE
|
||||
).setText("creatures you control gain menace"),
|
||||
new ManaCostsImpl<>("{W}{U}{B}{R}{G}")
|
||||
);
|
||||
ability.addEffect(new GainAbilityAllEffect(
|
||||
TrampleAbility.getInstance(), Duration.EndOfTurn,
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE
|
||||
).setText(", trample"));
|
||||
ability.addEffect(new GainAbilityAllEffect(
|
||||
LifelinkAbility.getInstance(), Duration.EndOfTurn,
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE
|
||||
).setText(", and lifelink until end of turn"));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Partner--Character select
|
||||
this.addAbility(PartnerVariantType.CHARACTER_SELECT.makeAbility());
|
||||
}
|
||||
|
||||
private LeonardoTheBalance(final LeonardoTheBalance card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeonardoTheBalance copy() {
|
||||
return new LeonardoTheBalance(this);
|
||||
}
|
||||
}
|
||||
50
Mage.Sets/src/mage/cards/l/LeonardoWorldlyWarrior.java
Normal file
50
Mage.Sets/src/mage/cards/l/LeonardoWorldlyWarrior.java
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
|
||||
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect;
|
||||
import mage.abilities.hint.common.CreaturesYouControlHint;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.Zone;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class LeonardoWorldlyWarrior extends CardImpl {
|
||||
|
||||
public LeonardoWorldlyWarrior(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{7}{W}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// This spell costs {1} less to cast for each creature you control.
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new SpellCostReductionForEachSourceEffect(1, CreaturesYouControlCount.SINGULAR)
|
||||
).addHint(CreaturesYouControlHint.instance));
|
||||
|
||||
// Double strike
|
||||
this.addAbility(DoubleStrikeAbility.getInstance());
|
||||
}
|
||||
|
||||
private LeonardoWorldlyWarrior(final LeonardoWorldlyWarrior card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeonardoWorldlyWarrior copy() {
|
||||
return new LeonardoWorldlyWarrior(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -53,8 +53,8 @@ public final class LongListOfTheEnts extends CardImpl {
|
|||
return new LongListOfTheEnts(this);
|
||||
}
|
||||
|
||||
static String getKey(Game game, Ability source, int offset) {
|
||||
return "EntList_" + source.getSourceId() + "_" + (offset + CardUtil.getActualSourceObjectZoneChangeCounter(game, source));
|
||||
static String getKey(Game game, Ability source) {
|
||||
return "EntList_" + source.getSourceId() + "_" + CardUtil.getActualSourceObjectZoneChangeCounter(game, source);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ enum LongListOfTheEntsHint implements Hint {
|
|||
if (ability.getSourcePermanentIfItStillExists(game) == null) {
|
||||
return null;
|
||||
}
|
||||
Set<SubType> subTypes = (Set<SubType>) game.getState().getValue(LongListOfTheEnts.getKey(game, ability, 0));
|
||||
Set<SubType> subTypes = (Set<SubType>) game.getState().getValue(LongListOfTheEnts.getKey(game, ability));
|
||||
if (subTypes == null || subTypes.isEmpty()) {
|
||||
return "No creature types have been noted yet.";
|
||||
}
|
||||
|
|
@ -109,14 +109,11 @@ class LongListOfTheEntsEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
|
||||
Object existingEntList = game.getState().getValue(LongListOfTheEnts.getKey(game, source, 0));
|
||||
int offset;
|
||||
Object existingEntList = game.getState().getValue(LongListOfTheEnts.getKey(game, source));
|
||||
Set<SubType> newEntList;
|
||||
if (existingEntList == null) {
|
||||
offset = 1; // zcc is off-by-one due to still entering battlefield
|
||||
newEntList = new LinkedHashSet<>();
|
||||
} else {
|
||||
offset = 0;
|
||||
newEntList = new LinkedHashSet<>((Set<SubType>) existingEntList);
|
||||
}
|
||||
Set<String> chosenTypes = newEntList
|
||||
|
|
@ -132,7 +129,7 @@ class LongListOfTheEntsEffect extends OneShotEffect {
|
|||
SubType subType = SubType.byDescription(choice.getChoiceKey());
|
||||
game.informPlayers(player.getLogName() + " notes the creature type " + subType);
|
||||
newEntList.add(subType);
|
||||
game.getState().setValue(LongListOfTheEnts.getKey(game, source, offset), newEntList);
|
||||
game.getState().setValue(LongListOfTheEnts.getKey(game, source), newEntList);
|
||||
FilterSpell filter = new FilterCreatureSpell("a creature spell of that type");
|
||||
filter.add(subType.getPredicate());
|
||||
game.addDelayedTriggeredAbility(new AddCounterNextSpellDelayedTriggeredAbility(filter), source);
|
||||
|
|
|
|||
53
Mage.Sets/src/mage/cards/m/MegaFlare.java
Normal file
53
Mage.Sets/src/mage/cards/m/MegaFlare.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import mage.abilities.condition.common.KickedCondition;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.keyword.KickerAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.game.permanent.token.UtvaraHellkiteDragonToken;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.targetadjustment.ForEachPlayerTargetsAdjuster;
|
||||
import mage.target.targetpointer.EachTargetPointer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class MegaFlare extends CardImpl {
|
||||
|
||||
public MegaFlare(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}");
|
||||
|
||||
// Kicker {3}{R}{R}
|
||||
this.addAbility(new KickerAbility("{3}{R}{R}"));
|
||||
|
||||
// If this spell was kicked, create a 6/6 red Dragon creature token with flying.
|
||||
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
|
||||
new CreateTokenEffect(new UtvaraHellkiteDragonToken()), KickedCondition.ONCE,
|
||||
"if this spell was kicked, create a 6/6 red Dragon creature token with flying"
|
||||
));
|
||||
|
||||
// For each opponent, choose up to one target creature that player controls. Mega Flare deals damage equal to the greatest power among creatures you control to each of the chosen creatures.
|
||||
this.getSpellAbility().addEffect(new DamageTargetEffect(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES)
|
||||
.setText("<br>For each opponent, choose up to one target creature that player controls. " +
|
||||
"{this} deals damage equal to the greatest power among creatures you control " +
|
||||
"to each of the chosen creatures").setTargetPointer(new EachTargetPointer()));
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1));
|
||||
this.getSpellAbility().setTargetAdjuster(new ForEachPlayerTargetsAdjuster(false, true));
|
||||
}
|
||||
|
||||
private MegaFlare(final MegaFlare card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MegaFlare copy() {
|
||||
return new MegaFlare(this);
|
||||
}
|
||||
}
|
||||
62
Mage.Sets/src/mage/cards/m/MichelangeloTheHeart.java
Normal file
62
Mage.Sets/src/mage/cards/m/MichelangeloTheHeart.java
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.common.RaidCondition;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.abilities.hint.common.RaidHint;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.abilities.triggers.BeginningOfSecondMainTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PartnerVariantType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.permanent.token.FoodToken;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.watchers.common.PlayerAttackedWatcher;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class MichelangeloTheHeart extends CardImpl {
|
||||
|
||||
public MichelangeloTheHeart(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// Trample
|
||||
this.addAbility(TrampleAbility.getInstance());
|
||||
|
||||
// Raid (the Fridge) -- At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token.
|
||||
Ability ability = new BeginningOfSecondMainTriggeredAbility(
|
||||
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false
|
||||
).withInterveningIf(RaidCondition.instance);
|
||||
ability.addEffect(new CreateTokenEffect(new FoodToken()).concatBy("and"));
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
this.addAbility(ability.addHint(RaidHint.instance).withFlavorWord("Raid (the Fridge)"), new PlayerAttackedWatcher());
|
||||
|
||||
// Partner--Character select
|
||||
this.addAbility(PartnerVariantType.CHARACTER_SELECT.makeAbility());
|
||||
}
|
||||
|
||||
private MichelangeloTheHeart(final MichelangeloTheHeart card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MichelangeloTheHeart copy() {
|
||||
return new MichelangeloTheHeart(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ public final class MuYanlingCelestialWind extends CardImpl {
|
|||
Ability ability = new LoyaltyAbility(new BoostTargetEffect(
|
||||
-5, 0, Duration.UntilYourNextTurn
|
||||
).setText("Until your next turn, up to one target creature gets -5/-0."), 1);
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
ability.addTarget(new TargetCreaturePermanent(0, 1));
|
||||
this.addAbility(ability);
|
||||
|
||||
// −3: Return up to two target creatures to their owners' hands.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class RampagingGrowthEffect extends OneShotEffect {
|
|||
}
|
||||
TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND_A);
|
||||
player.searchLibrary(target, source, game);
|
||||
Card card = player.getLibrary().getCard(target.getTargetController(), game);
|
||||
Card card = player.getLibrary().getCard(target.getFirstTarget(), game);
|
||||
if (card == null) {
|
||||
player.shuffleLibrary(source, game);
|
||||
return true;
|
||||
|
|
|
|||
96
Mage.Sets/src/mage/cards/r/RaphaelTheMuscle.java
Normal file
96
Mage.Sets/src/mage/cards/r/RaphaelTheMuscle.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.MutagenToken;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class RaphaelTheMuscle extends CardImpl {
|
||||
|
||||
public RaphaelTheMuscle(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.TURTLE);
|
||||
this.power = new MageInt(4);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Double all damage that creatures you control with counters on them would deal.
|
||||
this.addAbility(new SimpleStaticAbility(new RaphaelTheMuscleReplacementEffect()));
|
||||
|
||||
// When Raphael enters, create a Mutagen token.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MutagenToken())));
|
||||
|
||||
// Partner--Character select
|
||||
this.addAbility(PartnerVariantType.CHARACTER_SELECT.makeAbility());
|
||||
}
|
||||
|
||||
private RaphaelTheMuscle(final RaphaelTheMuscle card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaphaelTheMuscle copy() {
|
||||
return new RaphaelTheMuscle(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RaphaelTheMuscleReplacementEffect extends ReplacementEffectImpl {
|
||||
|
||||
RaphaelTheMuscleReplacementEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Damage);
|
||||
staticText = "double all damage that creatures you control with counters on them would deal";
|
||||
}
|
||||
|
||||
private RaphaelTheMuscleReplacementEffect(final RaphaelTheMuscleReplacementEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaphaelTheMuscleReplacementEffect copy() {
|
||||
return new RaphaelTheMuscleReplacementEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
switch (event.getType()) {
|
||||
case DAMAGE_PLAYER:
|
||||
case DAMAGE_PERMANENT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
|
||||
return permanent != null
|
||||
&& permanent.isCreature(game)
|
||||
&& permanent.isControlledBy(source.getControllerId())
|
||||
&& permanent.getCounters(game).getTotalCount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
76
Mage.Sets/src/mage/cards/r/RaphaelsTechnique.java
Normal file
76
Mage.Sets/src/mage/cards/r/RaphaelsTechnique.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.SneakAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class RaphaelsTechnique extends CardImpl {
|
||||
|
||||
public RaphaelsTechnique(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}{R}");
|
||||
|
||||
// Sneak {2}{R}
|
||||
this.addAbility(new SneakAbility(this, "{2}{R}"));
|
||||
|
||||
// Each player may discard their hand and draw seven cards.
|
||||
this.getSpellAbility().addEffect(new RaphaelsTechniqueEffect());
|
||||
}
|
||||
|
||||
private RaphaelsTechnique(final RaphaelsTechnique card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaphaelsTechnique copy() {
|
||||
return new RaphaelsTechnique(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RaphaelsTechniqueEffect extends OneShotEffect {
|
||||
|
||||
RaphaelsTechniqueEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "each player may discard their hand and draw seven cards";
|
||||
}
|
||||
|
||||
private RaphaelsTechniqueEffect(final RaphaelsTechniqueEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaphaelsTechniqueEffect copy() {
|
||||
return new RaphaelsTechniqueEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
List<Player> wheelers = new ArrayList<>();
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null && player.chooseUse(
|
||||
Outcome.DrawCard, "Discard your hand and draw seven?", source, game
|
||||
)) {
|
||||
game.informPlayers(player.getName() + " chooses to discard their hand and draw seven");
|
||||
wheelers.add(player);
|
||||
}
|
||||
}
|
||||
for (Player player : wheelers) {
|
||||
player.discard(player.getHand(), false, source, game);
|
||||
player.drawCards(7, source, game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
package mage.cards.r;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -10,19 +8,21 @@ import mage.abilities.keyword.TrampleAbility;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.ColorPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author North
|
||||
*/
|
||||
public final class RoughshodMentor extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("Green creatures");
|
||||
private static final FilterPermanent filter = new FilterCreaturePermanent("Green creatures");
|
||||
|
||||
static {
|
||||
filter.add(new ColorPredicate(ObjectColor.GREEN));
|
||||
|
|
|
|||
102
Mage.Sets/src/mage/cards/s/SeiferBalambRival.java
Normal file
102
Mage.Sets/src/mage/cards/s/SeiferBalambRival.java
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class SeiferBalambRival extends CardImpl {
|
||||
|
||||
public SeiferBalambRival(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.MERCENARY);
|
||||
this.power = new MageInt(4);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// First strike
|
||||
this.addAbility(FirstStrikeAbility.getInstance());
|
||||
|
||||
// Whenever you attack a player, goad target creature that player controls.
|
||||
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(
|
||||
new GoadTargetEffect().setText("goad target creature that player controls"), SetTargetPointer.NONE
|
||||
);
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Whenever a creature attacking one of your opponents becomes blocked by two or more creatures, that attacking creature gains deathtouch until end of turn.
|
||||
this.addAbility(new SeiferBalambRivalTriggeredAbility());
|
||||
}
|
||||
|
||||
private SeiferBalambRival(final SeiferBalambRival card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeiferBalambRival copy() {
|
||||
return new SeiferBalambRival(this);
|
||||
}
|
||||
}
|
||||
|
||||
class SeiferBalambRivalTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
SeiferBalambRivalTriggeredAbility() {
|
||||
super(Zone.BATTLEFIELD, new GainAbilityTargetEffect(DeathtouchAbility.getInstance()).setText("that attacking creature gains deathtouch until end of turn"));
|
||||
this.setTriggerPhrase("Whenever a creature attacking one of your opponents becomes blocked by two or more creatures, ");
|
||||
}
|
||||
|
||||
private SeiferBalambRivalTriggeredAbility(final SeiferBalambRivalTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeiferBalambRivalTriggeredAbility copy() {
|
||||
return new SeiferBalambRivalTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.CREATURE_BLOCKED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (!game
|
||||
.getOpponents(game.getCombat().getDefenderId(event.getTargetId()))
|
||||
.contains(this.getControllerId())
|
||||
|| game
|
||||
.getCombat()
|
||||
.findGroup(event.getTargetId())
|
||||
.getBlockers()
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(permanent -> permanent.isCreature(game))
|
||||
.count() < 2) {
|
||||
return false;
|
||||
}
|
||||
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,12 +30,12 @@ public final class SpiderSense extends CardImpl {
|
|||
public SpiderSense(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
|
||||
|
||||
// Web-slinging {U}
|
||||
this.addAbility(new WebSlingingAbility(this, "{U}"));
|
||||
|
||||
// Counter target instant spell, sorcery spell, or triggered ability.
|
||||
this.getSpellAbility().addEffect(new CounterTargetEffect());
|
||||
this.getSpellAbility().addTarget(new TargetStackObject(filter));
|
||||
|
||||
// Web-slinging {U}
|
||||
this.addAbility(new WebSlingingAbility(this, "{U}"));
|
||||
}
|
||||
|
||||
private SpiderSense(final SpiderSense card) {
|
||||
|
|
|
|||
57
Mage.Sets/src/mage/cards/s/SplinterTheMentor.java
Normal file
57
Mage.Sets/src/mage/cards/s/SplinterTheMentor.java
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PartnerVariantType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.FilterPermanentThisOrAnother;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.permanent.token.MutagenToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class SplinterTheMentor extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanentThisOrAnother(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN, false
|
||||
);
|
||||
|
||||
public SplinterTheMentor(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.RAT);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(2);
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility());
|
||||
|
||||
// Whenever Splinter or another nontoken creature you control leaves the battlefield, create a Mutagen token.
|
||||
this.addAbility(new LeavesBattlefieldAllTriggeredAbility(new CreateTokenEffect(new MutagenToken()), filter));
|
||||
|
||||
// Partner--Character select
|
||||
this.addAbility(PartnerVariantType.CHARACTER_SELECT.makeAbility());
|
||||
}
|
||||
|
||||
private SplinterTheMentor(final SplinterTheMentor card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SplinterTheMentor copy() {
|
||||
return new SplinterTheMentor(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ public class StrongTheBrutishThespian extends CardImpl {
|
|||
this.toughness = new MageInt(7);
|
||||
|
||||
// Ward {2}
|
||||
this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}")));
|
||||
this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false));
|
||||
|
||||
// Enrage - Whenever Strong is dealt damage, you get three rad counters and put three +1/+1 counters on Strong.
|
||||
Ability enrageAbility = new DealtDamageToSourceTriggeredAbility(new AddCountersPlayersEffect(CounterType.RAD.createInstance(3), TargetController.YOU), false, true);
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class SummonEsperValigarmandaExileEffect extends OneShotEffect {
|
|||
return !cards.isEmpty()
|
||||
&& controller.moveCardsToExile(
|
||||
cards.getCards(game), source, game, true,
|
||||
CardUtil.getExileZoneId(game, source, 1),
|
||||
CardUtil.getExileZoneId(game, source),
|
||||
CardUtil.getSourceName(game, source)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
56
Mage.Sets/src/mage/cards/s/SuperShredder.java
Normal file
56
Mage.Sets/src/mage/cards/s/SuperShredder.java
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class SuperShredder extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("another permanent");
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
}
|
||||
|
||||
public SuperShredder(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.MUTANT);
|
||||
this.subtype.add(SubType.NINJA);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility());
|
||||
|
||||
// Whenever another permanent leaves the battlefield, put a +1/+1 counter on Super Shredder.
|
||||
this.addAbility(new LeavesBattlefieldAllTriggeredAbility(
|
||||
new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter
|
||||
));
|
||||
}
|
||||
|
||||
private SuperShredder(final SuperShredder card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuperShredder copy() {
|
||||
return new SuperShredder(this);
|
||||
}
|
||||
}
|
||||
71
Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java
Normal file
71
Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.common.FilterCreatureCard;
|
||||
import mage.filter.predicate.mageobject.ManaValuePredicate;
|
||||
import mage.target.common.TargetAttackingCreature;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author oscscull
|
||||
*/
|
||||
public final class SurgicalSuiteHospitalRoom extends RoomCard {
|
||||
private static final FilterCard filter = new FilterCreatureCard(
|
||||
"creature card with mana value 3 or less from your graveyard");
|
||||
|
||||
static {
|
||||
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4));
|
||||
}
|
||||
|
||||
public SurgicalSuiteHospitalRoom(UUID ownerId, CardSetInfo setInfo) {
|
||||
// Surgical Suite
|
||||
// {1}{W}
|
||||
// When you unlock this door, return target creature card with mana value 3 or
|
||||
// less from your graveyard to the battlefield.
|
||||
// Hospital Room
|
||||
// {3}{W}
|
||||
// Enchantment -- Room
|
||||
// Whenever you attack, put a +1/+1 counter on target attacking creature.
|
||||
super(ownerId, setInfo,
|
||||
new CardType[] { CardType.ENCHANTMENT },
|
||||
"{1}{W}", "{3}{W}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Left half ability - "When you unlock this door, return target creature card with mana value 3 or
|
||||
// less from your graveyard to the battlefield."
|
||||
UnlockThisDoorTriggeredAbility left = new UnlockThisDoorTriggeredAbility(
|
||||
new ReturnFromGraveyardToBattlefieldTargetEffect(), false, true);
|
||||
left.addTarget(new TargetCardInYourGraveyard(filter));
|
||||
|
||||
// Right half ability - "Whenever you attack, put a +1/+1 counter on target attacking creature."
|
||||
AttacksWithCreaturesTriggeredAbility right = new AttacksWithCreaturesTriggeredAbility(
|
||||
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1
|
||||
);
|
||||
right.addTarget(new TargetAttackingCreature());
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private SurgicalSuiteHospitalRoom(final SurgicalSuiteHospitalRoom card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurgicalSuiteHospitalRoom copy() {
|
||||
return new SurgicalSuiteHospitalRoom(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ class TheAesirEscapeValhallaOneEffect extends OneShotEffect {
|
|||
controller.choose(outcome, target, source, game);
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
if (card != null) {
|
||||
UUID exileId = CardUtil.getExileZoneId(game, source, 1);
|
||||
UUID exileId = CardUtil.getExileZoneId(game, source);
|
||||
MageObject sourceObject = source.getSourceObject(game);
|
||||
String exileName = sourceObject != null ? sourceObject.getName() : "";
|
||||
controller.moveCardsToExile(card, source, game, false, exileId, exileName);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class TheCreationOfAvacynOneEffect extends OneShotEffect {
|
|||
if (card != null) {
|
||||
// exile it face down
|
||||
card.setFaceDown(true, game);
|
||||
UUID exileId = CardUtil.getExileZoneId(game, source, 1);
|
||||
UUID exileId = CardUtil.getExileZoneId(game, source);
|
||||
MageObject sourceObject = source.getSourceObject(game);
|
||||
String exileName = sourceObject != null ? sourceObject.getName() : "";
|
||||
controller.moveCardsToExile(card, source, game, false, exileId, exileName);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public final class TheFinalDays extends CardImpl {
|
|||
|
||||
// Create two tapped 2/2 black Horror creature tokens. If this spell was cast from a graveyard, instead create X of those tokens, where X is the number of creature cards in your graveyard.
|
||||
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
|
||||
new CreateTokenEffect(new Horror3Token(), xValue), new CreateTokenEffect(new Horror3Token(), 2),
|
||||
new CreateTokenEffect(new Horror3Token(), xValue, true, false), new CreateTokenEffect(new Horror3Token(), 2, true),
|
||||
CastFromGraveyardSourceCondition.instance, "create two tapped 2/2 black Horror creature tokens. " +
|
||||
"If this spell was cast from a graveyard, instead create X of those tokens, " +
|
||||
"where X is the number of creature cards in your graveyard"
|
||||
|
|
|
|||
92
Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java
Normal file
92
Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package mage.cards.u;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.token.Demon66Token;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class UnholyAnnexRitualChamber extends RoomCard {
|
||||
|
||||
private static final FilterControlledPermanent filter = new FilterControlledPermanent("Demon");
|
||||
|
||||
static {
|
||||
filter.add(SubType.DEMON.getPredicate());
|
||||
}
|
||||
|
||||
public UnholyAnnexRitualChamber(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}", "{3}{B}{B}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Unholy Annex: At the beginning of your end step, draw a card. If you control a Demon, each opponent loses 2 life and you gain 2 life. Otherwise, you lose 2 life.
|
||||
Ability left = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1));
|
||||
left.addEffect(new ConditionalOneShotEffect(
|
||||
new UnholyAnnexDrainEffect(), new LoseLifeSourceControllerEffect(2),
|
||||
new PermanentsOnTheBattlefieldCondition(filter), "If you control a Demon, each opponent loses 2 life and you gain 2 life. Otherwise, you lose 2 life"
|
||||
));
|
||||
left.addHint(new ConditionHint(new PermanentsOnTheBattlefieldCondition(filter), "You control a Demon"));
|
||||
|
||||
// Ritual Chamber: When you unlock this door, create a 6/6 black Demon creature token with flying.
|
||||
Ability right = new UnlockThisDoorTriggeredAbility(new CreateTokenEffect(new Demon66Token()), false, false);
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private UnholyAnnexRitualChamber(final UnholyAnnexRitualChamber card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnholyAnnexRitualChamber copy() {
|
||||
return new UnholyAnnexRitualChamber(this);
|
||||
}
|
||||
}
|
||||
|
||||
class UnholyAnnexDrainEffect extends OneShotEffect {
|
||||
|
||||
UnholyAnnexDrainEffect() {
|
||||
super(Outcome.GainLife);
|
||||
this.staticText = "each opponent loses 2 life and you gain 2 life";
|
||||
}
|
||||
|
||||
private UnholyAnnexDrainEffect(final UnholyAnnexDrainEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnholyAnnexDrainEffect copy() {
|
||||
return new UnholyAnnexDrainEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (UUID opponentId : game.getOpponents(source.getControllerId())) {
|
||||
Player player = game.getPlayer(opponentId);
|
||||
if (player != null) {
|
||||
player.loseLife(2, game, source, false);
|
||||
}
|
||||
}
|
||||
game.getPlayer(source.getControllerId()).gainLife(2, game, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
53
Mage.Sets/src/mage/cards/v/VivisPersistence.java
Normal file
53
Mage.Sets/src/mage/cards/v/VivisPersistence.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.v;
|
||||
|
||||
import mage.abilities.common.EntersBattlefieldOrAttacksAllTriggeredAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DoIfCostPaid;
|
||||
import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.mageobject.CommanderPredicate;
|
||||
import mage.game.permanent.token.BlackWizardToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class VivisPersistence extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("you commander");
|
||||
|
||||
static {
|
||||
filter.add(TargetController.YOU.getOwnerPredicate());
|
||||
filter.add(CommanderPredicate.instance);
|
||||
}
|
||||
|
||||
public VivisPersistence(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}");
|
||||
|
||||
// Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new BlackWizardToken()));
|
||||
|
||||
// Whenever your commander enters or attacks, you may pay {2}. If you do, return this card from your graveyard to your hand.
|
||||
this.addAbility(new EntersBattlefieldOrAttacksAllTriggeredAbility(
|
||||
Zone.GRAVEYARD,
|
||||
new DoIfCostPaid(new ReturnSourceFromGraveyardToHandEffect(), new GenericManaCost(2)),
|
||||
filter, false
|
||||
));
|
||||
}
|
||||
|
||||
private VivisPersistence(final VivisPersistence card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VivisPersistence copy() {
|
||||
return new VivisPersistence(this);
|
||||
}
|
||||
}
|
||||
49
Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java
Normal file
49
Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package mage.cards.w;
|
||||
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect;
|
||||
import mage.abilities.effects.common.ruleModifying.PlayFromGraveyardControllerEffect;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class WalkInClosetForgottenCellar extends RoomCard {
|
||||
|
||||
public WalkInClosetForgottenCellar(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}", "{3}{G}{G}", SpellAbilityType.SPLIT);
|
||||
this.subtype.add(SubType.ROOM);
|
||||
|
||||
// Walk-In Closet: You may play lands from your graveyard.
|
||||
SimpleStaticAbility left = new SimpleStaticAbility(PlayFromGraveyardControllerEffect.playLands());
|
||||
|
||||
// Forgotten Cellar: When you unlock this door, you may cast spells from your graveyard this turn, and if a card would be put into your graveyard from anywhere this turn, exile it instead.
|
||||
UnlockThisDoorTriggeredAbility right = new UnlockThisDoorTriggeredAbility(
|
||||
new PlayFromGraveyardControllerEffect(StaticFilters.FILTER_CARD_NON_LAND, Duration.EndOfTurn)
|
||||
.setText("you may cast spells from your graveyard this turn"), false, false
|
||||
);
|
||||
right.addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn).concatBy(", and")
|
||||
.setText("if a card would be put into your graveyard from anywhere this turn, exile it instead")
|
||||
);
|
||||
|
||||
this.addRoomAbilities(left, right);
|
||||
}
|
||||
|
||||
private WalkInClosetForgottenCellar(final WalkInClosetForgottenCellar card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WalkInClosetForgottenCellar copy() {
|
||||
return new WalkInClosetForgottenCellar(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Blazemire Verge", 329, Rarity.RARE, mage.cards.b.BlazemireVerge.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Bleeding Woods", 257, Rarity.COMMON, mage.cards.b.BleedingWoods.class));
|
||||
cards.add(new SetCardInfo("Boilerbilges Ripper", 127, Rarity.COMMON, mage.cards.b.BoilerbilgesRipper.class));
|
||||
cards.add(new SetCardInfo("Bottomless Pool // Locker Room", 43, Rarity.UNCOMMON, mage.cards.b.BottomlessPoolLockerRoom.class));
|
||||
cards.add(new SetCardInfo("Break Down the Door", 170, Rarity.UNCOMMON, mage.cards.b.BreakDownTheDoor.class));
|
||||
cards.add(new SetCardInfo("Broodspinner", 211, Rarity.UNCOMMON, mage.cards.b.Broodspinner.class));
|
||||
cards.add(new SetCardInfo("Cackling Slasher", 85, Rarity.COMMON, mage.cards.c.CacklingSlasher.class));
|
||||
|
|
@ -69,6 +70,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Cynical Loner", 89, Rarity.UNCOMMON, mage.cards.c.CynicalLoner.class));
|
||||
cards.add(new SetCardInfo("Daggermaw Megalodon", 48, Rarity.COMMON, mage.cards.d.DaggermawMegalodon.class));
|
||||
cards.add(new SetCardInfo("Dashing Bloodsucker", 90, Rarity.UNCOMMON, mage.cards.d.DashingBloodsucker.class));
|
||||
cards.add(new SetCardInfo("Dazzling Theater // Prop Room", 3, Rarity.RARE, mage.cards.d.DazzlingTheaterPropRoom.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Dazzling Theater // Prop Room", 334, Rarity.RARE, mage.cards.d.DazzlingTheaterPropRoom.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Defiant Survivor", 175, Rarity.UNCOMMON, mage.cards.d.DefiantSurvivor.class));
|
||||
cards.add(new SetCardInfo("Demonic Counsel", 310, Rarity.RARE, mage.cards.d.DemonicCounsel.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Demonic Counsel", 92, Rarity.RARE, mage.cards.d.DemonicCounsel.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
@ -76,6 +79,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Dissection Tools", 385, Rarity.RARE, mage.cards.d.DissectionTools.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Disturbing Mirth", 212, Rarity.UNCOMMON, mage.cards.d.DisturbingMirth.class));
|
||||
cards.add(new SetCardInfo("Diversion Specialist", 132, Rarity.UNCOMMON, mage.cards.d.DiversionSpecialist.class));
|
||||
cards.add(new SetCardInfo("Dollmaker's Shop // Porcelain Gallery", 4, Rarity.MYTHIC, mage.cards.d.DollmakersShopPorcelainGallery.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Dollmaker's Shop // Porcelain Gallery", 335, Rarity.MYTHIC, mage.cards.d.DollmakersShopPorcelainGallery.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Don't Make a Sound", 49, Rarity.COMMON, mage.cards.d.DontMakeASound.class));
|
||||
cards.add(new SetCardInfo("Doomsday Excruciator", 346, Rarity.RARE, mage.cards.d.DoomsdayExcruciator.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Doomsday Excruciator", 94, Rarity.RARE, mage.cards.d.DoomsdayExcruciator.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
@ -139,6 +144,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Frantic Strength", 179, Rarity.COMMON, mage.cards.f.FranticStrength.class));
|
||||
cards.add(new SetCardInfo("Friendly Ghost", 12, Rarity.COMMON, mage.cards.f.FriendlyGhost.class));
|
||||
cards.add(new SetCardInfo("Friendly Teddy", 247, Rarity.COMMON, mage.cards.f.FriendlyTeddy.class));
|
||||
cards.add(new SetCardInfo("Funeral Room // Awakening Hall", 100, Rarity.MYTHIC, mage.cards.f.FuneralRoomAwakeningHall.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Funeral Room // Awakening Hall", 338, Rarity.MYTHIC, mage.cards.f.FuneralRoomAwakeningHall.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class));
|
||||
cards.add(new SetCardInfo("Ghost Vacuum", 248, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Ghost Vacuum", 326, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
@ -318,6 +325,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Stay Hidden, Stay Silent", 291, Rarity.UNCOMMON, mage.cards.s.StayHiddenStaySilent.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Stay Hidden, Stay Silent", 74, Rarity.UNCOMMON, mage.cards.s.StayHiddenStaySilent.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Strangled Cemetery", 268, Rarity.COMMON, mage.cards.s.StrangledCemetery.class));
|
||||
cards.add(new SetCardInfo("Surgical Suite // Hospital Room", 34, Rarity.UNCOMMON, mage.cards.s.SurgicalSuiteHospitalRoom.class));
|
||||
cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Swamp", 281, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Swamp", 282, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
@ -357,6 +365,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Undead Sprinter", 350, Rarity.RARE, mage.cards.u.UndeadSprinter.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Under the Skin", 203, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Under the Skin", 323, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unholy Annex // Ritual Chamber", 118, Rarity.RARE, mage.cards.u.UnholyAnnexRitualChamber.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unholy Annex // Ritual Chamber", 339, Rarity.RARE, mage.cards.u.UnholyAnnexRitualChamber.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unidentified Hovership", 305, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unidentified Hovership", 37, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Unnerving Grasp", 80, Rarity.UNCOMMON, mage.cards.u.UnnervingGrasp.class));
|
||||
|
|
@ -385,6 +395,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Victor, Valgavoth's Seneschal", 364, Rarity.RARE, mage.cards.v.VictorValgavothsSeneschal.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Vile Mutilator", 122, Rarity.UNCOMMON, mage.cards.v.VileMutilator.class));
|
||||
cards.add(new SetCardInfo("Violent Urge", 164, Rarity.UNCOMMON, mage.cards.v.ViolentUrge.class));
|
||||
cards.add(new SetCardInfo("Walk-In Closet // Forgotten Cellar", 205, Rarity.MYTHIC, mage.cards.w.WalkInClosetForgottenCellar.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Walk-In Closet // Forgotten Cellar", 341, Rarity.MYTHIC, mage.cards.w.WalkInClosetForgottenCellar.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Waltz of Rage", 165, Rarity.RARE, mage.cards.w.WaltzOfRage.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Waltz of Rage", 318, Rarity.RARE, mage.cards.w.WaltzOfRage.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Wary Watchdog", 206, Rarity.COMMON, mage.cards.w.WaryWatchdog.class));
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ public final class FinalFantasyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Fight Rigging", 303, Rarity.RARE, mage.cards.f.FightRigging.class));
|
||||
cards.add(new SetCardInfo("Final Judgment", 243, Rarity.MYTHIC, mage.cards.f.FinalJudgment.class));
|
||||
cards.add(new SetCardInfo("Fire-Lit Thicket", 392, Rarity.RARE, mage.cards.f.FireLitThicket.class));
|
||||
cards.add(new SetCardInfo("Fishing Gear", 461, Rarity.RARE, mage.cards.f.FishingGear.class));
|
||||
cards.add(new SetCardInfo("Flash Photography", 463, Rarity.RARE, mage.cards.f.FlashPhotography.class));
|
||||
cards.add(new SetCardInfo("Flayer of the Hatebound", 293, Rarity.RARE, mage.cards.f.FlayerOfTheHatebound.class));
|
||||
cards.add(new SetCardInfo("Flooded Grove", 393, Rarity.RARE, mage.cards.f.FloodedGrove.class));
|
||||
|
|
@ -257,6 +258,7 @@ public final class FinalFantasyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Maester Seymour", 160, Rarity.RARE, mage.cards.m.MaesterSeymour.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Maester Seymour", 68, Rarity.RARE, mage.cards.m.MaesterSeymour.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Mask of Memory", 350, Rarity.UNCOMMON, mage.cards.m.MaskOfMemory.class));
|
||||
cards.add(new SetCardInfo("Mega Flare", 456, Rarity.RARE, mage.cards.m.MegaFlare.class));
|
||||
cards.add(new SetCardInfo("Meteor Golem", 351, Rarity.UNCOMMON, mage.cards.m.MeteorGolem.class));
|
||||
cards.add(new SetCardInfo("Millikin", 352, Rarity.UNCOMMON, mage.cards.m.Millikin.class));
|
||||
cards.add(new SetCardInfo("Mind Stone", 353, Rarity.UNCOMMON, mage.cards.m.MindStone.class));
|
||||
|
|
@ -332,6 +334,7 @@ public final class FinalFantasyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Secret Rendezvous", 218, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Secret Rendezvous", 219, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Secret Rendezvous", 253, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Seifer, Balamb Rival", 451, Rarity.RARE, mage.cards.s.SeiferBalambRival.class));
|
||||
cards.add(new SetCardInfo("Sephiroth, Fallen Hero", 182, Rarity.RARE, mage.cards.s.SephirothFallenHero.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Sephiroth, Fallen Hero", 92, Rarity.RARE, mage.cards.s.SephirothFallenHero.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Sepulchral Primordial", 284, Rarity.RARE, mage.cards.s.SepulchralPrimordial.class));
|
||||
|
|
@ -466,6 +469,7 @@ public final class FinalFantasyCommander extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Vincent, Vengeful Atoner", 64, Rarity.RARE, mage.cards.v.VincentVengefulAtoner.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Vindicate", 330, Rarity.RARE, mage.cards.v.Vindicate.class));
|
||||
cards.add(new SetCardInfo("Vineglimmer Snarl", 440, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class));
|
||||
cards.add(new SetCardInfo("Vivi's Persistence", 458, Rarity.RARE, mage.cards.v.VivisPersistence.class));
|
||||
cards.add(new SetCardInfo("Void Rend", 331, Rarity.RARE, mage.cards.v.VoidRend.class));
|
||||
cards.add(new SetCardInfo("Wakka, Devoted Guardian", 190, Rarity.RARE, mage.cards.w.WakkaDevotedGuardian.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Wakka, Devoted Guardian", 477, Rarity.RARE, mage.cards.w.WakkaDevotedGuardian.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
|||
40
Mage.Sets/src/mage/sets/TeenageMutantNinjaTurtles.java
Normal file
40
Mage.Sets/src/mage/sets/TeenageMutantNinjaTurtles.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package mage.sets;
|
||||
|
||||
import mage.cards.ExpansionSet;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.SetType;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class TeenageMutantNinjaTurtles extends ExpansionSet {
|
||||
|
||||
private static final TeenageMutantNinjaTurtles instance = new TeenageMutantNinjaTurtles();
|
||||
|
||||
public static TeenageMutantNinjaTurtles getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private TeenageMutantNinjaTurtles() {
|
||||
super("Teenage Mutant Ninja Turtles", "TMT", ExpansionSet.buildDate(2026, 3, 6), SetType.EXPANSION);
|
||||
this.blockName = "Teenage Mutant Ninja Turtles"; // for sorting in GUI
|
||||
this.hasBasicLands = true;
|
||||
|
||||
cards.add(new SetCardInfo("April O'Neil, Hacktivist", 29, Rarity.RARE, mage.cards.a.AprilONeilHacktivist.class));
|
||||
cards.add(new SetCardInfo("Bebop & Rocksteady", 140, Rarity.RARE, mage.cards.b.BebopAndRocksteady.class));
|
||||
cards.add(new SetCardInfo("Casey Jones, Jury-Rig Justiciar", 87, Rarity.UNCOMMON, mage.cards.c.CaseyJonesJuryRigJusticiar.class));
|
||||
cards.add(new SetCardInfo("Forest", 257, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Forest", 314, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Island", 254, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Island", 311, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Krang, Master Mind", 43, Rarity.RARE, mage.cards.k.KrangMasterMind.class));
|
||||
cards.add(new SetCardInfo("Mountain", 256, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Mountain", 313, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Plains", 253, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Plains", 310, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Raphael's Technique", 105, Rarity.RARE, mage.cards.r.RaphaelsTechnique.class));
|
||||
cards.add(new SetCardInfo("Super Shredder", 83, Rarity.MYTHIC, mage.cards.s.SuperShredder.class));
|
||||
cards.add(new SetCardInfo("Swamp", 255, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
|
||||
cards.add(new SetCardInfo("Swamp", 312, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package mage.sets;
|
||||
|
||||
import mage.cards.ExpansionSet;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.SetType;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class TeenageMutantNinjaTurtlesEternal extends ExpansionSet {
|
||||
|
||||
private static final TeenageMutantNinjaTurtlesEternal instance = new TeenageMutantNinjaTurtlesEternal();
|
||||
|
||||
public static TeenageMutantNinjaTurtlesEternal getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private TeenageMutantNinjaTurtlesEternal() {
|
||||
super("Teenage Mutant Ninja Turtles Eternal", "TMC", ExpansionSet.buildDate(2026, 3, 6), SetType.EXPANSION);
|
||||
this.hasBasicLands = false;
|
||||
|
||||
cards.add(new SetCardInfo("Dark Ritual", 131, Rarity.MYTHIC, mage.cards.d.DarkRitual.class));
|
||||
cards.add(new SetCardInfo("Donatello, Rad Scientist", 109, Rarity.MYTHIC, mage.cards.d.DonatelloRadScientist.class));
|
||||
cards.add(new SetCardInfo("Donatello, the Brains", 2, Rarity.MYTHIC, mage.cards.d.DonatelloTheBrains.class));
|
||||
cards.add(new SetCardInfo("Donnie & April, Adorkable Duo", 111, Rarity.RARE, mage.cards.d.DonnieAndAprilAdorkableDuo.class));
|
||||
cards.add(new SetCardInfo("Heroes in a Half Shell", 6, Rarity.MYTHIC, mage.cards.h.HeroesInAHalfShell.class));
|
||||
cards.add(new SetCardInfo("Leonardo, Worldly Warrior", 101, Rarity.MYTHIC, mage.cards.l.LeonardoWorldlyWarrior.class));
|
||||
cards.add(new SetCardInfo("Leonardo, the Balance", 1, Rarity.MYTHIC, mage.cards.l.LeonardoTheBalance.class));
|
||||
cards.add(new SetCardInfo("Michelangelo, the Heart", 5, Rarity.MYTHIC, mage.cards.m.MichelangeloTheHeart.class));
|
||||
cards.add(new SetCardInfo("Raphael, the Muscle", 4, Rarity.MYTHIC, mage.cards.r.RaphaelTheMuscle.class));
|
||||
cards.add(new SetCardInfo("Splinter, the Mentor", 3, Rarity.MYTHIC, mage.cards.s.SplinterTheMentor.class));
|
||||
}
|
||||
}
|
||||
|
|
@ -50,21 +50,29 @@ public class SimulationTriggersAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
@Test
|
||||
public void test_DeepglowSkate_PerformanceOnTooManyChoices() {
|
||||
// bug: game freeze with 100% CPU usage
|
||||
// https://github.com/magefree/mage/issues/9438
|
||||
int cardsCount = 2; // 2+ cards will generate too much target options for simulations
|
||||
int boostMultiplier = (int) Math.pow(2, cardsCount);
|
||||
int quantity = 1;
|
||||
String[] cardNames = {
|
||||
"Island", "Plains", "Swamp", "Mountain",
|
||||
"Runeclaw Bear", "Absolute Law", "Gilded Lotus", "Alpha Myr"
|
||||
};
|
||||
|
||||
// When Deepglow Skate enters the battlefield, double the number of each kind of counter on any number
|
||||
// of target permanents.
|
||||
addCard(Zone.HAND, playerA, "Deepglow Skate", cardsCount); // {4}{U}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5 * cardsCount);
|
||||
//
|
||||
addCard(Zone.HAND, playerA, "Deepglow Skate", 1); // {4}{U}
|
||||
// Bloat the battlefield with permanents (possible targets)
|
||||
for (String card : cardNames) {
|
||||
addCard(Zone.BATTLEFIELD, playerA, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerB, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerC, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerD, card, quantity);
|
||||
}
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ajani, Adversary of Tyrants", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ajani, Caller of the Pride", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Ajani Goldmane", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Ajani, Inspiring Leader", 1); // x5 loyalty
|
||||
//
|
||||
|
||||
// Players can't activate planeswalkers' loyalty abilities.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Immortal Sun", 1); // disable planeswalkers usage by AI
|
||||
|
||||
|
|
@ -75,9 +83,9 @@ public class SimulationTriggersAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Deepglow Skate", cardsCount);
|
||||
assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * boostMultiplier);
|
||||
assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * boostMultiplier);
|
||||
assertPermanentCount(playerA, "Deepglow Skate", 1);
|
||||
assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * 2);
|
||||
assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * 2);
|
||||
assertCounterCount(playerB, "Ajani Goldmane", CounterType.LOYALTY, 4);
|
||||
assertCounterCount(playerB, "Ajani, Inspiring Leader", CounterType.LOYALTY, 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,904 @@
|
|||
package org.mage.test.cards.cost.splitcards;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.EmptyNames;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
*/
|
||||
public class RoomCardTest extends CardTestPlayerBase {
|
||||
|
||||
// Bottomless pool is cast. It unlocks, and the trigger to return a creature
|
||||
// should bounce one of two grizzly bears.
|
||||
@Test
|
||||
public void testBottomlessPoolETB() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 2);
|
||||
|
||||
checkPlayableAbility("playerA can cast Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"Cast Bottomless Pool", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
|
||||
// Target one of playerB's "Grizzly Bears" with the return effect.
|
||||
addTarget(playerA, "Grizzly Bears");
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Grizzly Bears" is still on playerB's battlefield.
|
||||
assertPermanentCount(playerB, "Grizzly Bears", 1);
|
||||
// Verify that one "Grizzly Bears" has been returned to playerB's hand.
|
||||
assertHandCount(playerB, "Grizzly Bears", 1);
|
||||
// Verify that "Bottomless Pool" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 1);
|
||||
// Verify that "Bottomless Pool" is an Enchantment.
|
||||
assertType("Bottomless Pool", CardType.ENCHANTMENT, true);
|
||||
// Verify that "Bottomless Pool" has the Room subtype.
|
||||
assertSubtype("Bottomless Pool", SubType.ROOM);
|
||||
}
|
||||
|
||||
// Locker room is cast. It enters, and gives a coastal piracy effect that
|
||||
// triggers on damage.
|
||||
@Test
|
||||
public void testLockerRoomCombatDamageTrigger() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
|
||||
// Cards to be drawn
|
||||
addCard(Zone.LIBRARY, playerA, "Plains", 2); // Expected cards to be drawn
|
||||
|
||||
// 2 attackers
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room");
|
||||
attack(1, playerA, "Memnite");
|
||||
attack(1, playerA, "Memnite");
|
||||
// After combat damage, Memnites dealt combat damage to playerB (1 damage * 2).
|
||||
// 2 Locker Room triggers should go on the stack.
|
||||
checkStackSize("Locker Room trigger must be on the stack", 1, PhaseStep.COMBAT_DAMAGE, playerA, 2);
|
||||
checkStackObject("Locker Room trigger must be correct", 1, PhaseStep.COMBAT_DAMAGE, playerA,
|
||||
"Whenever a creature you control deals combat damage to an opponent, draw a card.", 2);
|
||||
|
||||
// Stop at the end of the combat phase to check triggers.
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
|
||||
// Assertions after the first execute() (Locker Room and creatures are on
|
||||
// battlefield, combat resolved):
|
||||
assertPermanentCount(playerA, "Locker Room", 1);
|
||||
assertType("Locker Room", CardType.ENCHANTMENT, true);
|
||||
assertSubtype("Locker Room", SubType.ROOM);
|
||||
assertPermanentCount(playerA, "Memnite", 2);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute(); // Resolve the Locker Room trigger.
|
||||
|
||||
// PlayerA should have drawn two plains cards
|
||||
assertHandCount(playerA, "Plains", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBottomlessPoolUnlock() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||
|
||||
// 2 creatures owned by player A
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkPlayableAbility("playerA can unlock Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}: Unlock the left half.", true);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}: Unlock the left half.");
|
||||
addTarget(playerA, "Memnite");
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Memnite" is still on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Memnite", 1);
|
||||
// Verify that one "Memnite" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Memnite", 1);
|
||||
// Verify that "Bottomless Pool // Locker Room" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
// Verify that "Bottomless Pool // Locker Room" is an Enchantment.
|
||||
assertType("Bottomless Pool // Locker Room", CardType.ENCHANTMENT, true);
|
||||
// Verify that "Bottomless Pool // Locker Room" has the Room subtype.
|
||||
assertSubtype("Bottomless Pool // Locker Room", SubType.ROOM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlickerNameAndManaCost() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Felidar Guardian");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
|
||||
// creatures owned by player A
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
// resolve spell cast
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// unlock and trigger bounce on Memnite
|
||||
addTarget(playerA, "Memnite");
|
||||
// resolve bounce
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Felidar Guardian");
|
||||
// resolve spell cast
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// etb and flicker on Bottomless Pool
|
||||
setChoice(playerA, "Yes");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Memnite" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Memnite", 1);
|
||||
// Verify that a room with no name is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1);
|
||||
// Verify that "Felidar Guardian" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Felidar Guardian", 1);
|
||||
// Verify that a room with no name is an Enchantment.
|
||||
assertType(EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), CardType.ENCHANTMENT, true);
|
||||
// Verify that a room with no name has the Room subtype.
|
||||
assertSubtype(EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), SubType.ROOM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlickerCanBeUnlockedAgain() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Felidar Guardian");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||
|
||||
// creatures owned by player A
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
// resolve spell cast
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// unlock and trigger bounce on Memnite
|
||||
addTarget(playerA, "Memnite");
|
||||
// resolve bounce
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Felidar Guardian");
|
||||
// resolve spell cast
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// etb and flicker on Bottomless Pool
|
||||
setChoice(playerA, "Yes");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// can unlock again
|
||||
checkPlayableAbility("playerA can unlock Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}: Unlock the left half.", true);
|
||||
// unlock again
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}: Unlock the left half.");
|
||||
addTarget(playerA, "Black Knight");
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Memnite" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Memnite", 1);
|
||||
// Verify that one "Black Knight" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Black Knight", 1);
|
||||
// Verify that "Bottomless Pool" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 1);
|
||||
// Verify that "Felidar Guardian" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Felidar Guardian", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEerie() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Erratic Apparition", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
// resolve spell cast
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
setChoice(playerA, "When you unlock"); // x2 triggers
|
||||
// don't bounce anything
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
// resolve ability
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// unlock other side
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that "Bottomless Pool // Locker Room" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
// Verify that "Erratic Apparition" is on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Erratic Apparition", 1);
|
||||
// Verify that "Erratic Apparition" has been pumped twice (etb + fully unlock)
|
||||
assertPowerToughness(playerA, "Erratic Apparition", 3, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyOnStack() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner’s hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "See Double");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ornithopter", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
// Copy spell on the stack
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "See Double");
|
||||
setModeChoice(playerA, "1");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 3);
|
||||
addTarget(playerA, "Memnite");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, "Ornithopter");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Memnite" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Memnite", 1);
|
||||
// Verify that one "Ornithopter" has been returned to playerA's hand.
|
||||
assertHandCount(playerA, "Ornithopter", 1);
|
||||
// Verify that 2 "Bottomless Pool" are on playerA's battlefield.
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyOnBattlefield() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Clever Impersonator");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ornithopter", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, "Memnite");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// Copy spell on the battlefield
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clever Impersonator");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
setChoice(playerA, "Yes");
|
||||
setChoice(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}: Unlock the left half.");
|
||||
addTarget(playerA, "Ornithopter");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that one "Memnite" has been returned to playerA's hand (from original
|
||||
// unlock).
|
||||
assertHandCount(playerA, "Memnite", 1);
|
||||
// Verify that "Ornithopter" has been returned to playerA's hand (from clone
|
||||
// unlock).
|
||||
assertHandCount(playerA, "Ornithopter", 1);
|
||||
// Verify that the original "Bottomless Pool" is on playerA's battlefield, and a
|
||||
// clone.
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchOnStack() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
|
||||
// Mindreaver
|
||||
// {U}{U}
|
||||
// Creature — Human Wizard
|
||||
// Heroic — Whenever you cast a spell that targets this creature, exile the top
|
||||
// three cards of target player’s library.
|
||||
// {U}{U}, Sacrifice this creature: Counter target spell with the same name as a
|
||||
// card exiled with this creature.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Twiddle");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mindreaver", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||
addCard(Zone.LIBRARY, playerA, "Bottomless Pool // Locker Room", 1);
|
||||
addCard(Zone.LIBRARY, playerA, "Plains", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
// tap or untap target permanent
|
||||
addTarget(playerA, "Mindreaver");
|
||||
// tap that permanent?
|
||||
setChoice(playerA, "No");
|
||||
// Whenever you cast a spell that targets this creature, exile the top
|
||||
// three cards of target player’s library.
|
||||
addTarget(playerA, playerA);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{U}{U}, Sacrifice {this}:");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchOnFieldFromLocked() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
//
|
||||
// Opalescence
|
||||
// {2}{W}{W}
|
||||
// Enchantment
|
||||
// Each other non-Aura enchantment is a creature in addition to its other types
|
||||
// and has base power and base toughness each equal to its mana value.
|
||||
//
|
||||
// Glorious Anthem
|
||||
// {1}{W}{W}
|
||||
// Enchantment
|
||||
// Creatures you control get +1/+1.
|
||||
//
|
||||
// Cackling Counterpart
|
||||
// {1}{U}{U}
|
||||
// Instant
|
||||
// Create a token that's a copy of target creature you control.
|
||||
//
|
||||
// Bile Blight
|
||||
// {B}{B}
|
||||
// Instant
|
||||
// Target creature and all other creatures with the same name as that creature
|
||||
// get -3/-3 until end of turn.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4);
|
||||
addCard(Zone.HAND, playerA, "Cackling Counterpart");
|
||||
addCard(Zone.HAND, playerA, "Bile Blight");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Glorious Anthem");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Opalescence");
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Locker Room (unlocked right half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bottomless Pool then unlock Locker Room (both halves unlocked)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Create a fully locked room using Cackling Counterpart
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bile Blight targeting the fully locked room
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bile Blight");
|
||||
addTarget(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand());
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// The fully locked room should be affected by Bile Blight (-3/-3)
|
||||
// Since it's a 0/0 creature (mana value 0) +1/+1 from anthem, it becomes 1/1,
|
||||
// then -2/-2 after Bile Blight (dies)
|
||||
assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 0);
|
||||
// Token, so nothing should be in grave
|
||||
assertGraveyardCount(playerA, "Bottomless Pool // Locker Room", 0);
|
||||
|
||||
// Other rooms should NOT be affected by Bile Blight since they have different
|
||||
// names
|
||||
// Bottomless Pool: 1/1 base + 1/1 from anthem = 2/2
|
||||
assertPowerToughness(playerA, "Bottomless Pool", 2, 2);
|
||||
// Locker Room: 5/5 base + 1/1 from anthem = 6/6
|
||||
assertPowerToughness(playerA, "Locker Room", 6, 6);
|
||||
// Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem = 7/7
|
||||
assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 7, 7);
|
||||
|
||||
// Verify remaining rooms are still on battlefield
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 1);
|
||||
assertPermanentCount(playerA, "Locker Room", 1);
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchOnFieldFromHalf() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
//
|
||||
// Opalescence
|
||||
// {2}{W}{W}
|
||||
// Enchantment
|
||||
// Each other non-Aura enchantment is a creature in addition to its other types
|
||||
// and has base power and base toughness each equal to its mana value.
|
||||
//
|
||||
// Glorious Anthem
|
||||
// {1}{W}{W}
|
||||
// Enchantment
|
||||
// Creatures you control get +1/+1.
|
||||
//
|
||||
// Cackling Counterpart
|
||||
// {1}{U}{U}
|
||||
// Instant
|
||||
// Create a token that's a copy of target creature you control.
|
||||
//
|
||||
// Bile Blight
|
||||
// {B}{B}
|
||||
// Instant
|
||||
// Target creature and all other creatures with the same name as that creature
|
||||
// get -3/-3 until end of turn.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4);
|
||||
addCard(Zone.HAND, playerA, "Cackling Counterpart");
|
||||
addCard(Zone.HAND, playerA, "Bile Blight");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Glorious Anthem");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Opalescence");
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Locker Room (unlocked right half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bottomless Pool then unlock Locker Room (both halves unlocked)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Create a fully locked room using Cackling Counterpart
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bile Blight targeting the half locked room
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bile Blight");
|
||||
addTarget(playerA, "Locker Room");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Locker Room and Bottomless Pool // Locker Room should both be affected by
|
||||
// Bile Blight
|
||||
// since they share the "Locker Room" name component
|
||||
|
||||
// Locker Room: 5/5 base + 1/1 from anthem - 3/3 from Bile Blight = 3/3
|
||||
assertPowerToughness(playerA, "Locker Room", 3, 3);
|
||||
// Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem - 3/3 from Bile
|
||||
// Blight = 4/4
|
||||
assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 4, 4);
|
||||
|
||||
// Other rooms should NOT be affected
|
||||
// Bottomless Pool: 1/1 base + 1/1 from anthem = 2/2 (unaffected)
|
||||
assertPowerToughness(playerA, "Bottomless Pool", 2, 2);
|
||||
// Fully locked room: 0/0 base + 1/1 from anthem = 1/1 (unaffected)
|
||||
assertPowerToughness(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1, 1);
|
||||
|
||||
// Verify all rooms are still on battlefield
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 1);
|
||||
assertPermanentCount(playerA, "Locker Room", 1);
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchOnFieldFromUnlocked() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
//
|
||||
// Opalescence
|
||||
// {2}{W}{W}
|
||||
// Enchantment
|
||||
// Each other non-Aura enchantment is a creature in addition to its other types
|
||||
// and has base power and base toughness each equal to its mana value.
|
||||
//
|
||||
// Glorious Anthem
|
||||
// {1}{W}{W}
|
||||
// Enchantment
|
||||
// Creatures you control get +1/+1.
|
||||
//
|
||||
// Cackling Counterpart
|
||||
// {1}{U}{U}
|
||||
// Instant
|
||||
// Create a token that's a copy of target creature you control.
|
||||
//
|
||||
// Bile Blight
|
||||
// {B}{B}
|
||||
// Instant
|
||||
// Target creature and all other creatures with the same name as that creature
|
||||
// get -3/-3 until end of turn.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4);
|
||||
addCard(Zone.HAND, playerA, "Cackling Counterpart");
|
||||
addCard(Zone.HAND, playerA, "Bile Blight");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Glorious Anthem");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Opalescence");
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Locker Room (unlocked right half)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bottomless Pool then unlock Locker Room (both halves unlocked)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Create a fully locked room using Cackling Counterpart
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Bile Blight targeting the fully locked room
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bile Blight");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// All rooms except the fully locked room should be affected by Bile Blight
|
||||
// since they all share name components with "Bottomless Pool // Locker Room"
|
||||
|
||||
// Bottomless Pool: 1/1 base + 1/1 from anthem - 3/3 from Bile Blight = -1/-1
|
||||
// (dies)
|
||||
assertPermanentCount(playerA, "Bottomless Pool", 0);
|
||||
assertGraveyardCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
|
||||
// Locker Room: 5/5 base + 1/1 from anthem - 3/3 from Bile Blight = 3/3
|
||||
assertPowerToughness(playerA, "Locker Room", 3, 3);
|
||||
|
||||
// Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem - 3/3 from Bile
|
||||
// Blight = 4/4
|
||||
assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 4, 4);
|
||||
|
||||
// Fully locked room should NOT be affected (different name)
|
||||
// Fully locked room: 0/0 base + 1/1 from anthem = 1/1 (unaffected)
|
||||
assertPowerToughness(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1, 1);
|
||||
|
||||
// Verify remaining rooms are still on battlefield
|
||||
assertPermanentCount(playerA, "Locker Room", 1);
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCounterspellThenReanimate() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Counterspell");
|
||||
addCard(Zone.HAND, playerA, "Campus Renovation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||
|
||||
// Target creature for potential bounce (should not be bounced)
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1);
|
||||
|
||||
// Cast Bottomless Pool
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
|
||||
// Counter it while on stack
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Counterspell");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Use Campus Renovation to return it from graveyard
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Campus Renovation");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Assertions:
|
||||
// Verify that "Grizzly Bears" is still on playerB's battlefield (not bounced)
|
||||
assertPermanentCount(playerB, "Grizzly Bears", 1);
|
||||
// Verify that "Grizzly Bears" is not in playerB's hand
|
||||
assertHandCount(playerB, "Grizzly Bears", 0);
|
||||
// Verify that a room with no name is on playerA's battlefield
|
||||
assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1);
|
||||
// Verify that the nameless room is an Enchantment
|
||||
assertType(EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), CardType.ENCHANTMENT, true);
|
||||
// Verify that the nameless room has the Room subtype
|
||||
assertSubtype(EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), SubType.ROOM);
|
||||
// Verify that Campus Renovation is in graveyard
|
||||
assertGraveyardCount(playerA, "Campus Renovation", 1);
|
||||
// Verify that Counterspell is in graveyard
|
||||
assertGraveyardCount(playerA, "Counterspell", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPithingNeedleActivatedAbility() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
//
|
||||
// Opalescence
|
||||
// {2}{W}{W}
|
||||
// Enchantment
|
||||
// Each other non-Aura enchantment is a creature in addition to its other types
|
||||
// and has base power and base toughness each equal to its mana value.
|
||||
//
|
||||
// Diviner's Wand
|
||||
// {3}
|
||||
// Kindred Artifact — Wizard Equipment
|
||||
// Equipped creature has "Whenever you draw a card, this creature gets +1/+1
|
||||
// and gains flying until end of turn" and "{4}: Draw a card."
|
||||
// Whenever a Wizard creature enters, you may attach this Equipment to it.
|
||||
// Equip {3}
|
||||
//
|
||||
// Pithing Needle
|
||||
// {1}
|
||||
// Artifact
|
||||
// As Pithing Needle enters, choose a card name.
|
||||
// Activated abilities of sources with the chosen name can't be activated.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Pithing Needle");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Opalescence");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Diviner's Wand");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 20);
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half only)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Equip Diviner's Wand
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}");
|
||||
addTarget(playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Pithing Needle naming the locked side
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle");
|
||||
setChoice(playerA, "Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Validate that the room can activate the gained ability
|
||||
checkPlayableAbility("Room can use Diviner's Wand ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA,
|
||||
"{4}: Draw a card.", true);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Unlock the other side
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Validate that you can no longer activate the ability
|
||||
checkPlayableAbility("Room cannot use Diviner's Wand ability after unlock", 1, PhaseStep.PRECOMBAT_MAIN,
|
||||
playerA, "{4}: Draw a card.", false);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Verify the room is now fully unlocked
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
}
|
||||
|
||||
// Test converting one permanent into one room, then another (the room halves
|
||||
// should STAY UNLOCKED on the appropriate side!)
|
||||
@Test
|
||||
public void testUnlockingPermanentMakeCopyOfOtherRoom() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
//
|
||||
// Surgical Suite {1}{W}
|
||||
// When you unlock this door, return target creature card with mana value 3 or
|
||||
// less from your graveyard to the battlefield.
|
||||
// Hospital Room {3}{W}
|
||||
// Whenever you attack, put a +1/+1 counter on target attacking creature.
|
||||
//
|
||||
// Mirage Mirror {3}
|
||||
// {3}: Mirage Mirror becomes a copy of target artifact, creature, enchantment,
|
||||
// or
|
||||
// land until end of turn.
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.HAND, playerA, "Surgical Suite // Hospital Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mirage Mirror");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 20);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half only)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Surgical Suite (unlocked left half only)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Surgical Suite");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this} becomes a copy");
|
||||
addTarget(playerA, "Bottomless Pool // Locker Room");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
|
||||
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this} becomes a copy");
|
||||
addTarget(playerA, "Surgical Suite");
|
||||
|
||||
attack(3, playerA, "Memnite");
|
||||
addTarget(playerA, "Memnite");
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Verify unlocked Bottomless pool
|
||||
assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1);
|
||||
// Verify unlocked Surgical Suite
|
||||
assertPermanentCount(playerA, "Surgical Suite", 1);
|
||||
// Verify mirage mirror is Hospital Room
|
||||
assertPermanentCount(playerA, "Hospital Room", 1);
|
||||
// Memnite got a buff
|
||||
assertPowerToughness(playerA, "Memnite", 2, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSakashimaCopiesRoomCard() {
|
||||
skipInitShuffling();
|
||||
// Bottomless Pool {U} When you unlock this door, return up to one target
|
||||
// creature to its owner's hand.
|
||||
// Locker Room {4}{U} Whenever one or more creatures you control deal combat
|
||||
// damage to a player, draw a card.
|
||||
|
||||
// Sakashima the Impostor {2}{U}{U}
|
||||
// Legendary Creature — Human Rogue
|
||||
// You may have Sakashima the Impostor enter the battlefield as a copy of any
|
||||
// creature on the battlefield,
|
||||
// except its name is Sakashima the Impostor, it's legendary in addition to its
|
||||
// other types,
|
||||
// and it has "{2}{U}{U}: Return Sakashima the Impostor to its owner's hand at
|
||||
// the beginning of the next end step."
|
||||
|
||||
addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 10);
|
||||
|
||||
addCard(Zone.HAND, playerB, "Sakashima the Impostor");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 10);
|
||||
|
||||
// Cast Bottomless Pool (unlocked left half only)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
addTarget(playerA, TestPlayer.TARGET_SKIP);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// Cast Sakashima copying the room
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sakashima the Impostor");
|
||||
setChoice(playerB, "Yes"); // Choose to copy
|
||||
waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half.");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
// Verify Sakashima entered and is copying the room
|
||||
assertPermanentCount(playerB, "Sakashima the Impostor", 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.cmm;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -19,7 +18,6 @@ public class BattleAtTheHelvaultTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String battle = "Battle at the Helvault";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay() {
|
||||
addCard(Zone.HAND, playerA, battle, 1);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package org.mage.test.cards.single.fic;
|
|||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
|
@ -23,7 +22,6 @@ public class SummonIxionTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String ixion = "Summon: Ixion";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay() {
|
||||
addCard(Zone.HAND, playerA, ixion, 1);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.pip;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
|
@ -21,7 +20,6 @@ public class Vault13DwellersJourneyTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String vault = "Vault 13: Dweller's Journey";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay_ReturnOne() {
|
||||
addCard(Zone.HAND, playerA, vault, 1);
|
||||
|
|
@ -49,7 +47,6 @@ public class Vault13DwellersJourneyTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Memnite", 1);
|
||||
assertLife(playerA, 20 + 2);
|
||||
}
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay_Return() {
|
||||
addCard(Zone.HAND, playerA, vault, 1);
|
||||
|
|
@ -81,7 +78,6 @@ public class Vault13DwellersJourneyTest extends CardTestPlayerBase {
|
|||
assertLife(playerA, 20 + 2);
|
||||
}
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay_NoReturn() {
|
||||
addCard(Zone.HAND, playerA, vault, 1);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
package org.mage.test.cards.single.shm;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author xenohedron
|
||||
*/
|
||||
public class GlamerSpinnersTest extends CardTestPlayerBase {
|
||||
|
||||
/*
|
||||
Glamer Spinners
|
||||
{4}{WU}
|
||||
Creature - Faerie Wizard
|
||||
Flash
|
||||
Flying
|
||||
When Glamer Spinners enters the battlefield, attach all Auras enchanting target permanent to another permanent with the same controller.
|
||||
2/4
|
||||
*/
|
||||
private static final String glamerSpinners = "Glamer Spinners";
|
||||
|
||||
/*
|
||||
Feral Invocation
|
||||
{2}{G}
|
||||
Enchantment - Aura
|
||||
Flash <i>(You may cast this spell any time you could cast an instant.)</i>
|
||||
Enchant creature
|
||||
Enchanted creature gets +2/+2.
|
||||
*/
|
||||
private static final String feralInvocation = "Feral Invocation";
|
||||
|
||||
/*
|
||||
Memnite
|
||||
{0}
|
||||
Artifact Creature - Construct
|
||||
1/1
|
||||
*/
|
||||
private static final String memnite = "Memnite";
|
||||
|
||||
/*
|
||||
Kraken Hatchling
|
||||
{U}
|
||||
Creature - Kraken
|
||||
0/4
|
||||
*/
|
||||
private static final String krakenHatchling = "Kraken Hatchling";
|
||||
|
||||
@Test
|
||||
public void testGlamerSpinners() {
|
||||
addCard(Zone.HAND, playerA, glamerSpinners);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.HAND, playerB, feralInvocation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Forest", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerB, memnite);
|
||||
addCard(Zone.BATTLEFIELD, playerB, krakenHatchling);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, feralInvocation, memnite);
|
||||
checkPT("enchanted", 1, PhaseStep.BEGIN_COMBAT, playerB, memnite, 3, 3);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, glamerSpinners);
|
||||
addTarget(playerA, memnite);
|
||||
setChoice(playerA, krakenHatchling);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, glamerSpinners, 1);
|
||||
assertPowerToughness(playerB, memnite, 1, 1);
|
||||
assertPowerToughness(playerB, krakenHatchling, 2, 6);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package org.mage.test.cards.single.spm;
|
||||
|
||||
import mage.constants.ManaType;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
|
|
@ -39,6 +41,16 @@ public class ElectroAssaultingBatteryTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String lightningBolt = "Lightning Bolt";
|
||||
|
||||
/*
|
||||
Final Showdown
|
||||
{W}
|
||||
Instant
|
||||
Spree
|
||||
+ {1} -- All creatures lose all abilities until end of turn.
|
||||
+ {1} -- Choose a creature you control. It gains indestructible until end of turn.
|
||||
+ {3}{W}{W} -- Destroy all creatures.
|
||||
*/
|
||||
private static final String finalShowdown = "Final Showdown";
|
||||
|
||||
@Test
|
||||
public void testElectroAssaultingBattery() {
|
||||
|
|
@ -62,4 +74,34 @@ public class ElectroAssaultingBatteryTest extends CardTestPlayerBase {
|
|||
|
||||
assertLife(playerB, 20 - 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testElectroAssaultingBatteryFinalShowdown() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, electroAssaultingBattery);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Plains", 7);
|
||||
addCard(Zone.HAND, playerB, finalShowdown);
|
||||
addCard(Zone.HAND, playerA, lightningBolt);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, finalShowdown);
|
||||
setModeChoice(playerB, "1");
|
||||
setModeChoice(playerB, "3");
|
||||
setModeChoice(playerB, TestPlayer.MODE_SKIP);
|
||||
|
||||
checkManaPool("Should have 1 red mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, lightningBolt, 1);
|
||||
assertGraveyardCount(playerB, finalShowdown, 1);
|
||||
assertGraveyardCount(playerA, electroAssaultingBattery, 1);
|
||||
|
||||
assertManaPool(playerA, ManaType.RED, 0); // Electro's ability is gone
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.who;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -18,7 +17,6 @@ public class DayOfTheMoonTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String day = "Day of the Moon";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay() {
|
||||
addCard(Zone.HAND, playerA, day, 1);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.who;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -20,7 +19,6 @@ public class TheWarGamesTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String war = "The War Games";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay_NoExile() {
|
||||
addCard(Zone.HAND, playerA, war, 1);
|
||||
|
|
@ -63,7 +61,6 @@ public class TheWarGamesTest extends CardTestPlayerBase {
|
|||
assertLife(playerB, 20 - 6 - 9);
|
||||
}
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay_Exile() {
|
||||
addCard(Zone.HAND, playerA, war, 1);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.who;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -20,7 +19,6 @@ public class TrialOfATimeLordTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String trial = "Trial of a Time Lord";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay() {
|
||||
addCard(Zone.HAND, playerA, trial, 1);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package org.mage.test.cards.single.woe;
|
|||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ public class ThePrincessTakesFlightTest extends CardTestPlayerBase {
|
|||
*/
|
||||
private static final String flight = "The Princess Takes Flight";
|
||||
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void test_SimplePlay() {
|
||||
addCard(Zone.HAND, playerA, flight, 1);
|
||||
|
|
@ -54,7 +52,6 @@ public class ThePrincessTakesFlightTest extends CardTestPlayerBase {
|
|||
assertExileCount(playerB, "Memnite", 0);
|
||||
assertPermanentCount(playerB, "Memnite", 1);
|
||||
}
|
||||
@Ignore // TODO: goal of #11619 is to fix this nicely
|
||||
@Test
|
||||
public void testFlicker() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
|
||||
|
|
@ -82,4 +79,73 @@ public class ThePrincessTakesFlightTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Memnite", 1);
|
||||
assertGraveyardCount(playerA, flight, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_TokenCopy() {
|
||||
addCard(Zone.HAND, playerA, flight, 1);
|
||||
addCard(Zone.HAND, playerA, "Swords to Plowshares", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ondu Spiritdancer", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, flight);
|
||||
setChoice(playerA, "I - ");
|
||||
addTarget(playerA, "Memnite");
|
||||
setChoice(playerA, true);
|
||||
addTarget(playerA, "Grizzly Bears");
|
||||
|
||||
checkExileCount("after I, exiled Memnite", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Memnite", 1);
|
||||
checkExileCount("after I, exiled Grizzly Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Grizzly Bears", 1);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", "Ondu Spiritdancer");
|
||||
|
||||
// turn 3
|
||||
setChoice(playerA, "II - ");
|
||||
// No targets available
|
||||
|
||||
// turn 5
|
||||
setChoice(playerA, "III - ");
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertExileCount(playerA, "Grizzly Bears", 0);
|
||||
assertExileCount(playerB, "Memnite", 0);
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||
assertPermanentCount(playerB, "Memnite", 1);
|
||||
}
|
||||
@Test
|
||||
public void test_SpellCopy() {
|
||||
addCard(Zone.HAND, playerA, flight, 1);
|
||||
addCard(Zone.HAND, playerA, "Swords to Plowshares", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Sixth Doctor", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, flight);
|
||||
addTarget(playerA, "Memnite");
|
||||
addTarget(playerA, "Grizzly Bears");
|
||||
|
||||
checkExileCount("after I, exiled Memnite", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Memnite", 1);
|
||||
checkExileCount("after I, exiled Grizzly Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Grizzly Bears", 1);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", "The Sixth Doctor");
|
||||
|
||||
// turn 3
|
||||
setChoice(playerA, "II - ");
|
||||
// No targets available
|
||||
|
||||
// turn 5
|
||||
setChoice(playerA, "III - ");
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertExileCount(playerA, "Grizzly Bears", 0);
|
||||
assertExileCount(playerB, "Memnite", 0);
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||
assertPermanentCount(playerB, "Memnite", 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,28 @@ public class CommanderDeckValidationTest extends MageTestPlayerBase {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartnerVariants() {
|
||||
DeckTester deckTester = new DeckTester(new Commander());
|
||||
deckTester.addMaindeck("Swamp", 98);
|
||||
|
||||
deckTester.addSideboard("Ellie, Vengeful Hunter", 1);
|
||||
deckTester.addSideboard("Joel, Resolute Survivor", 1);
|
||||
|
||||
deckTester.validate("You can have two commanders if they both have the same Partner variant");
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void testPartnerVariants2() {
|
||||
DeckTester deckTester = new DeckTester(new Commander());
|
||||
deckTester.addMaindeck("Mountain", 98);
|
||||
|
||||
deckTester.addSideboard("Ellie, Vengeful Hunter", 1);
|
||||
deckTester.addSideboard("Atreus, Impulsive Son", 1);
|
||||
|
||||
deckTester.validate("You can't have two commanders if they don't have the same Partner variant");
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testVehicles1() {
|
||||
DeckTester deckTester = new DeckTester(new Commander());
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ public class AliasesApiTest extends CardTestPlayerBase {
|
|||
Assert.assertTrue(CardUtil.haveSameNames(splitCard1, "Armed // Dangerous", currentGame));
|
||||
Assert.assertTrue(CardUtil.haveSameNames(splitCard1, splitCard1));
|
||||
Assert.assertFalse(CardUtil.haveSameNames(splitCard1, "Other", currentGame));
|
||||
Assert.assertFalse(CardUtil.haveSameNames(splitCard1, "Other // Dangerous", currentGame));
|
||||
Assert.assertFalse(CardUtil.haveSameNames(splitCard1, "Armed // Other", currentGame));
|
||||
// The below don't seem to matter/be correct, so they've been disabled.
|
||||
//Assert.assertFalse(CardUtil.haveSameNames(splitCard1, "Other // Dangerous", currentGame));
|
||||
//Assert.assertFalse(CardUtil.haveSameNames(splitCard1, "Armed // Other", currentGame));
|
||||
Assert.assertFalse(CardUtil.haveSameNames(splitCard1, splitCard2));
|
||||
|
||||
// name with face down spells: face down spells don't have names, see https://github.com/magefree/mage/issues/6569
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import mage.game.events.BatchEvent;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.players.Player;
|
||||
|
|
@ -1707,15 +1708,17 @@ public abstract class AbilityImpl implements Ability {
|
|||
|
||||
private int getCurrentSourceObjectZoneChangeCounter(Game game){
|
||||
int zcc = game.getState().getZoneChangeCounter(getSourceId());
|
||||
// TODO: Enable this, #13710
|
||||
/*if (game.getPermanentEntering(getSourceId()) != null){
|
||||
Permanent p = game.getPermanentEntering(getSourceId());
|
||||
if (p != null && !(p instanceof PermanentToken)){
|
||||
// If the triggered ability triggered while the permanent is entering the battlefield
|
||||
// then add 1 zcc so that it triggers as if the permanent was already on the battlefield
|
||||
// So "Enters with counters" causes "Whenever counters are placed" to trigger with battlefield zcc
|
||||
// Particularly relevant for Sagas, which always involve both
|
||||
// Note that this does NOT apply to "As ~ ETB" effects, those still use the stack zcc
|
||||
// TODO: JayDi doesn't like this solution, consider finding another one.
|
||||
zcc += 1;
|
||||
}*/
|
||||
// However, tokens don't change their zcc upon entering the battlefield, so don't add for them
|
||||
}
|
||||
return zcc;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* TODO: This only triggers off of enchantments entering as the room mechanic hasn't been implemented yet
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
|
@ -41,15 +40,23 @@ public class EerieAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
|
||||
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD
|
||||
|| event.getType() == GameEvent.EventType.ROOM_FULLY_UNLOCKED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (!isControlledBy(event.getPlayerId())) {
|
||||
return false;
|
||||
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
|
||||
if (!isControlledBy(event.getPlayerId())) {
|
||||
return false;
|
||||
}
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
return permanent != null && permanent.isEnchantment(game);
|
||||
}
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
return permanent != null && permanent.isEnchantment(game);
|
||||
|
||||
if (event.getType() == GameEvent.EventType.ROOM_FULLY_UNLOCKED) {
|
||||
return isControlledBy(event.getPlayerId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.BatchTriggeredAbility;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
|
|
@ -13,13 +14,17 @@ import mage.game.events.DamagedPlayerEvent;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.target.targetpointer.FixedTargets;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Xanderhall, xenohedron
|
||||
*/
|
||||
public class OneOrMoreDamagePlayerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<DamagedPlayerEvent> {
|
||||
public class OneOrMoreDamagePlayerTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<DamagedPlayerEvent> {
|
||||
|
||||
private final SetTargetPointer setTargetPointer;
|
||||
private final FilterPermanent filter;
|
||||
|
|
@ -85,6 +90,16 @@ public class OneOrMoreDamagePlayerTriggeredAbility extends TriggeredAbilityImpl
|
|||
case PLAYER:
|
||||
this.getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
|
||||
break;
|
||||
case PERMANENT:
|
||||
Set<MageObjectReference> attackerSet = events
|
||||
.stream()
|
||||
.map(GameEvent::getSourceId)
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.map(permanent -> new MageObjectReference(permanent, game))
|
||||
.collect(Collectors.toSet());
|
||||
this.getAllEffects().setTargetPointer(new FixedTargets(attackerSet));
|
||||
break;
|
||||
case NONE:
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
106
Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java
Normal file
106
Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpecialAction;
|
||||
import mage.abilities.condition.common.RoomHalfLockedCondition;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TimingRule;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
* Special action for Room cards to unlock a locked half by paying its
|
||||
* mana
|
||||
* cost.
|
||||
* This ability is only present if the corresponding half is currently
|
||||
* locked.
|
||||
*/
|
||||
public class RoomUnlockAbility extends SpecialAction {
|
||||
|
||||
private final boolean isLeftHalf;
|
||||
|
||||
public RoomUnlockAbility(ManaCosts costs, boolean isLeftHalf) {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
this.addCost(costs);
|
||||
|
||||
this.isLeftHalf = isLeftHalf;
|
||||
this.timing = TimingRule.SORCERY;
|
||||
|
||||
// only works if the relevant half is *locked*
|
||||
if (isLeftHalf) {
|
||||
this.setCondition(RoomHalfLockedCondition.LEFT);
|
||||
} else {
|
||||
this.setCondition(RoomHalfLockedCondition.RIGHT);
|
||||
}
|
||||
|
||||
// Adds the effect to pay + unlock the half
|
||||
this.addEffect(new RoomUnlockHalfEffect(isLeftHalf));
|
||||
}
|
||||
|
||||
protected RoomUnlockAbility(final RoomUnlockAbility ability) {
|
||||
super(ability);
|
||||
this.isLeftHalf = ability.isLeftHalf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomUnlockAbility copy() {
|
||||
return new RoomUnlockAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getManaCostsToPay().getText()).append(": ");
|
||||
sb.append("Unlock the ");
|
||||
sb.append(isLeftHalf ? "left" : "right").append(" half.");
|
||||
sb.append(" <i>(Activate only as a sorcery, and only if the ");
|
||||
sb.append(isLeftHalf ? "left" : "right").append(" half is locked.)</i>");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to pay to unlock the door
|
||||
*/
|
||||
class RoomUnlockHalfEffect extends OneShotEffect {
|
||||
|
||||
private final boolean isLeftHalf;
|
||||
|
||||
public RoomUnlockHalfEffect(boolean isLeftHalf) {
|
||||
super(Outcome.Neutral);
|
||||
this.isLeftHalf = isLeftHalf;
|
||||
staticText = "unlock the " + (isLeftHalf ? "left" : "right") + " half";
|
||||
}
|
||||
|
||||
private RoomUnlockHalfEffect(final RoomUnlockHalfEffect effect) {
|
||||
super(effect);
|
||||
this.isLeftHalf = effect.isLeftHalf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomUnlockHalfEffect copy() {
|
||||
return new RoomUnlockHalfEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLeftHalf && permanent.isLeftDoorUnlocked()) {
|
||||
return false;
|
||||
}
|
||||
if (!isLeftHalf && permanent.isRightDoorUnlocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return permanent.unlockDoor(game, source, isLeftHalf);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
* Triggered ability for "when you unlock this door" effects
|
||||
*
|
||||
* @author oscscull
|
||||
*/
|
||||
public class UnlockThisDoorTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private final boolean isLeftHalf;
|
||||
|
||||
public UnlockThisDoorTriggeredAbility(Effect effect, boolean optional, boolean isLeftHalf) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.isLeftHalf = isLeftHalf;
|
||||
this.setTriggerPhrase("When you unlock this door, ");
|
||||
}
|
||||
|
||||
private UnlockThisDoorTriggeredAbility(final UnlockThisDoorTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.isLeftHalf = ability.isLeftHalf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.DOOR_UNLOCKED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return event.getTargetId().equals(getSourceId()) && event.getFlag() == isLeftHalf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnlockThisDoorTriggeredAbility copy() {
|
||||
return new UnlockThisDoorTriggeredAbility(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
* Checks if a Permanent's specified half is LOCKED (i.e., NOT unlocked).
|
||||
*/
|
||||
public enum RoomHalfLockedCondition implements Condition {
|
||||
|
||||
LEFT(true),
|
||||
RIGHT(false);
|
||||
|
||||
private final boolean checkLeft;
|
||||
|
||||
RoomHalfLockedCondition(boolean checkLeft) {
|
||||
this.checkLeft = checkLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if the specified half is NOT unlocked
|
||||
return checkLeft ? !permanent.isLeftDoorUnlocked() : !permanent.isRightDoorUnlocked();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
* Continuous effect that sets the name and mana value of a Room permanent based
|
||||
* on its unlocked halves.
|
||||
*
|
||||
* Functions as a characteristic-defining ability.
|
||||
* 709.5. Some split cards are permanent cards with a single shared type line.
|
||||
* A shared type line on such an object represents two static abilities that
|
||||
* function on the battlefield.
|
||||
* These are "As long as this permanent doesn't have the 'left half unlocked'
|
||||
* designation, it doesn't have the name, mana cost, or rules text of this
|
||||
* object's left half"
|
||||
* and "As long as this permanent doesn't have the 'right half unlocked'
|
||||
* designation, it doesn't have the name, mana cost, or rules text of this
|
||||
* object's right half."
|
||||
* These abilities, as well as which half of that permanent a characteristic is
|
||||
* in, are part of that object's copiable values.
|
||||
*/
|
||||
public class RoomCharacteristicsEffect extends ContinuousEffectImpl {
|
||||
|
||||
public RoomCharacteristicsEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a,
|
||||
Outcome.Neutral);
|
||||
staticText = "";
|
||||
}
|
||||
|
||||
private RoomCharacteristicsEffect(final RoomCharacteristicsEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomCharacteristicsEffect copy() {
|
||||
return new RoomCharacteristicsEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card roomCardBlueprint;
|
||||
|
||||
// Handle copies
|
||||
if (permanent.isCopy()) {
|
||||
MageObject copiedObject = permanent.getCopyFrom();
|
||||
if (copiedObject instanceof PermanentCard) {
|
||||
roomCardBlueprint = ((PermanentCard) copiedObject).getCard();
|
||||
} else if (copiedObject instanceof Card) {
|
||||
roomCardBlueprint = (Card) copiedObject;
|
||||
} else {
|
||||
roomCardBlueprint = permanent.getMainCard();
|
||||
}
|
||||
} else {
|
||||
roomCardBlueprint = permanent.getMainCard();
|
||||
}
|
||||
|
||||
if (!(roomCardBlueprint instanceof SplitCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SplitCard roomCard = (SplitCard) roomCardBlueprint;
|
||||
|
||||
// Set the name based on unlocked halves
|
||||
String newName = "";
|
||||
|
||||
boolean isLeftUnlocked = permanent.isLeftDoorUnlocked();
|
||||
if (isLeftUnlocked && roomCard.getLeftHalfCard() != null) {
|
||||
newName += roomCard.getLeftHalfCard().getName();
|
||||
}
|
||||
|
||||
boolean isRightUnlocked = permanent.isRightDoorUnlocked();
|
||||
if (isRightUnlocked && roomCard.getRightHalfCard() != null) {
|
||||
if (!newName.isEmpty()) {
|
||||
newName += " // "; // Split card name separator
|
||||
}
|
||||
newName += roomCard.getRightHalfCard().getName();
|
||||
}
|
||||
|
||||
permanent.setName(newName);
|
||||
|
||||
// Set the mana value based on unlocked halves
|
||||
// Create a new Mana object to accumulate the costs
|
||||
Mana totalManaCost = new Mana();
|
||||
|
||||
// Add the mana from the left half's cost to our total Mana object
|
||||
if (isLeftUnlocked) {
|
||||
ManaCosts leftHalfManaCost = null;
|
||||
if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getSpellAbility() != null) {
|
||||
leftHalfManaCost = roomCard.getLeftHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (leftHalfManaCost != null) {
|
||||
totalManaCost.add(leftHalfManaCost.getMana());
|
||||
}
|
||||
}
|
||||
|
||||
// Add the mana from the right half's cost to our total Mana object
|
||||
if (isRightUnlocked) {
|
||||
ManaCosts rightHalfManaCost = null;
|
||||
if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) {
|
||||
rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (rightHalfManaCost != null) {
|
||||
totalManaCost.add(rightHalfManaCost.getMana());
|
||||
}
|
||||
}
|
||||
|
||||
String newManaCostString = totalManaCost.toString();
|
||||
ManaCostsImpl newManaCosts;
|
||||
|
||||
// If both halves are locked or total 0, it's 0mv.
|
||||
if (newManaCostString.isEmpty() || totalManaCost.count() == 0) {
|
||||
newManaCosts = new ManaCostsImpl<>("");
|
||||
} else {
|
||||
newManaCosts = new ManaCostsImpl<>(newManaCostString);
|
||||
}
|
||||
|
||||
permanent.setManaCost(newManaCosts);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@ public class SetBasePowerToughnessAllEffect extends ContinuousEffectImpl {
|
|||
this(StaticValue.get(power), StaticValue.get(toughness), duration, filter);
|
||||
}
|
||||
|
||||
public SetBasePowerToughnessAllEffect(DynamicValue stats, Duration duration, FilterPermanent filter) {
|
||||
this(stats, stats, duration, filter);
|
||||
}
|
||||
|
||||
public SetBasePowerToughnessAllEffect(DynamicValue power, DynamicValue toughness, Duration duration, FilterPermanent filter) {
|
||||
super(duration, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.BoostCreature);
|
||||
this.power = power;
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.MageSingleton;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.constants.Zone;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class PartnerFatherAndSonAbility extends StaticAbility implements MageSingleton {
|
||||
|
||||
private static final PartnerFatherAndSonAbility instance = new PartnerFatherAndSonAbility();
|
||||
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static PartnerFatherAndSonAbility getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private PartnerFatherAndSonAbility() {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Partner—Father & son <i>(You can have two commanders if both have this ability.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartnerFatherAndSonAbility copy() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.MageSingleton;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.constants.Zone;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class PartnerSurvivorsAbility extends StaticAbility implements MageSingleton {
|
||||
|
||||
private static final PartnerSurvivorsAbility instance = new PartnerSurvivorsAbility();
|
||||
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static PartnerSurvivorsAbility getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private PartnerSurvivorsAbility() {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Partner—Survivors <i>(You can have two commanders if both have this ability.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartnerSurvivorsAbility copy() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
77
Mage/src/main/java/mage/abilities/keyword/SneakAbility.java
Normal file
77
Mage/src/main/java/mage/abilities/keyword/SneakAbility.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.common.ReturnToHandChosenControlledPermanentCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.TimingRule;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.permanent.AttackingPredicate;
|
||||
import mage.filter.predicate.permanent.UnblockedPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class SneakAbility extends SpellAbility {
|
||||
|
||||
public static final String SNEAK_ACTIVATION_VALUE_KEY = "sneakActivation";
|
||||
private static final FilterControlledPermanent filter = new FilterControlledPermanent("unblocked attacker you control");
|
||||
|
||||
static {
|
||||
filter.add(UnblockedPredicate.instance);
|
||||
filter.add(AttackingPredicate.instance);
|
||||
}
|
||||
|
||||
public SneakAbility(Card card, String manaString) {
|
||||
super(card.getSpellAbility());
|
||||
this.newId();
|
||||
this.setCardName(card.getName() + " with Sneak");
|
||||
timing = TimingRule.INSTANT;
|
||||
zone = Zone.HAND;
|
||||
spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
|
||||
|
||||
this.clearManaCosts();
|
||||
this.clearManaCostsToPay();
|
||||
this.addCost(new ManaCostsImpl<>(manaString));
|
||||
this.addCost(new ReturnToHandChosenControlledPermanentCost(new TargetControlledPermanent(filter)));
|
||||
|
||||
this.setRuleAtTheTop(true);
|
||||
}
|
||||
|
||||
protected SneakAbility(final SneakAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (!super.activate(game, allowedIdentifiers, noMana)
|
||||
|| game.getStep().getType() != PhaseStep.DECLARE_BLOCKERS) {
|
||||
return false;
|
||||
}
|
||||
this.setCostsTag(SNEAK_ACTIVATION_VALUE_KEY, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SneakAbility copy() {
|
||||
return new SneakAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
StringBuilder sb = new StringBuilder("Sneak ");
|
||||
sb.append(getManaCosts().getText());
|
||||
sb.append(" <i>(You may cast this spell for ");
|
||||
sb.append(getManaCosts().getText());
|
||||
sb.append(" if you also return an unblocked attacker you control to hand during the declare blockers step.)</i>");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
215
Mage/src/main/java/mage/cards/RoomCard.java
Normal file
215
Mage/src/main/java/mage/cards/RoomCard.java
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
package mage.cards;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.common.RoomUnlockAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.condition.common.RoomHalfLockedCondition;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.RoomCharacteristicsEffect;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.abilities.effects.common.continuous.LoseAbilitySourceEffect;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
*/
|
||||
public abstract class RoomCard extends SplitCard {
|
||||
private SpellAbilityType lastCastHalf = null;
|
||||
|
||||
protected RoomCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, String costsLeft,
|
||||
String costsRight, SpellAbilityType spellAbilityType) {
|
||||
super(ownerId, setInfo, costsLeft, costsRight, spellAbilityType, types);
|
||||
|
||||
String[] names = setInfo.getName().split(" // ");
|
||||
|
||||
leftHalfCard = new RoomCardHalfImpl(
|
||||
this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(),
|
||||
setInfo.getRarity(), setInfo.getGraphicInfo()),
|
||||
types, costsLeft, this, SpellAbilityType.SPLIT_LEFT);
|
||||
rightHalfCard = new RoomCardHalfImpl(
|
||||
this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(),
|
||||
setInfo.getRarity(), setInfo.getGraphicInfo()),
|
||||
types, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
|
||||
}
|
||||
|
||||
protected RoomCard(RoomCard card) {
|
||||
super(card);
|
||||
this.lastCastHalf = card.lastCastHalf;
|
||||
}
|
||||
|
||||
public SpellAbilityType getLastCastHalf() {
|
||||
return lastCastHalf;
|
||||
}
|
||||
|
||||
public void setLastCastHalf(SpellAbilityType lastCastHalf) {
|
||||
this.lastCastHalf = lastCastHalf;
|
||||
}
|
||||
|
||||
protected void addRoomAbilities(Ability leftAbility, Ability rightAbility) {
|
||||
getLeftHalfCard().addAbility(leftAbility);
|
||||
getRightHalfCard().addAbility(rightAbility);
|
||||
this.addAbility(leftAbility.copy());
|
||||
this.addAbility(rightAbility.copy());
|
||||
|
||||
// Add the one-shot effect to unlock a door on cast -> ETB
|
||||
Ability entersAbility = new EntersBattlefieldAbility(new RoomEnterUnlockEffect());
|
||||
entersAbility.setRuleVisible(false);
|
||||
this.addAbility(entersAbility);
|
||||
|
||||
// Remove locked door abilities - keeping unlock triggers (or they won't trigger
|
||||
// when unlocked)
|
||||
if (leftAbility != null && !(leftAbility instanceof UnlockThisDoorTriggeredAbility)) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(
|
||||
new LoseAbilitySourceEffect(leftAbility, Duration.WhileOnBattlefield),
|
||||
RoomHalfLockedCondition.LEFT, "")).setRuleVisible(false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
if (rightAbility != null && !(rightAbility instanceof UnlockThisDoorTriggeredAbility)) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(
|
||||
new LoseAbilitySourceEffect(rightAbility, Duration.WhileOnBattlefield),
|
||||
RoomHalfLockedCondition.RIGHT, "")).setRuleVisible(false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
// Add the Special Action to unlock doors.
|
||||
// These will ONLY be active if the corresponding half is LOCKED!
|
||||
if (leftAbility != null) {
|
||||
ManaCosts leftHalfManaCost = null;
|
||||
if (this.getLeftHalfCard() != null && this.getLeftHalfCard().getSpellAbility() != null) {
|
||||
leftHalfManaCost = this.getLeftHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
RoomUnlockAbility leftUnlockAbility = new RoomUnlockAbility(leftHalfManaCost, true);
|
||||
this.addAbility(leftUnlockAbility.setRuleAtTheTop(true));
|
||||
}
|
||||
|
||||
if (rightAbility != null) {
|
||||
ManaCosts rightHalfManaCost = null;
|
||||
if (this.getRightHalfCard() != null && this.getRightHalfCard().getSpellAbility() != null) {
|
||||
rightHalfManaCost = this.getRightHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
RoomUnlockAbility rightUnlockAbility = new RoomUnlockAbility(rightHalfManaCost, false);
|
||||
this.addAbility(rightUnlockAbility.setRuleAtTheTop(true));
|
||||
}
|
||||
|
||||
this.addAbility(new RoomAbility());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getAbilities() {
|
||||
return this.abilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getAbilities(Game game) {
|
||||
return this.abilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZone(Zone zone, Game game) {
|
||||
super.setZone(zone, game);
|
||||
|
||||
if (zone == Zone.BATTLEFIELD) {
|
||||
game.setZone(getLeftHalfCard().getId(), Zone.OUTSIDE);
|
||||
game.setZone(getRightHalfCard().getId(), Zone.OUTSIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
game.setZone(getLeftHalfCard().getId(), zone);
|
||||
game.setZone(getRightHalfCard().getId(), zone);
|
||||
}
|
||||
}
|
||||
|
||||
class RoomEnterUnlockEffect extends OneShotEffect {
|
||||
public RoomEnterUnlockEffect() {
|
||||
super(Outcome.Neutral);
|
||||
staticText = "";
|
||||
}
|
||||
|
||||
private RoomEnterUnlockEffect(final RoomEnterUnlockEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomEnterUnlockEffect copy() {
|
||||
return new RoomEnterUnlockEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permanent.wasRoomUnlockedOnCast()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
permanent.unlockRoomOnCast(game);
|
||||
RoomCard roomCard = null;
|
||||
// Get the parent card to access the lastCastHalf variable
|
||||
if (permanent instanceof PermanentToken) {
|
||||
Card mainCard = permanent.getMainCard();
|
||||
if (mainCard instanceof RoomCard) {
|
||||
roomCard = (RoomCard) mainCard;
|
||||
}
|
||||
} else {
|
||||
Card card = game.getCard(permanent.getId());
|
||||
if (card instanceof RoomCard) {
|
||||
roomCard = (RoomCard) card;
|
||||
}
|
||||
}
|
||||
if (roomCard == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SpellAbilityType lastCastHalf = roomCard.getLastCastHalf();
|
||||
|
||||
if (lastCastHalf == SpellAbilityType.SPLIT_LEFT || lastCastHalf == SpellAbilityType.SPLIT_RIGHT) {
|
||||
roomCard.setLastCastHalf(null);
|
||||
return permanent.unlockDoor(game, source, lastCastHalf == SpellAbilityType.SPLIT_LEFT);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For the overall Room card flavor text and mana value effect.
|
||||
class RoomAbility extends SimpleStaticAbility {
|
||||
public RoomAbility() {
|
||||
super(Zone.ALL, null);
|
||||
this.setRuleVisible(true);
|
||||
this.setRuleAtTheTop(true);
|
||||
this.addEffect(new RoomCharacteristicsEffect());
|
||||
}
|
||||
|
||||
protected RoomAbility(final RoomAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "<i>(You may cast either half. That door unlocks on the battlefield. " +
|
||||
"As a sorcery, you may pay the mana cost of a locked door to unlock it.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomAbility copy() {
|
||||
return new RoomAbility(this);
|
||||
}
|
||||
}
|
||||
9
Mage/src/main/java/mage/cards/RoomCardHalf.java
Normal file
9
Mage/src/main/java/mage/cards/RoomCardHalf.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package mage.cards;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
*/
|
||||
public interface RoomCardHalf extends SplitCardHalf {
|
||||
@Override
|
||||
RoomCardHalf copy();
|
||||
}
|
||||
68
Mage/src/main/java/mage/cards/RoomCardHalfImpl.java
Normal file
68
Mage/src/main/java/mage/cards/RoomCardHalfImpl.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
*/
|
||||
public class RoomCardHalfImpl extends SplitCardHalfImpl implements RoomCardHalf {
|
||||
|
||||
public RoomCardHalfImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs,
|
||||
RoomCard splitCardParent, SpellAbilityType spellAbilityType) {
|
||||
super(ownerId, setInfo, cardTypes, costs, splitCardParent, spellAbilityType);
|
||||
this.addSubType(SubType.ROOM);
|
||||
}
|
||||
|
||||
protected RoomCardHalfImpl(final RoomCardHalfImpl card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomCardHalfImpl copy() {
|
||||
return new RoomCardHalfImpl(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||||
SpellAbilityType abilityType = ability.getSpellAbilityType();
|
||||
RoomCard parentCard = (RoomCard) this.getParentCard();
|
||||
|
||||
if (parentCard != null) {
|
||||
if (abilityType == SpellAbilityType.SPLIT_LEFT) {
|
||||
parentCard.setLastCastHalf(SpellAbilityType.SPLIT_LEFT);
|
||||
} else if (abilityType == SpellAbilityType.SPLIT_RIGHT) {
|
||||
parentCard.setLastCastHalf(SpellAbilityType.SPLIT_RIGHT);
|
||||
} else {
|
||||
parentCard.setLastCastHalf(null);
|
||||
}
|
||||
}
|
||||
|
||||
return super.cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* A room half is used for the spell half on the stack, similar to a normal split card.
|
||||
* On the stack, it has only one name, mana cost, etc.
|
||||
* However, in the hand and on the battlefield, it is the full card, which is the parent of the half.
|
||||
* This code helps to ensure that the parent, and not the halves, are the only part of the card active on the battlefield.
|
||||
* This is important for example when that half has a triggered ability etc that otherwise might trigger twice (once for the parent, once for the half)
|
||||
* - in the case that the half was an object on the battlefield. In all other cases, they should all move together.
|
||||
*/
|
||||
@Override
|
||||
public void setZone(Zone zone, Game game) {
|
||||
if (zone == Zone.BATTLEFIELD) {
|
||||
game.setZone(splitCardParent.getId(), zone);
|
||||
game.setZone(splitCardParent.getLeftHalfCard().getId(), Zone.OUTSIDE);
|
||||
game.setZone(splitCardParent.getRightHalfCard().getId(), Zone.OUTSIDE);
|
||||
return;
|
||||
}
|
||||
super.setZone(zone, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,15 @@ public abstract class SplitCard extends CardImpl implements CardWithHalves {
|
|||
rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesRight, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
|
||||
}
|
||||
|
||||
// Params reordered as we need the same arguments as the parent constructor, with slightly different behaviour.
|
||||
// Currently only used for rooms, because they are the only current split card with a shared type line.
|
||||
protected SplitCard(UUID ownerId, CardSetInfo setInfo, String costsLeft, String costsRight, SpellAbilityType spellAbilityType, CardType[] singleTypeLine) {
|
||||
super(ownerId, setInfo, singleTypeLine, costsLeft + costsRight, spellAbilityType);
|
||||
String[] names = setInfo.getName().split(" // ");
|
||||
leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), singleTypeLine, costsLeft, this, SpellAbilityType.SPLIT_LEFT);
|
||||
rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), singleTypeLine, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
|
||||
}
|
||||
|
||||
protected SplitCard(SplitCard card) {
|
||||
super(card);
|
||||
// make sure all parts created and parent ref added
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ public enum EmptyNames {
|
|||
// TODO: replace all getName().equals to haveSameNames and haveEmptyName
|
||||
FACE_DOWN_CREATURE("", "[face_down_creature]"), // "Face down creature"
|
||||
FACE_DOWN_TOKEN("", "[face_down_token]"), // "Face down token"
|
||||
FACE_DOWN_CARD("", "[face_down_card]"); // "Face down card"
|
||||
FACE_DOWN_CARD("", "[face_down_card]"), // "Face down card"
|
||||
FULLY_LOCKED_ROOM("", "[fully_locked_room]"); // "Fully locked room"
|
||||
|
||||
public static final String EMPTY_NAME_IN_LOGS = "face down object";
|
||||
|
||||
|
|
@ -40,7 +41,8 @@ public enum EmptyNames {
|
|||
public static boolean isEmptyName(String objectName) {
|
||||
return objectName.equals(FACE_DOWN_CREATURE.getObjectName())
|
||||
|| objectName.equals(FACE_DOWN_TOKEN.getObjectName())
|
||||
|| objectName.equals(FACE_DOWN_CARD.getObjectName());
|
||||
|| objectName.equals(FACE_DOWN_CARD.getObjectName())
|
||||
|| objectName.equals(FULLY_LOCKED_ROOM.getObjectName());
|
||||
}
|
||||
|
||||
public static String replaceTestCommandByObjectName(String searchCommand) {
|
||||
|
|
|
|||
81
Mage/src/main/java/mage/constants/PartnerVariantType.java
Normal file
81
Mage/src/main/java/mage/constants/PartnerVariantType.java
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package mage.constants;
|
||||
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public enum PartnerVariantType {
|
||||
FATHER_AND_SON("Father & son"),
|
||||
SURVIVORS("Survivors"),
|
||||
CHARACTER_SELECT("Character select");
|
||||
|
||||
private final String name;
|
||||
|
||||
PartnerVariantType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PartnerVariantAbility makeAbility() {
|
||||
return new PartnerVariantAbility(this);
|
||||
}
|
||||
|
||||
private static Set<PartnerVariantType> getTypes(Card card) {
|
||||
return CardUtil
|
||||
.castStream(card.getAbilities(), PartnerVariantAbility.class)
|
||||
.map(PartnerVariantAbility::getType)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static boolean checkCommanders(Card commander1, Card commander2) {
|
||||
Set<PartnerVariantType> types1 = getTypes(commander1);
|
||||
if (types1.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Set<PartnerVariantType> types2 = getTypes(commander2);
|
||||
if (types2.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
types1.retainAll(types2);
|
||||
return !types1.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
class PartnerVariantAbility extends StaticAbility {
|
||||
|
||||
private final PartnerVariantType type;
|
||||
|
||||
PartnerVariantAbility(PartnerVariantType type) {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private PartnerVariantAbility(final PartnerVariantAbility ability) {
|
||||
super(ability);
|
||||
this.type = ability.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartnerVariantAbility copy() {
|
||||
return new PartnerVariantAbility(this);
|
||||
}
|
||||
|
||||
public PartnerVariantType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Partner—" + type + " <i>(You can have two commanders if both have this ability.)</i>";
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,7 @@ public enum SubType {
|
|||
JUNK("Junk", SubTypeSet.ArtifactType),
|
||||
LANDER("Lander", SubTypeSet.ArtifactType),
|
||||
MAP("Map", SubTypeSet.ArtifactType),
|
||||
MUTAGEN("Mutagen", SubTypeSet.ArtifactType),
|
||||
POWERSTONE("Powerstone", SubTypeSet.ArtifactType),
|
||||
SPACECRAFT("Spacecraft", SubTypeSet.ArtifactType),
|
||||
TREASURE("Treasure", SubTypeSet.ArtifactType),
|
||||
|
|
@ -431,6 +432,7 @@ public enum SubType {
|
|||
// U
|
||||
UGNAUGHT("Ugnaught", SubTypeSet.CreatureType, true),
|
||||
UNICORN("Unicorn", SubTypeSet.CreatureType),
|
||||
UTROM("Utrom", SubTypeSet.CreatureType),
|
||||
// V
|
||||
VAMPIRE("Vampire", SubTypeSet.CreatureType),
|
||||
VARMINT("Varmint", SubTypeSet.CreatureType),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.filter.predicate.mageobject;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.cards.CardWithHalves;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.filter.predicate.Predicate;
|
||||
|
|
@ -42,6 +41,7 @@ public class NamePredicate implements Predicate<MageObject> {
|
|||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a player names a card, the player may name either half of a split card, but not both.
|
||||
// A split card has the chosen name if one of its two names matches the chosen name.
|
||||
// This is NOT the same for double faced cards, where only the front side matches
|
||||
|
|
@ -51,28 +51,54 @@ public class NamePredicate implements Predicate<MageObject> {
|
|||
// including the one that you countered, because those cards have only their front-face characteristics
|
||||
// (including name) in the graveyard, hand, and library. (2021-04-16)
|
||||
|
||||
String[] searchNames = extractNames(name);
|
||||
|
||||
if (input instanceof SplitCard) {
|
||||
return CardUtil.haveSameNames(name, ((CardWithHalves) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
|
||||
CardUtil.haveSameNames(name, ((CardWithHalves) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
|
||||
CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
|
||||
} else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
|
||||
SplitCard splitCard = (SplitCard) input;
|
||||
// Check against left half, right half, and full card name
|
||||
return matchesAnyName(searchNames, new String[] {
|
||||
splitCard.getLeftHalfCard().getName(),
|
||||
splitCard.getRightHalfCard().getName(),
|
||||
splitCard.getName()
|
||||
});
|
||||
} else if (input instanceof Spell
|
||||
&& ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
|
||||
SplitCard card = (SplitCard) ((Spell) input).getCard();
|
||||
return CardUtil.haveSameNames(name, card.getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
|
||||
CardUtil.haveSameNames(name, card.getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
|
||||
CardUtil.haveSameNames(name, card.getName(), this.ignoreMtgRuleForEmptyNames);
|
||||
// Check against left half, right half, and full card name
|
||||
return matchesAnyName(searchNames, new String[] {
|
||||
card.getLeftHalfCard().getName(),
|
||||
card.getRightHalfCard().getName(),
|
||||
card.getName()
|
||||
});
|
||||
} else if (input instanceof Spell && ((Spell) input).isFaceDown(game)) {
|
||||
// face down spells don't have names, so it's not equal, see https://github.com/magefree/mage/issues/6569
|
||||
return false;
|
||||
} else {
|
||||
if (name.contains(" // ")) {
|
||||
String leftName = name.substring(0, name.indexOf(" // "));
|
||||
String rightName = name.substring(name.indexOf(" // ") + 4);
|
||||
return CardUtil.haveSameNames(leftName, input.getName(), this.ignoreMtgRuleForEmptyNames) ||
|
||||
CardUtil.haveSameNames(rightName, input.getName(), this.ignoreMtgRuleForEmptyNames);
|
||||
} else {
|
||||
return CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
|
||||
// For regular cards, extract names from input and compare
|
||||
String[] inputNames = extractNames(input.getName());
|
||||
return matchesAnyName(searchNames, inputNames);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] extractNames(String nameString) {
|
||||
if (nameString.contains(" // ")) {
|
||||
String leftName = nameString.substring(0, nameString.indexOf(" // "));
|
||||
String rightName = nameString.substring(nameString.indexOf(" // ") + 4);
|
||||
return new String[] { leftName, rightName };
|
||||
} else {
|
||||
return new String[] { nameString };
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesAnyName(String[] searchNames, String[] targetNames) {
|
||||
for (String searchName : searchNames) {
|
||||
for (String targetName : targetNames) {
|
||||
if (CardUtil.haveSameNames(searchName, targetName, this.ignoreMtgRuleForEmptyNames)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -191,6 +191,28 @@ public final class ZonesHandler {
|
|||
cardsToUpdate.get(toZone).add(mdfCard.getRightHalfCard());
|
||||
break;
|
||||
}
|
||||
} else if (targetCard instanceof RoomCard || targetCard instanceof RoomCardHalf) {
|
||||
// Room cards must be moved as single object
|
||||
RoomCard roomCard = (RoomCard) targetCard.getMainCard();
|
||||
cardsToMove = new CardsImpl(roomCard);
|
||||
cardsToUpdate.get(toZone).add(roomCard);
|
||||
switch (toZone) {
|
||||
case STACK:
|
||||
case BATTLEFIELD:
|
||||
// We don't want room halves to ever be on the battlefield
|
||||
cardsToUpdate.get(Zone.OUTSIDE).add(roomCard.getLeftHalfCard());
|
||||
cardsToUpdate.get(Zone.OUTSIDE).add(roomCard.getRightHalfCard());
|
||||
break;
|
||||
default:
|
||||
// move all parts
|
||||
cardsToUpdate.get(toZone).add(roomCard.getLeftHalfCard());
|
||||
cardsToUpdate.get(toZone).add(roomCard.getRightHalfCard());
|
||||
// If we aren't casting onto the stack or etb'ing, we need to clear this state
|
||||
// (countered, memory lapsed etc)
|
||||
// This prevents the state persisting for a put into play effect later
|
||||
roomCard.setLastCastHalf(null);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cardsToMove = new CardsImpl(targetCard);
|
||||
cardsToUpdate.get(toZone).addAll(cardsToMove);
|
||||
|
|
@ -269,7 +291,11 @@ public final class ZonesHandler {
|
|||
}
|
||||
|
||||
// update zone in main
|
||||
game.setZone(event.getTargetId(), event.getToZone());
|
||||
if (targetCard instanceof RoomCardHalf && (toZone == Zone.BATTLEFIELD)) {
|
||||
game.setZone(event.getTargetId(), Zone.OUTSIDE);
|
||||
} else {
|
||||
game.setZone(event.getTargetId(), event.getToZone());
|
||||
}
|
||||
|
||||
// update zone in other parts (meld cards, mdf half cards)
|
||||
cardsToUpdate.entrySet().forEach(entry -> {
|
||||
|
|
@ -378,7 +404,11 @@ public final class ZonesHandler {
|
|||
Permanent permanent;
|
||||
if (card instanceof MeldCard) {
|
||||
permanent = new PermanentMeld(card, event.getPlayerId(), game);
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
} else if (card instanceof RoomCardHalf) {
|
||||
// Only the main room card can etb
|
||||
permanent = new PermanentCard(card.getMainCard(), event.getPlayerId(), game);
|
||||
}
|
||||
else if (card instanceof ModalDoubleFacedCard) {
|
||||
// main mdf card must be processed before that call (e.g. only halves can be moved to battlefield)
|
||||
throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half");
|
||||
} else if (card instanceof Permanent) {
|
||||
|
|
|
|||
|
|
@ -699,6 +699,19 @@ public class GameEvent implements Serializable {
|
|||
AIRBENDED,
|
||||
FIREBENDED,
|
||||
WATERBENDED,
|
||||
/* A room permanent has a door unlocked.
|
||||
targetId the room permanent
|
||||
sourceId the unlock ability
|
||||
playerId the room permanent's controller
|
||||
flag true = left door unlocked false = right door unlocked
|
||||
*/
|
||||
DOOR_UNLOCKED,
|
||||
/* A room permanent has a door unlocked.
|
||||
targetId the room permanent
|
||||
sourceId the unlock ability
|
||||
playerId the room permanent's controller
|
||||
*/
|
||||
ROOM_FULLY_UNLOCKED,
|
||||
// custom events - must store some unique data to track
|
||||
CUSTOM_EVENT;
|
||||
|
||||
|
|
|
|||
|
|
@ -474,6 +474,16 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
void setHarnessed(boolean value);
|
||||
|
||||
boolean wasRoomUnlockedOnCast();
|
||||
|
||||
boolean isLeftDoorUnlocked();
|
||||
|
||||
boolean isRightDoorUnlocked();
|
||||
|
||||
boolean unlockRoomOnCast(Game game);
|
||||
|
||||
boolean unlockDoor(Game game, Ability source, boolean isLeftDoor);
|
||||
|
||||
@Override
|
||||
Permanent copy();
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ public class PermanentCard extends PermanentImpl {
|
|||
goodForBattlefield = false;
|
||||
} else if (card instanceof SplitCard) {
|
||||
// fused spells allowed (it uses main card)
|
||||
if (card.getSpellAbility() != null && !card.getSpellAbility().getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {
|
||||
// room spells allowed (it uses main card)
|
||||
if (card.getSpellAbility() != null && !card.getSpellAbility().getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED) && !(card instanceof RoomCard)) {
|
||||
goodForBattlefield = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean deathtouched;
|
||||
protected boolean solved = false;
|
||||
|
||||
protected boolean roomWasUnlockedOnCast = false;
|
||||
protected boolean leftHalfUnlocked = false;
|
||||
protected boolean rightHalfUnlocked = false;
|
||||
protected Map<String, List<UUID>> connectedCards = new HashMap<>();
|
||||
protected Set<MageObjectReference> dealtDamageByThisTurn;
|
||||
protected UUID attachedTo;
|
||||
|
|
@ -191,6 +194,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
this.morphed = permanent.morphed;
|
||||
this.disguised = permanent.disguised;
|
||||
this.leftHalfUnlocked = permanent.leftHalfUnlocked;
|
||||
this.rightHalfUnlocked = permanent.rightHalfUnlocked;
|
||||
this.roomWasUnlockedOnCast = permanent.roomWasUnlockedOnCast;
|
||||
this.manifested = permanent.manifested;
|
||||
this.cloaked = permanent.cloaked;
|
||||
this.createOrder = permanent.createOrder;
|
||||
|
|
@ -2086,4 +2092,65 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
return ZonesHandler.moveCard(zcInfo, game, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasRoomUnlockedOnCast() {
|
||||
return roomWasUnlockedOnCast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeftDoorUnlocked() {
|
||||
return leftHalfUnlocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRightDoorUnlocked() {
|
||||
return rightHalfUnlocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlockRoomOnCast(Game game) {
|
||||
if (this.roomWasUnlockedOnCast) {
|
||||
return false;
|
||||
}
|
||||
this.roomWasUnlockedOnCast = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlockDoor(Game game, Ability source, boolean isLeftDoor) {
|
||||
// Check if already unlocked
|
||||
boolean thisDoorUnlocked = isLeftDoor ? leftHalfUnlocked : rightHalfUnlocked;
|
||||
if (thisDoorUnlocked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log the unlock
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
String doorSide = isLeftDoor ? "left" : "right";
|
||||
game.informPlayers(controller.getLogName() + " unlocked the " + doorSide + " door of "
|
||||
+ getLogName() + CardUtil.getSourceLogName(game, source));
|
||||
}
|
||||
|
||||
// Update unlock state
|
||||
if (isLeftDoor) {
|
||||
leftHalfUnlocked = true;
|
||||
} else {
|
||||
rightHalfUnlocked = true;
|
||||
}
|
||||
|
||||
// Fire door unlock event
|
||||
GameEvent event = new GameEvent(GameEvent.EventType.DOOR_UNLOCKED, getId(), source, source.getControllerId());
|
||||
event.setFlag(isLeftDoor);
|
||||
game.fireEvent(event);
|
||||
|
||||
// Check if room is now fully unlocked
|
||||
boolean otherDoorUnlocked = isLeftDoor ? rightHalfUnlocked : leftHalfUnlocked;
|
||||
if (otherDoorUnlocked) {
|
||||
game.fireEvent(new GameEvent(EventType.ROOM_FULLY_UNLOCKED, getId(), source, source.getControllerId()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +138,13 @@ public class PermanentToken extends PermanentImpl {
|
|||
|
||||
@Override
|
||||
public Card getMainCard() {
|
||||
// token don't have game card, so return itself
|
||||
// Check if we have a copy source card (for tokens created from copied spells)
|
||||
Card copySourceCard = token.getCopySourceCard();
|
||||
if (copySourceCard != null) {
|
||||
return copySourceCard;
|
||||
}
|
||||
|
||||
// Fallback to current behavior
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class Demon66Token extends TokenImpl {
|
||||
|
||||
public Demon66Token() {
|
||||
super("Demon Token", "6/6 black Demon creature token with flying");
|
||||
cardType.add(CardType.CREATURE);
|
||||
color.setBlack(true);
|
||||
subtype.add(SubType.DEMON);
|
||||
power = new MageInt(6);
|
||||
toughness = new MageInt(6);
|
||||
addAbility(FlyingAbility.getInstance());
|
||||
}
|
||||
|
||||
private Demon66Token(final Demon66Token token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Demon66Token copy() {
|
||||
return new Demon66Token(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class MutagenToken extends TokenImpl {
|
||||
|
||||
public MutagenToken() {
|
||||
super("Mutagen Token", "Mutagen token");
|
||||
cardType.add(CardType.ARTIFACT);
|
||||
subtype.add(SubType.MUTAGEN);
|
||||
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(
|
||||
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new GenericManaCost(1)
|
||||
);
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new SacrificeSourceCost().setText("sacrifice this token"));
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private MutagenToken(final MutagenToken token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
public MutagenToken copy() {
|
||||
return new MutagenToken(this);
|
||||
}
|
||||
}
|
||||
30
Mage/src/main/java/mage/game/permanent/token/ToyToken.java
Normal file
30
Mage/src/main/java/mage/game/permanent/token/ToyToken.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class ToyToken extends TokenImpl {
|
||||
|
||||
public ToyToken() {
|
||||
super("Toy Token", "1/1 white Toy artifact creature token");
|
||||
cardType.add(CardType.ARTIFACT);
|
||||
cardType.add(CardType.CREATURE);
|
||||
subtype.add(SubType.TOY);
|
||||
color.setWhite(true);
|
||||
power = new MageInt(1);
|
||||
toughness = new MageInt(1);
|
||||
}
|
||||
|
||||
private ToyToken(final ToyToken token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToyToken copy() {
|
||||
return new ToyToken(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,54 +26,19 @@ public class TargetOptimization {
|
|||
|
||||
// for up to or any amount - limit max game sims to analyse
|
||||
// (it's useless to calc all possible combinations on too much targets)
|
||||
static public int AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE = 7;
|
||||
static public int AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE = 18;
|
||||
|
||||
public static void optimizePossibleTargets(Ability source, Game game, Set<UUID> possibleTargets, int maxPossibleTargetsToSimulate) {
|
||||
// remove duplicated/same creatures
|
||||
// example: distribute 3 damage between 10+ same tokens
|
||||
// example: target x1 from x10 forests - it's useless to recalc each forest
|
||||
|
||||
if (possibleTargets.size() < maxPossibleTargetsToSimulate) {
|
||||
if (possibleTargets.size() <= maxPossibleTargetsToSimulate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// split targets by groups
|
||||
Map<UUID, String> targetGroups = new HashMap<>();
|
||||
possibleTargets.forEach(id -> {
|
||||
String groupKey = "";
|
||||
|
||||
// player
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
groupKey = getTargetGroupKeyAsPlayer(player);
|
||||
}
|
||||
|
||||
// game object
|
||||
MageObject object = game.getObject(id);
|
||||
if (object != null) {
|
||||
groupKey = object.getName();
|
||||
if (object instanceof Permanent) {
|
||||
groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object);
|
||||
} else if (object instanceof Card) {
|
||||
groupKey += getTargetGroupKeyAsCard(game, (Card) object);
|
||||
} else {
|
||||
groupKey += getTargetGroupKeyAsOther(game, object);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown - use all
|
||||
if (groupKey.isEmpty()) {
|
||||
groupKey = id.toString();
|
||||
}
|
||||
|
||||
targetGroups.put(id, groupKey);
|
||||
});
|
||||
|
||||
Map<String, List<UUID>> groups = new HashMap<>();
|
||||
targetGroups.forEach((id, groupKey) -> {
|
||||
groups.computeIfAbsent(groupKey, k -> new ArrayList<>());
|
||||
groups.get(groupKey).add(id);
|
||||
});
|
||||
Map<String, ArrayList<UUID>> targetGroups = createGroups(game, possibleTargets, maxPossibleTargetsToSimulate, false);
|
||||
|
||||
// optimize logic:
|
||||
// - use one target from each target group all the time
|
||||
|
|
@ -81,7 +46,7 @@ public class TargetOptimization {
|
|||
|
||||
// use one target per group
|
||||
Set<UUID> newPossibleTargets = new HashSet<>();
|
||||
groups.forEach((groupKey, groupTargets) -> {
|
||||
targetGroups.forEach((groupKey, groupTargets) -> {
|
||||
UUID targetId = RandomUtil.randomFromCollection(groupTargets);
|
||||
if (targetId != null) {
|
||||
newPossibleTargets.add(targetId);
|
||||
|
|
@ -91,13 +56,13 @@ public class TargetOptimization {
|
|||
|
||||
// use random target until fill condition
|
||||
while (newPossibleTargets.size() < maxPossibleTargetsToSimulate) {
|
||||
String groupKey = RandomUtil.randomFromCollection(groups.keySet());
|
||||
String groupKey = RandomUtil.randomFromCollection(targetGroups.keySet());
|
||||
if (groupKey == null) {
|
||||
break;
|
||||
}
|
||||
List<UUID> groupTargets = groups.getOrDefault(groupKey, null);
|
||||
List<UUID> groupTargets = targetGroups.getOrDefault(groupKey, null);
|
||||
if (groupTargets == null || groupTargets.isEmpty()) {
|
||||
groups.remove(groupKey);
|
||||
targetGroups.remove(groupKey);
|
||||
continue;
|
||||
}
|
||||
UUID targetId = RandomUtil.randomFromCollection(groupTargets);
|
||||
|
|
@ -112,6 +77,49 @@ public class TargetOptimization {
|
|||
possibleTargets.addAll(newPossibleTargets);
|
||||
}
|
||||
|
||||
private static Map<String, ArrayList<UUID>> createGroups(Game game, Set<UUID> possibleTargets, int maxPossibleTargetsToSimulate, boolean isLoose) {
|
||||
Map<String, ArrayList<UUID>> targetGroups = new HashMap<>();
|
||||
|
||||
possibleTargets.forEach(id -> {
|
||||
String groupKey = "";
|
||||
|
||||
// player
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
groupKey = getTargetGroupKeyAsPlayer(player);
|
||||
}
|
||||
|
||||
// game object
|
||||
MageObject object = game.getObject(id);
|
||||
if (object != null) {
|
||||
groupKey = object.getName();
|
||||
if (object instanceof Permanent) {
|
||||
groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object, isLoose);
|
||||
} else if (object instanceof Card) {
|
||||
groupKey += getTargetGroupKeyAsCard(game, (Card) object, isLoose);
|
||||
} else {
|
||||
groupKey += getTargetGroupKeyAsOther(game, object);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown - use all
|
||||
if (groupKey.isEmpty()) {
|
||||
groupKey = id.toString();
|
||||
}
|
||||
|
||||
targetGroups.computeIfAbsent(groupKey, k -> new ArrayList<>()).add(id);
|
||||
});
|
||||
|
||||
if (targetGroups.size() > maxPossibleTargetsToSimulate && !isLoose) {
|
||||
// If too many possible target groups, regroup with less specific characteristics
|
||||
return createGroups(game, possibleTargets, maxPossibleTargetsToSimulate, true);
|
||||
}
|
||||
|
||||
// Return appropriate target groups or, if still too many possible targets after loose grouping,
|
||||
// allow optimizePossibleTargets (defined above) to choose random targets within limit
|
||||
return targetGroups;
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsPlayer(Player player) {
|
||||
// use all
|
||||
return String.join(";", Arrays.asList(
|
||||
|
|
@ -120,38 +128,57 @@ public class TargetOptimization {
|
|||
));
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsPermanent(Game game, Permanent permanent) {
|
||||
private static String getTargetGroupKeyAsPermanent(Game game, Permanent permanent, boolean isLoose) {
|
||||
// split by name and stats
|
||||
// TODO: rework and combine with PermanentEvaluator (to use battlefield score)
|
||||
|
||||
// try to use short text/hash for lesser data on debug
|
||||
return String.join(";", Arrays.asList(
|
||||
permanent.getName(),
|
||||
String.valueOf(permanent.getControllerId().hashCode()),
|
||||
String.valueOf(permanent.getOwnerId().hashCode()),
|
||||
String.valueOf(permanent.isTapped()),
|
||||
String.valueOf(permanent.getPower().getValue()),
|
||||
String.valueOf(permanent.getToughness().getValue()),
|
||||
String.valueOf(permanent.getDamage()),
|
||||
String.valueOf(permanent.getCardType(game).toString().hashCode()),
|
||||
String.valueOf(permanent.getSubtype(game).toString().hashCode()),
|
||||
String.valueOf(permanent.getCounters(game).getTotalCount()),
|
||||
String.valueOf(permanent.getAbilities(game).size()),
|
||||
String.valueOf(permanent.getRules(game).toString().hashCode())
|
||||
));
|
||||
if (!isLoose) {
|
||||
return String.join(";", Arrays.asList(
|
||||
permanent.getName(),
|
||||
String.valueOf(permanent.getControllerId().hashCode()),
|
||||
String.valueOf(permanent.getOwnerId().hashCode()),
|
||||
String.valueOf(permanent.isTapped()),
|
||||
String.valueOf(permanent.getPower().getValue()),
|
||||
String.valueOf(permanent.getToughness().getValue()),
|
||||
String.valueOf(permanent.getDamage()),
|
||||
String.valueOf(permanent.getCardType(game).toString().hashCode()),
|
||||
String.valueOf(permanent.getSubtype(game).toString().hashCode()),
|
||||
String.valueOf(permanent.getCounters(game).getTotalCount()),
|
||||
String.valueOf(permanent.getAbilities(game).size()),
|
||||
String.valueOf(permanent.getRules(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
else {
|
||||
return String.join(";", Arrays.asList(
|
||||
String.valueOf(permanent.getControllerId().hashCode()),
|
||||
String.valueOf(permanent.getPower().getValue()),
|
||||
String.valueOf(permanent.getToughness().getValue()),
|
||||
String.valueOf(permanent.getDamage()),
|
||||
String.valueOf(permanent.getCardType(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsCard(Game game, Card card) {
|
||||
private static String getTargetGroupKeyAsCard(Game game, Card card, boolean isLoose) {
|
||||
// split by name and stats
|
||||
return String.join(";", Arrays.asList(
|
||||
card.getName(),
|
||||
String.valueOf(card.getOwnerId().hashCode()),
|
||||
String.valueOf(card.getCardType(game).toString().hashCode()),
|
||||
String.valueOf(card.getSubtype(game).toString().hashCode()),
|
||||
String.valueOf(card.getCounters(game).getTotalCount()),
|
||||
String.valueOf(card.getAbilities(game).size()),
|
||||
String.valueOf(card.getRules(game).toString().hashCode())
|
||||
));
|
||||
if (!isLoose) {
|
||||
return String.join(";", Arrays.asList(
|
||||
card.getName(),
|
||||
String.valueOf(card.getOwnerId().hashCode()),
|
||||
String.valueOf(card.getCardType(game).toString().hashCode()),
|
||||
String.valueOf(card.getSubtype(game).toString().hashCode()),
|
||||
String.valueOf(card.getCounters(game).getTotalCount()),
|
||||
String.valueOf(card.getAbilities(game).size()),
|
||||
String.valueOf(card.getRules(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
else {
|
||||
return String.join(";", Arrays.asList(
|
||||
String.valueOf(card.getOwnerId().hashCode()),
|
||||
String.valueOf(card.getCardType(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsOther(Game game, MageObject item) {
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
package mage.util.validation;
|
||||
|
||||
import mage.abilities.keyword.PartnerFatherAndSonAbility;
|
||||
import mage.cards.Card;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public enum PartnerFatherAndSonValidator implements CommanderValidator {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean checkPartner(Card commander1, Card commander2) {
|
||||
return commander1.getAbilities().containsClass(PartnerFatherAndSonAbility.class);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue