[WOC] Implement Unfinished Business (#11144)

* [WOC] Implement Unfinished Business

* Added assertIsNotAttachedTo

* Added unittests for [WOC] Unfinished Business

* Remove unused import

* Refactored assertIsAttachedTo & assertIsNotAttachedTo into assertIsAttached.

* Added processAction after return of creature

* Added comments and minor changes

---------

Co-authored-by: Codermann63 <zoiazous@gmail.com>
This commit is contained in:
Codermann63 2023-09-14 03:12:09 +02:00 committed by GitHub
parent 4a53ca5ad8
commit e92031a3bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 281 additions and 13 deletions

View file

@ -0,0 +1,126 @@
package mage.cards.u;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
/**
*
* @author Codermann63
*/
public final class UnfinishedBusiness extends CardImpl {
private static final FilterCard auraOrEquipmentFilter = new FilterCard("Aura or Equipment card");
static {
auraOrEquipmentFilter.add(Predicates.or(
SubType.EQUIPMENT.getPredicate(),
SubType.AURA.getPredicate()
));
}
public UnfinishedBusiness(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{W}");
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(1,1, StaticFilters.FILTER_CARD_CREATURE));
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 2, auraOrEquipmentFilter));
// Return target creature card from your graveyard to the battlefield,
// then return up to two target Aura and/or Equipment cards from your graveyard to the battlefield attached to that creature.
this.getSpellAbility().addEffect(new UnfinishedBusinessEffect());
}
private UnfinishedBusiness(final UnfinishedBusiness card) {
super(card);
}
@Override
public UnfinishedBusiness copy() {
return new UnfinishedBusiness(this);
}
}
class UnfinishedBusinessEffect extends OneShotEffect{
UnfinishedBusinessEffect() {
super(Outcome.PutCreatureInPlay);
staticText = "Return target creature card from your graveyard to the battlefield, then return up to two target Aura and/or Equipment cards from your graveyard to the battlefield attached to that creature. <i>(If the Auras can not enchant that creature, they remain in your graveyard.)</i>";
}
private UnfinishedBusinessEffect(final UnfinishedBusinessEffect effect) {super(effect);}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer((source.getControllerId()));
if (controller == null){
return false;
}
// Return target creature from the graveyard to the battlefield
Card targetCreature = game.getCard(source.getTargets().getFirstTarget());
if (targetCreature != null){
controller.moveCards(targetCreature, Zone.BATTLEFIELD, source, game);
game.getState().processAction(game);
}
Permanent permanentCreature = targetCreature == null ? null : game.getPermanent(targetCreature.getId());
// Target auras and/or equipment in your graveyard.
Cards cardsInitial = new CardsImpl(source.getTargets().get(1).getTargets());
if (cardsInitial.isEmpty()) {
return false;
}
// Auras that cannot be attached to the creature stay in the graveyard
// Create a list of legal cards to return
Cards cards = new CardsImpl();
for(UUID c: cardsInitial){
if (game.getCard(c).hasSubtype(SubType.EQUIPMENT,game)){
// always add equipment cards
cards.add(c);
}
else if (permanentCreature != null &&
!permanentCreature.cantBeAttachedBy(game.getCard(c),source, game, false) &&
game.getCard(c).hasSubtype(SubType.AURA, game)){
// only add auras if the creature has returned
// only add auras that can be attached to creature
cards.add(c);
}
}
if (cards.isEmpty()){
return false;
}
// Handle return of legal auras and equipment
if (permanentCreature != null){
cards.getCards(game)
.forEach(card -> game.getState().setValue("attachTo:" + card.getId(), permanentCreature));
}
controller.moveCards(cards, Zone.BATTLEFIELD, source, game);
if (permanentCreature != null){
for(UUID id: cards){
if (!permanentCreature.cantBeAttachedBy(game.getCard(id), source, game, true)){
permanentCreature.addAttachment(id, source, game);
}
}
}
return true;
}
@Override
public UnfinishedBusinessEffect copy() {
return new UnfinishedBusinessEffect(this);
}
}

View file

@ -148,6 +148,7 @@ public final class WildsOfEldraineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Tithe Taker", 80, Rarity.RARE, mage.cards.t.TitheTaker.class));
cards.add(new SetCardInfo("Transcendent Envoy", 81, Rarity.COMMON, mage.cards.t.TranscendentEnvoy.class));
cards.add(new SetCardInfo("Umbra Mystic", 82, Rarity.RARE, mage.cards.u.UmbraMystic.class));
cards.add(new SetCardInfo("Unfinished Business", 8, Rarity.RARE, mage.cards.u.UnfinishedBusiness.class));
cards.add(new SetCardInfo("Utopia Sprawl", 135, Rarity.COMMON, mage.cards.u.UtopiaSprawl.class));
cards.add(new SetCardInfo("Verdant Embrace", 136, Rarity.RARE, mage.cards.v.VerdantEmbrace.class));
cards.add(new SetCardInfo("Vitu-Ghazi, the City-Tree", 173, Rarity.UNCOMMON, mage.cards.v.VituGhaziTheCityTree.class));

View file

@ -33,7 +33,7 @@ public class ReconfigureTest extends CardTestPlayerBase {
assertType(boar, CardType.CREATURE, false);
assertSubtype(boar, SubType.EQUIPMENT);
assertIsAttachedTo(playerA, boar, lion);
assertAttachedTo(playerA, boar, lion, true);
assertPowerToughness(playerA, lion, 2 + 3, 2 + 2);
assertAbility(playerA, lion, TrampleAbility.getInstance(), true);
}
@ -74,7 +74,7 @@ public class ReconfigureTest extends CardTestPlayerBase {
assertType(boar, CardType.CREATURE, false);
assertSubtype(boar, SubType.EQUIPMENT);
assertIsAttachedTo(playerA, boar, lion);
assertAttachedTo(playerA, boar, lion, true);
assertPowerToughness(playerA, lion, 2 + 3, 2 + 2);
assertAbility(playerA, lion, TrampleAbility.getInstance(), true);
}
@ -94,7 +94,7 @@ public class ReconfigureTest extends CardTestPlayerBase {
assertType(boar, CardType.CREATURE, false);
assertSubtype(boar, SubType.EQUIPMENT);
assertIsAttachedTo(playerA, boar, lion);
assertAttachedTo(playerA, boar, lion, true);
assertPowerToughness(playerA, lion, 2 + 3, 2 + 2);
assertAbility(playerA, lion, TrampleAbility.getInstance(), true);
}

View file

@ -27,7 +27,7 @@ public class BronzehideLionTest extends CardTestPlayerBase {
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, lion, 0);
assertIsAttachedTo(playerA, lion, "Grizzly Bears");
assertAttachedTo(playerA, lion, "Grizzly Bears", true);
}

View file

@ -28,7 +28,7 @@ public class BeltOfGiantStrengthTest extends CardTestPlayerBase {
setStopAt(1, PhaseStep.END_TURN);
execute();
assertIsAttachedTo(playerA, belt, gigantosauras);
assertAttachedTo(playerA, belt, gigantosauras, true);
Assert.assertTrue(
"All Forests should be untapped",
currentGame
@ -51,6 +51,6 @@ public class BeltOfGiantStrengthTest extends CardTestPlayerBase {
setStopAt(1, PhaseStep.END_TURN);
execute();
assertIsAttachedTo(playerA, belt, gigantosauras);
assertAttachedTo(playerA, belt, gigantosauras, true);
}
}

View file

@ -39,6 +39,6 @@ public class ForgeAnewTest extends CardTestPlayerBase {
execute();
// Make sure it is attached
assertIsAttachedTo(playerA, EQUIPMENT, CREATURE);
assertAttachedTo(playerA, EQUIPMENT, CREATURE, true);
}
}

View file

@ -0,0 +1,138 @@
package org.mage.test.cards.single.woc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Codermann63
*/
public class UnfinishedBusinessTest extends CardTestPlayerBase {
/*
* Unfinished Business
* {3}{W}{W} - Sorcery
* Return target creature card from your graveyard to the battlefield,
* then return up to two target Aura and/or Equipment cards from your graveyard to the battlefield attached to that creature.
* (If the Auras cant enchant that creature, they remain in your graveyard.)
*/
private static final String UNFINISHEDBUSINESS = "Unfinished Business";
// Deadly insect - 6/1 creature with Shroud
private static final String SHROUDCREATURE = "Deadly Insect";
// Apostle of Purifying Light - white creature with protection from black & activated ability to exile target card from a graveyard.
private static final String APOSTLE = "Apostle of Purifying Light";
private static final String BEAR = "Grizzly Bears";
// Blanchwood Armor - green aura - enchanted creature gets +1/+1 for each forest you control
private static final String AURA = "Blanchwood Armor";
// Ghoulflesh - black aura - enchanted creature get -1/-1 and is a black Zombie in addition to its other colors and types.
private static final String GHOULFLESH = "Ghoulflesh";
// Shuko - colorless Equipment - Equipped creature gets +1/+0.
private static final String EQUIPMENT = "Shuko";
// Enormous Energy Blade - black equipment - Equipped creature gets +4/+0. Whenever Enormous Energy Blade becomes attached to a creature, tap that creature.
private static final String EEB = "Enormous Energy Blade";
// Return a creature with shroud, and return an Aura and an Equipment checking that both attach.
@Test
public void testShroud() {
addCard(Zone.HAND, playerA, UNFINISHEDBUSINESS);
addCard(Zone.GRAVEYARD, playerA, SHROUDCREATURE, 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.GRAVEYARD, playerA, AURA);
addCard(Zone.GRAVEYARD, playerA, EQUIPMENT);
setStrictChooseMode(true);
castSpell(1,PhaseStep.PRECOMBAT_MAIN,playerA,UNFINISHEDBUSINESS,SHROUDCREATURE);
addTarget(playerA, AURA+"^"+EQUIPMENT);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
execute();
// Check that all returned to the battlefield
assertPermanentCount(playerA, SHROUDCREATURE, 1);
assertPermanentCount(playerA, AURA, 1);
assertPermanentCount(playerA, EQUIPMENT, 1);
// Check aura and equipment is attached
assertAttachedTo(playerA, AURA, SHROUDCREATURE, true);
assertAttachedTo(playerA, EQUIPMENT, SHROUDCREATURE, true);
}
// Return Apostle of purifying light (creature with protection from black),
// and try to return a Ghoulflesh(black aura) and Enormous energy blade(black equipment).
// The aura should remain in graveyard and the equipment should return but not attach.
@Test
public void testProtection(){
addCard(Zone.HAND, playerA, UNFINISHEDBUSINESS);
// Nexus wardens gain life if an enchantment entered the battlefield
// This is to test if ghoulflesh enters the battlefield
addCard(Zone.BATTLEFIELD, playerA, "Nexus Wardens");
addCard(Zone.GRAVEYARD, playerA, APOSTLE);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.GRAVEYARD, playerA, GHOULFLESH);
addCard(Zone.GRAVEYARD, playerA, EEB);
setStrictChooseMode(true);
castSpell(1,PhaseStep.PRECOMBAT_MAIN,playerA,UNFINISHEDBUSINESS,APOSTLE);
addTarget(playerA, GHOULFLESH+"^"+EEB);
waitStackResolved(1, PhaseStep.END_TURN);
execute();
// Check boardstate
assertPermanentCount(playerA, APOSTLE, 1);
assertPermanentCount(playerA, GHOULFLESH, 0);
assertPermanentCount(playerA, EEB, 1);
// EEB should never have been attached and therefore the White knight should be untapped
assertTapped(APOSTLE,false);
assertAttachedTo(playerA, EEB, APOSTLE,false);
// Check that Ghoulflesh never entered the battlefield
assertLife(playerA, 20);
assertGraveyardCount(playerA,GHOULFLESH, 1);
}
// Test equipment return if creature is exiled from graveyard before spell resolution
@Test
public void testExileCreatureBeforeResolution(){
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
addCard(Zone.HAND,playerA,UNFINISHEDBUSINESS);
addCard(Zone.GRAVEYARD, playerA, EQUIPMENT);
addCard(Zone.GRAVEYARD, playerA, AURA);
addCard(Zone.GRAVEYARD, playerA, BEAR);
// Apostle of Purifying Light has an activated ability to exile card from graveyard
addCard(Zone.BATTLEFIELD, playerB, APOSTLE);
// Nexus wardens gain life if an enchantment entered the battlefield
// This is to test if the aura enters the battlefield
addCard(Zone.BATTLEFIELD, playerA, "Nexus Wardens");
setStrictChooseMode(true);
// Cast Unfinished Business targeting GrizzlyBears, and aura and an equipment
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, UNFINISHEDBUSINESS);
addTarget(playerA, BEAR);
addTarget(playerA, EQUIPMENT+"^"+AURA);
// Exile Grizzly Bears from graveyard before spell resolution
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB,"{2}: ",BEAR,UNFINISHEDBUSINESS);
waitStackResolved(1, PhaseStep.END_TURN);
execute();
// Grizzly Bears should be exiled
assertExileCount(playerA, BEAR, 1);
// The aura should still be in the graveyard and should never have entered
assertGraveyardCount(playerA, AURA, 1);
assertLife(playerA, 20);
// The equipment should have returned to the battlefield
assertPermanentCount(playerA,EQUIPMENT, 1);
}
}

View file

@ -1556,24 +1556,27 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public void assertTopCardRevealed(TestPlayer player, boolean isRevealed) {
Assert.assertEquals(isRevealed, player.isTopCardRevealed());
}
public void assertIsAttachedTo(TestPlayer thePlayer, String theAttachment, String thePermanent) {
/**
* Asserts if, or if not, theAttachment is attached to thePermanent.
*
* @param isAttached true => assertIsAttachedTo, false => assertIsNotAttachedTo
*/
public void assertAttachedTo(TestPlayer thePlayer, String theAttachment, String thePermanent, boolean isAttached) {
List<Permanent> permanents = currentGame.getBattlefield().getAllActivePermanents().stream()
.filter(permanent -> permanent.isControlledBy(thePlayer.getId()))
.filter(permanent -> permanent.getName().equals(thePermanent))
.collect(Collectors.toList());
assertTrue(theAttachment + " was not attached to " + thePermanent,
assertTrue(theAttachment + " was "+ (!isAttached ? "":"not") +" attached to " + thePermanent,
!isAttached ^
permanents.stream()
.anyMatch(permanent -> permanent.getAttachments()
.stream()
.map(id -> currentGame.getCard(id))
.map(MageObject::getName)
.collect(Collectors.toList()).contains(theAttachment)));
}
public Permanent getPermanent(String cardName, UUID controller) {
assertAliaseSupportInActivateCommand(cardName, false);
Permanent found = null;