forked from External/mage
Improve attachment to permanent logic; implement [PIP] Codsworth, Handy Helper (#12098)
* [PIP] Implement Codsworth, Handy Helper * Fix Codsworth and Halvar * Write tests for attachments * Fix auras going to graveyard when attaching to illegal targets * Fix Captured by the Consulate interaction * Fix failing tests, add additional test * Add source name to log message * Implement requested changes * Revert removed null check * Remove filter check, clean up code * Add additional test * Fix failing roles test * Account for all current attachment edge cases * Implement rule 303.4g * Apply requested changes
This commit is contained in:
parent
9fcbfdeac6
commit
8d249aa691
10 changed files with 580 additions and 20 deletions
168
Mage.Sets/src/mage/cards/c/CodsworthHandyHelper.java
Normal file
168
Mage.Sets/src/mage/cards/c/CodsworthHandyHelper.java
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.ConditionalMana;
|
||||
import mage.MageInt;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
|
||||
import mage.abilities.keyword.WardAbility;
|
||||
import mage.abilities.mana.ConditionalColoredManaAbility;
|
||||
import mage.abilities.mana.builder.ConditionalManaBuilder;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.CommanderPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public final class CodsworthHandyHelper extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterControlledPermanent("commanders");
|
||||
private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("Aura or Equipment you control");
|
||||
|
||||
static {
|
||||
filter.add(CommanderPredicate.instance);
|
||||
filter2.add(Predicates.or(SubType.AURA.getPredicate(), SubType.EQUIPMENT.getPredicate()));
|
||||
}
|
||||
|
||||
public CodsworthHandyHelper(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{W}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.ROBOT);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Commanders you control have ward {2}.
|
||||
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
|
||||
new WardAbility(new GenericManaCost(2)), Duration.WhileOnBattlefield, filter
|
||||
)));
|
||||
|
||||
// {T}: Add {W}{W}. Spend this mana only to cast Aura and/or Equipment spells.
|
||||
this.addAbility(new ConditionalColoredManaAbility(
|
||||
new TapSourceCost(), Mana.WhiteMana(2),
|
||||
new CodsworthHandyHelperManaBuilder()
|
||||
));
|
||||
|
||||
// {T}: Attach target Aura or Equipment you control to target creature you control. Activate only as a sorcery.
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(new CodsworthHandyHelperEffect(), new TapSourceCost());
|
||||
ability.addTarget(new TargetPermanent(filter2));
|
||||
ability.addTarget(new TargetControlledCreaturePermanent());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private CodsworthHandyHelper(final CodsworthHandyHelper card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodsworthHandyHelper copy() {
|
||||
return new CodsworthHandyHelper(this);
|
||||
}
|
||||
}
|
||||
|
||||
class CodsworthHandyHelperManaBuilder extends ConditionalManaBuilder {
|
||||
|
||||
@Override
|
||||
public ConditionalMana build(Object... options) {
|
||||
return new CodsworthHandyHelperConditionalMana(this.mana);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Spend this mana only to cast Aura and/or Equipment spells";
|
||||
}
|
||||
}
|
||||
|
||||
class CodsworthHandyHelperConditionalMana extends ConditionalMana {
|
||||
|
||||
public CodsworthHandyHelperConditionalMana(Mana mana) {
|
||||
super(mana);
|
||||
addCondition(new CodsworthHandyHelperManaCondition());
|
||||
}
|
||||
|
||||
private CodsworthHandyHelperConditionalMana(final CodsworthHandyHelperConditionalMana conditionalMana) {
|
||||
super(conditionalMana);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodsworthHandyHelperConditionalMana copy() {
|
||||
return new CodsworthHandyHelperConditionalMana(this);
|
||||
}
|
||||
}
|
||||
|
||||
class CodsworthHandyHelperManaCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (source instanceof SpellAbility) {
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
return card != null && (
|
||||
card.getSubtype(game).contains(SubType.AURA) || card.getSubtype(game).contains(SubType.EQUIPMENT)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
class CodsworthHandyHelperEffect extends OneShotEffect {
|
||||
|
||||
CodsworthHandyHelperEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "Attach target Aura or Equipment you control to target creature you control";
|
||||
}
|
||||
|
||||
private CodsworthHandyHelperEffect(final CodsworthHandyHelperEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodsworthHandyHelperEffect copy() {
|
||||
return new CodsworthHandyHelperEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Permanent attachment = game.getPermanent(source.getTargets().get(0).getFirstTarget());
|
||||
Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget());
|
||||
if (controller == null || attachment == null || creature == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (creature.cantBeAttachedBy(attachment, source, game, true)) {
|
||||
game.informPlayers(attachment.getLogName() + " was not attached to " + creature.getLogName()
|
||||
+ " because it's not a legal target" + CardUtil.getSourceLogName(game, source));
|
||||
return false;
|
||||
}
|
||||
Permanent oldCreature = game.getPermanent(attachment.getAttachedTo());
|
||||
if (oldCreature != null) {
|
||||
oldCreature.removeAttachment(attachment.getId(), source, game);
|
||||
}
|
||||
creature.addAttachment(attachment.getId(), source, game);
|
||||
game.informPlayers(attachment.getLogName() + " was "
|
||||
+ (oldCreature != null ? "unattached from " + oldCreature.getLogName() + " and " : "")
|
||||
+ "attached to " + creature.getLogName()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import mage.filter.predicate.permanent.EquippedPredicate;
|
|||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
|
|
@ -126,17 +125,12 @@ class HalvarGodOfBattleEffect extends OneShotEffect {
|
|||
if (controller != null && attachment != null && creature != null && creature.isControlledBy(controller.getId())) {
|
||||
Permanent oldCreature = game.getPermanent(attachment.getAttachedTo());
|
||||
if (oldCreature != null && oldCreature.isControlledBy(controller.getId()) && !oldCreature.equals(creature)) {
|
||||
boolean canAttach = true;
|
||||
if (attachment.hasSubtype(SubType.AURA, game)) {
|
||||
Target auraTarget = attachment.getSpellAbility().getTargets().get(0);
|
||||
if (!auraTarget.canTarget(creature.getId(), game)) {
|
||||
canAttach = false;
|
||||
}
|
||||
}
|
||||
if (!canAttach) {
|
||||
if (creature.cantBeAttachedBy(attachment, source, game, true)) {
|
||||
game.informPlayers(attachment.getLogName() + " was not attached to " + creature.getLogName()
|
||||
+ " because it's not a legal target for the aura");
|
||||
} else if (controller.chooseUse(Outcome.BoostCreature, "Attach " + attachment.getLogName()
|
||||
+ " because it's not a legal target");
|
||||
return false;
|
||||
}
|
||||
if (controller.chooseUse(Outcome.BoostCreature, "Attach " + attachment.getLogName()
|
||||
+ " to " + creature.getLogName() + "?", source, game)) {
|
||||
oldCreature.removeAttachment(attachment.getId(), source, game);
|
||||
creature.addAttachment(attachment.getId(), source, game);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ public final class Fallout extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Charisma Bobblehead", 130, Rarity.UNCOMMON, mage.cards.c.CharismaBobblehead.class));
|
||||
cards.add(new SetCardInfo("Cinder Glade", 257, Rarity.RARE, mage.cards.c.CinderGlade.class));
|
||||
cards.add(new SetCardInfo("Clifftop Retreat", 258, Rarity.RARE, mage.cards.c.ClifftopRetreat.class));
|
||||
cards.add(new SetCardInfo("Codsworth, Handy Helper", 14, Rarity.RARE, mage.cards.c.CodsworthHandyHelper.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Codsworth, Handy Helper", 366, Rarity.RARE, mage.cards.c.CodsworthHandyHelper.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Colonel Autumn", 98, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Colonel Autumn", 411, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Colonel Autumn", 626, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,374 @@
|
|||
package org.mage.test.cards.rules;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author PurpleCrowbar
|
||||
*/
|
||||
public class AttachmentTest extends CardTestPlayerBase {
|
||||
|
||||
// {T}: Attach target Aura or Equipment you control to target creature you control. Activate only as a sorcery.
|
||||
private static final String codsworth = "Codsworth, Handy Helper";
|
||||
|
||||
/**
|
||||
* Tests that a permanent that becomes non-attachable (i.e., non-aura, non-equipment, non-fortification)
|
||||
* due to loss of card type before resolution of an ability is not still attached.
|
||||
*/
|
||||
@Test
|
||||
public void testAttachNonAttachable() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lion Sash"); // Reconfigurable equipment creature
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Vedalken Orrery"); // You may cast spells as though they had flash
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||
addCard(Zone.HAND, playerA, "Darksteel Mutation"); // Enchanted creature loses equipment type
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Lion Sash");
|
||||
addTarget(playerA, codsworth);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Darksteel Mutation", "Lion Sash");
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertAttachedTo(playerA, "Lion Sash", codsworth, false);
|
||||
assertGraveyardCount(playerA, "Darksteel Mutation", 0);
|
||||
assertType("Lion Sash", CardType.CREATURE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that protection prevents attachment. Attachment should remain attached to whatever it was attached to.
|
||||
*/
|
||||
@Test
|
||||
public void testProtectionPreventsAttachment() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Candlestick"); // Blue equipment
|
||||
addCard(Zone.HAND, playerA, "Agoraphobia"); // Blue aura
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bloated Toad"); // Protection from blue
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", codsworth);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Agoraphobia", codsworth);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Candlestick");
|
||||
addTarget(playerA, "Bloated Toad");
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Agoraphobia");
|
||||
addTarget(playerA, "Bloated Toad");
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertAttachedTo(playerA, "Candlestick", codsworth, true);
|
||||
assertAttachedTo(playerA, "Agoraphobia", codsworth, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that attachments fall off a permanent that gains the appropriate protection.
|
||||
*/
|
||||
@Test
|
||||
public void testProtectionShedsAttachments() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
// {R}: {this} gains protection from red until end of turn.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Keeper of Kookus");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Jackhammer"); // Red equipment
|
||||
addCard(Zone.HAND, playerA, "Agility"); // Red aura
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Keeper of Kookus");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Agility", "Keeper of Kookus");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}:");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Agility", 1);
|
||||
assertPermanentCount(playerA, "Jackhammer", 1);
|
||||
assertAttachedTo(playerA, "Jackhammer", "Keeper of Kookus", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that attachments can only attach to legal targets. Specifically,
|
||||
* that an "Enchant land" aura cannot be attached to a nonland creature.
|
||||
*/
|
||||
@Test
|
||||
public void testDeniedMoveIllegalAuraTarget() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest");
|
||||
addCard(Zone.HAND, playerA, "Wild Growth"); // Enchant land aura
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wild Growth", "Forest");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Wild Growth");
|
||||
addTarget(playerA, codsworth);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Wild Growth", 0);
|
||||
assertAttachedTo(playerA, "Wild Growth", "Forest", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an equipment cannot be moved to a noncreature permanent.
|
||||
* Equipment should remain attached to whatever it was attached to.
|
||||
*/
|
||||
@Test
|
||||
public void testDeniedMoveIllegalEquipmentTarget() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bonesplitter"); // Equip {1}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bonebreaker Giant"); // Arbitrary creature
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Vedalken Orrery"); // You may cast spells as though they had flash
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.HAND, playerA, "One with the Stars"); // Enchanted permanent loses creature type
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {1}", codsworth);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Bonesplitter");
|
||||
addTarget(playerA, "Bonebreaker Giant");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "One with the Stars", "Bonebreaker Giant");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertAttachedTo(playerA, "Bonesplitter", codsworth, true);
|
||||
assertGraveyardCount(playerA, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an enchant ability prohibition not pertaining to card type is respected when trying to move an aura.
|
||||
* In this case, an "Enchant creature you don't control" aura shouldn't be movable to a controlled creature.
|
||||
*/
|
||||
@Test
|
||||
public void testDeniedMoveIllegalAuraTargetNotCardType() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
addCard(Zone.HAND, playerA, "Captured by the Consulate"); // Enchant creature you don't control
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Pia Nalaar"); // Arbitrary creature
|
||||
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}.", 4);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Captured by the Consulate", "Pia Nalaar");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Captured by the Consulate");
|
||||
addTarget(playerA, codsworth);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, 0);
|
||||
assertAttachedTo(playerB, "Captured by the Consulate", "Pia Nalaar", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an aura that attaches to a player (specifically an opponent in this case) cannot attach to a permanent.
|
||||
*/
|
||||
@Test
|
||||
public void testDeniedMoveIllegalPlayerAuraTarget() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.HAND, playerA, "Psychic Possession"); // Enchant opponent
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}.", 4);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Possession", playerB);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Psychic Possession");
|
||||
addTarget(playerA, codsworth);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Psychic Possession", 0); // TODO: Currently, the aura goes to graveyard. Must fix
|
||||
assertAttachedTo(playerA, "Psychic Possession", codsworth, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an aura that attaches to a card in the graveyard cannot attach to a permanent.
|
||||
*/
|
||||
@Test
|
||||
public void testDeniedMoveIllegalCardAuraTarget() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, codsworth);
|
||||
addCard(Zone.HAND, playerA, "Spellweaver Volute"); // Enchant instant card in graveyard
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.GRAVEYARD, playerA, "Counterspell"); // Arbitrary instant
|
||||
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}.", 5);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spellweaver Volute", "Counterspell");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Attach", "Spellweaver Volute");
|
||||
addTarget(playerA, codsworth);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Spellweaver Volute", 0); // TODO: Currently goes to graveyard. Must fix
|
||||
assertAttachedTo(playerA, "Spellweaver Volute", codsworth, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a player gains protection from an aura, it falls off (is moved to the graveyard).
|
||||
*/
|
||||
@Test
|
||||
public void testCurseFallsOffFromGainedProtection() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Curse of Opulence"); // Arbitrary curse (enchant player)
|
||||
addCard(Zone.HAND, playerB, "Runed Halo"); // You have protection from the chosen card name.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Opulence", playerB);
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Runed Halo");
|
||||
setChoice(playerB, "Curse of Opulence");
|
||||
|
||||
setStopAt(2, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Curse of Opulence", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when an aura is cast and the target becomes illegal (in this
|
||||
* case, due to a change in card type), the aura goes to the graveyard.
|
||||
*/
|
||||
@Test
|
||||
public void testCastAuraIllegalTarget() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Arcane Flight"); // Arbitrary "enchant creature" aura
|
||||
addCard(Zone.HAND, playerA, "One with the Stars"); // Enchanted permanent loses creature type
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Runeclaw Bear"); // Arbitrary creature
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Vedalken Orrery"); // You may cast spells as though they had flash
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcane Flight", "Runeclaw Bear");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "One with the Stars", "Runeclaw Bear");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Arcane Flight", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when an aura tries to move from a player's hand without being cast, and
|
||||
* it has no legal objects to attach to, it instead remains in the player's hand.
|
||||
*/
|
||||
@Test
|
||||
public void testAuraMoveFromHandWithNoAttachableObject() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Show and Tell"); // Puts an aura from hand onto the battlefield without casting
|
||||
addCard(Zone.HAND, playerA, "Aether Tunnel"); // Enchant creature
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Show and Tell");
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerB, false);
|
||||
addTarget(playerA, "Aether Tunnel");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Aether Tunnel", 0);
|
||||
assertHandCount(playerA, "Aether Tunnel", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when an aura tries to move from a player's graveyard without being cast, and
|
||||
* it has no legal objects to attach to, it instead remains in the player's graveyard.
|
||||
*/
|
||||
@Test
|
||||
public void testAuraMoveFromGraveyardWithNoAttachableObject() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Replenish"); // Put all enchantments from graveyard onto battlefield
|
||||
addCard(Zone.GRAVEYARD, playerA, "Divine Favor"); // Enchant creature, gain 3 life on ETB
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Divine Favor", 1);
|
||||
assertLife(playerA, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when an aura tries to move from exile without being cast, and
|
||||
* it has no legal objects to attach to, it instead remains in exile.
|
||||
*/
|
||||
@Test
|
||||
public void testAuraMoveFromExileWithNoAttachableObject() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Sudden Disappearance"); // Exile nonland permanents, return at end of turn
|
||||
addCard(Zone.HAND, playerA, "Spiritual Visit"); // Create a 1/1 token
|
||||
addCard(Zone.HAND, playerA, "Divine Favor"); // Enchant creature, gain 3 life on ETB
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 9);
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spiritual Visit");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Divine Favor", "Spirit Token");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sudden Disappearance", playerA);
|
||||
waitStackResolved(1, PhaseStep.END_TURN, playerA);
|
||||
|
||||
setStopAt(2, PhaseStep.UNTAP);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Divine Favor", 0);
|
||||
assertExileCount(playerA, "Divine Favor", 1);
|
||||
assertLife(playerA, 23);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Dream Leash can correctly attach to untapped permanents when it enters through non-casting means (e.g., via Show and Tell)
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void testDreamLeashNoUntappedPermanents() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Omniscience");
|
||||
// Enchant permanent
|
||||
// You can't choose an untapped permanent as Dream Leash's target as you cast Dream Leash.
|
||||
// You control enchanted permanent.
|
||||
addCard(Zone.HAND, playerA, "Dream Leash");
|
||||
addCard(Zone.HAND, playerA, "Show and Tell"); // Puts a permanent directly into play without casting
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Show and Tell");
|
||||
setChoice(playerA, true, 2); // 1. Cast without paying cost? 2. Put permanent into play?
|
||||
setChoice(playerB, false); // Put permanent into play?
|
||||
addTarget(playerA, "Dream Leash"); // Show and Tell's target
|
||||
setChoice(playerA, "Omniscience"); // Dream Leash's choice
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Dream Leash", 0);
|
||||
assertAttachedTo(playerA, "Dream Leash", "Omniscience", true);
|
||||
assertHandCount(playerA, "Dream Leash", 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -774,7 +774,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if an event won't happen because of an rule modifying effect
|
||||
* Checks if an event won't happen because of a rule modifying effect
|
||||
*
|
||||
* @param event
|
||||
* @param targetAbility ability the event is attached to. can be null.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import mage.constants.*;
|
|||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.counters.Counters;
|
||||
import mage.filter.FilterOpponent;
|
||||
import mage.filter.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameState;
|
||||
import mage.game.ZoneChangeInfo;
|
||||
|
|
@ -1355,7 +1355,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return !ability.getDoesntRemoveControlled() || isControlledBy(game.getControllerId(attachment.getId()));
|
||||
}
|
||||
}
|
||||
return game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode);
|
||||
|
||||
boolean canAttach = true;
|
||||
Permanent attachmentPermanent = game.getPermanent(attachment.getId());
|
||||
// If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability
|
||||
if (attachment.hasSubtype(SubType.AURA, game)
|
||||
&& attachmentPermanent != null
|
||||
&& attachmentPermanent.getSpellAbility() != null
|
||||
&& !attachmentPermanent.getSpellAbility().getTargets().isEmpty()) {
|
||||
// Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
|
||||
// Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
|
||||
canAttach = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game);
|
||||
}
|
||||
|
||||
return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -4836,6 +4836,15 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (enterTransformed != null && enterTransformed && !card.isTransformable()) {
|
||||
continue;
|
||||
}
|
||||
// 303.4g. If an Aura is entering the battlefield and there is no legal object or player for it to enchant,
|
||||
// the Aura remains in its current zone, unless that zone is the stack. In that case, the Aura is put into
|
||||
// its owner's graveyard instead of entering the battlefield. If the Aura is a token, it isn't created.
|
||||
if (card.hasSubtype(SubType.AURA, game)
|
||||
&& card.getSpellAbility() != null
|
||||
&& !card.getSpellAbility().getTargets().isEmpty()
|
||||
&& !card.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).canChoose(byOwner ? card.getOwnerId() : getId(), game)) {
|
||||
continue;
|
||||
}
|
||||
ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source,
|
||||
byOwner ? card.getOwnerId() : getId(), fromZone, Zone.BATTLEFIELD, appliedEffects);
|
||||
infoList.add(new ZoneChangeInfo.Battlefield(event, faceDown, tapped, source));
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public interface Target extends Serializable {
|
|||
*/
|
||||
boolean canTarget(UUID id, Ability source, Game game);
|
||||
|
||||
boolean stillLegalTarget(UUID id, Ability source, Game game);
|
||||
boolean stillLegalTarget(UUID playerId, UUID id, Ability source, Game game);
|
||||
|
||||
boolean canTarget(UUID playerId, UUID id, Ability source, Game game);
|
||||
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ public abstract class TargetImpl implements Target {
|
|||
illegalTargets.add(targetId);
|
||||
continue;
|
||||
}
|
||||
if (!stillLegalTarget(targetId, source, game)) {
|
||||
if (!stillLegalTarget(source.getControllerId(), targetId, source, game)) {
|
||||
illegalTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
|
|
@ -546,8 +546,8 @@ public abstract class TargetImpl implements Target {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean stillLegalTarget(UUID id, Ability source, Game game) {
|
||||
return canTarget(id, source, game);
|
||||
public boolean stillLegalTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return canTarget(controllerId, id, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ public class TargetTappedPermanentAsYouCast extends TargetPermanent {
|
|||
|
||||
// See ruling: https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/253345-dream-leash
|
||||
@Override
|
||||
public boolean stillLegalTarget(UUID id, Ability source, Game game) {
|
||||
public boolean stillLegalTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
return permanent != null
|
||||
&& getFilter().match(permanent, game)
|
||||
&& super.canTarget(id, game); // check everything but leave out the tapped requirement
|
||||
&& super.canTarget(controllerId, id, source, game); // check everything but leave out the tapped requirement
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue