[MKM] Implement Coveted Falcon (#12057)

* [MKM] Implement Coveted Falcon

* Rework to use OneShotEffect with new approach

* Use static ZCC to be safe

* Add tests

* Remove check in GainControlTargetEffect
This commit is contained in:
Cameron Merkel 2024-05-03 23:46:58 -05:00 committed by GitHub
parent 2522f712e9
commit b1b83dc5b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 323 additions and 2 deletions

View file

@ -0,0 +1,119 @@
package mage.cards.c;
import java.util.List;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.constants.*;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.keyword.DisguiseAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget;
/**
* @author Cguy7777
*/
public final class CovetedFalcon extends CardImpl {
private static final FilterPermanent filter
= new FilterPermanent("permanent you own but don't control");
static {
filter.add(TargetController.YOU.getOwnerPredicate());
filter.add(TargetController.NOT_YOU.getControllerPredicate());
}
public CovetedFalcon(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}{U}");
this.subtype.add(SubType.BIRD);
this.power = new MageInt(1);
this.toughness = new MageInt(4);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever Coveted Falcon attacks, gain control of target permanent you own but don't control.
Ability attacksTriggeredAbility = new AttacksTriggeredAbility(new GainControlTargetEffect(Duration.Custom));
attacksTriggeredAbility.addTarget(new TargetPermanent(filter));
this.addAbility(attacksTriggeredAbility);
// Disguise {1}{U}
this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{1}{U}")));
// When Coveted Falcon is turned face up, target opponent gains control of any number of target permanents you control.
// Draw a card for each one they gained control of this way.
Ability turnedFaceUpTriggeredAbility = new TurnedFaceUpSourceTriggeredAbility(new CovetedFalconEffect());
turnedFaceUpTriggeredAbility.addTarget(new TargetOpponent());
turnedFaceUpTriggeredAbility.addTarget(
new TargetControlledPermanent(0, Integer.MAX_VALUE, StaticFilters.FILTER_CONTROLLED_PERMANENT, false));
this.addAbility(turnedFaceUpTriggeredAbility);
}
private CovetedFalcon(final CovetedFalcon card) {
super(card);
}
@Override
public CovetedFalcon copy() {
return new CovetedFalcon(this);
}
}
class CovetedFalconEffect extends OneShotEffect {
CovetedFalconEffect() {
super(Outcome.Benefit);
staticText = "target opponent gains control of any number of target permanents you control. " +
"Draw a card for each one they gained control of this way";
}
private CovetedFalconEffect(final CovetedFalconEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
UUID targetOpponentId = source.getFirstTarget();
List<UUID> targetPermanentIds = source.getTargets().get(1).getTargets();
for (UUID permanentId : targetPermanentIds) {
game.addEffect(
new GainControlTargetEffect(Duration.Custom, true, targetOpponentId)
.setTargetPointer(new FixedTarget(permanentId, game)),
source);
}
game.getState().processAction(game);
int cardsToDraw = 0;
for (UUID permanentId : targetPermanentIds) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null && permanent.isControlledBy(targetOpponentId)) {
cardsToDraw++;
}
}
new DrawCardSourceControllerEffect(cardsToDraw).apply(game, source);
return true;
}
@Override
public CovetedFalconEffect copy() {
return new CovetedFalconEffect(this);
}
}

View file

@ -86,6 +86,8 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
cards.add(new SetCardInfo("Conspiracy Unraveler", 47, Rarity.MYTHIC, mage.cards.c.ConspiracyUnraveler.class)); cards.add(new SetCardInfo("Conspiracy Unraveler", 47, Rarity.MYTHIC, mage.cards.c.ConspiracyUnraveler.class));
cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class)); cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class));
cards.add(new SetCardInfo("Cornered Crook", 120, Rarity.UNCOMMON, mage.cards.c.CorneredCrook.class)); cards.add(new SetCardInfo("Cornered Crook", 120, Rarity.UNCOMMON, mage.cards.c.CorneredCrook.class));
cards.add(new SetCardInfo("Coveted Falcon", 48, Rarity.RARE, mage.cards.c.CovetedFalcon.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Coveted Falcon", 393, Rarity.RARE, mage.cards.c.CovetedFalcon.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class)); cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class));
cards.add(new SetCardInfo("Crimestopper Sprite", 49, Rarity.COMMON, mage.cards.c.CrimestopperSprite.class)); cards.add(new SetCardInfo("Crimestopper Sprite", 49, Rarity.COMMON, mage.cards.c.CrimestopperSprite.class));
cards.add(new SetCardInfo("Crowd-Control Warden", 193, Rarity.COMMON, mage.cards.c.CrowdControlWarden.class)); cards.add(new SetCardInfo("Crowd-Control Warden", 193, Rarity.COMMON, mage.cards.c.CrowdControlWarden.class));

View file

@ -0,0 +1,201 @@
package org.mage.test.cards.single.mkm;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
public class CovetedFalconTest extends CardTestPlayerBase {
/*
* Coveted Falcon {1}{U}{U}
* Artifact Creature - Bird (1/4)
* Flying
* Whenever Coveted Falcon attacks, gain control of target permanent you own but don't control.
* Disguise {1}{U}
* When Coveted Falcon is turned face up, target opponent gains control of any number of target permanents you control.
* Draw a card for each one they gained control of this way.
*/
@Test
public void test_GiveControl() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
// When Coveted Falcon is turned face up, target opponent gains control of any number of target permanents you control.
// Draw a card for each one they gained control of this way.
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, "Grizzly Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Grizzly Bears", 1);
assertHandCount(playerA, 1);
}
@Test
public void test_TargetChangesControllerInResponse() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 5);
// Turn Against {4}{R}
// Instant
// Devoid
// Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn.
addCard(Zone.HAND, playerB, "Turn Against", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, "Grizzly Bears");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Turn Against", "Grizzly Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Grizzly Bears", 1);
assertHandCount(playerA, 0);
}
@Test
public void test_TargetLeavesAndReturnsUnderYourControlInResponse() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
addCard(Zone.BATTLEFIELD, playerA, "Putrid Goblin", 1); // 2/2 Zombie Goblin w/ Persist
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
addCard(Zone.HAND, playerB, "Murder", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, "Putrid Goblin");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Murder", "Putrid Goblin");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Putrid Goblin", 0);
assertHandCount(playerA, 0);
}
@Test
public void test_TargetLeavesAndReturnsUnderOpponentsControlInResponse() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
// Treacherous Pit-Dweller {B}{B}
// Creature - Demon (4/3)
// When Treacherous Pit-Dweller enters the battlefield from a graveyard, target opponent gains control of it.
// Undying
addCard(Zone.BATTLEFIELD, playerA, "Treacherous Pit-Dweller", 1);
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
addCard(Zone.HAND, playerB, "Murder", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, "Treacherous Pit-Dweller");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Murder", "Treacherous Pit-Dweller");
// When Treacherous Pit-Dweller enters the battlefield from a graveyard, target opponent gains control of it.
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Treacherous Pit-Dweller", 1);
assertHandCount(playerA, 0);
}
/*
* Guardian Beast {3}{B}
* Creature - Beast (2/4)
* As long as Guardian Beast is untapped, noncreature artifacts you control can't be enchanted,
* they have indestructible, and other players can't gain control of them.
* This effect doesn't remove Auras already attached to those artifacts.
*/
// When you turn Coveted Falcon face up while controlling Guardian Beast, you can target noncreature artifacts,
// but your opponent shouldn't gain control of them, and you shouldn't draw cards for them.
// If Guardian Beast dies after Falcon's turn-up trigger resolves, you should still keep those artifacts.
@Test
public void test_GiveArtifactAndNonartifactWhileControllingGuardianBeast() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
addCard(Zone.BATTLEFIELD, playerA, "Guardian Beast", 1);
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic", 1); // Artifact w/ Indestructible
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); // Should be given away normally
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
addCard(Zone.HAND, playerB, "Murder", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, "Darksteel Relic^Grizzly Bears");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Murder", "Guardian Beast");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Darksteel Relic", 0);
assertPermanentCount(playerB, "Grizzly Bears", 1);
assertHandCount(playerA, 1);
}
// If you target Guardian Beast AND one or more noncreature artifacts to give away, you should only give away the Beast.
// Test is duplicated to catch glitches that only occur when the targets are ordered a certain way.
// TODO Doesn't work properly
@Test
@Ignore
public void test_GiveGuardianBeastAndArtifactsA() {
setupGiveGuardianBeastAndArtifactsTest(true);
}
@Test
@Ignore
public void test_GiveGuardianBeastAndArtifactsB() {
setupGiveGuardianBeastAndArtifactsTest(false);
}
private void setupGiveGuardianBeastAndArtifactsTest(final boolean guardianBeastFirst) {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3 + 2);
addCard(Zone.BATTLEFIELD, playerA, "Guardian Beast", 1);
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic", 1);
addCard(Zone.HAND, playerA, "Coveted Falcon", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coveted Falcon using Disguise", true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up.");
addTarget(playerA, playerB);
addTarget(playerA, guardianBeastFirst ? "Guardian Beast^Darksteel Relic" : "Darksteel Relic^Guardian Beast");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, "Guardian Beast", 1);
assertPermanentCount(playerB, "Darksteel Relic", 0);
assertHandCount(playerA, 1);
}
}

View file

@ -105,8 +105,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
controlChanged = true; controlChanged = true;
} }
} }
if (source.isActivatedAbility() if (firstControlChange && !controlChanged) {
&& firstControlChange && !controlChanged) {
// If it was not possible to get control of target permanent by the activated ability the first time it took place // If it was not possible to get control of target permanent by the activated ability the first time it took place
// the effect failed (e.g. because of Guardian Beast) and must be discarded // the effect failed (e.g. because of Guardian Beast) and must be discarded
// This does not handle correctly multiple targets at once // This does not handle correctly multiple targets at once