Implement DFC tokens for Incubate (#10231)

* remove incubate skip

* initial implementation of DFC tokens

* separate incubator back face to separate class

* small refactor to token copy function

* token copies now have back faces as well

* effects which modify token copies now correctly apply to both faces

* add skip for exception

* tokens now enter transformed correctly

* [MOC] remove skip for incubate

* fix verify failure
This commit is contained in:
Evan Kranzler 2023-04-27 11:03:25 -04:00 committed by GitHub
parent f8d23ff56b
commit 726e289646
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 540 additions and 156 deletions

View file

@ -55,7 +55,7 @@ public final class BrimazBlightOfOreskos extends CardImpl {
// At the beginning of each end step, if a Phyrexian died under your control this turn, proliferate.
this.addAbility(new BeginningOfEndStepTriggeredAbility(
new ProliferateEffect(), TargetController.ANY,
new ProliferateEffect(false), TargetController.ANY,
BrimazBlightOfOreskosCondition.instance, false
), new BrimazBlightOfOreskosWatcher());
}

View file

@ -18,12 +18,11 @@ import mage.game.Game;
import mage.game.events.CreateTokenEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
import mage.util.functions.CopyApplier;
import mage.util.functions.CopyTokenFunction;
import mage.util.functions.EmptyCopyApplier;
import mage.watchers.Watcher;
@ -145,8 +144,7 @@ class EsixFractalBloomEffect extends ReplacementEffectImpl {
}
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(copyFromPermanent, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
Token token = CopyTokenFunction.createTokenCopy(copyFromPermanent, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
applier.apply(game, token, source, permanent.getId());
return token;
}

View file

@ -14,11 +14,11 @@ import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.List;
import java.util.Objects;
@ -83,12 +83,11 @@ class GodPharaohsGiftEffect extends OneShotEffect {
return false;
}
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(cardChosen, game);
Token token = CopyTokenFunction.createTokenCopy(cardChosen, game);
token.removePTCDA();
token.setPower(4);
token.setToughness(4);
token.getColor().setColor(ObjectColor.BLACK);
token.setColor(ObjectColor.BLACK);
token.removeAllCreatureTypes();
token.addSubType(SubType.ZOMBIE);
token.putOntoBattlefield(1, game, source, source.getControllerId());

View file

@ -12,12 +12,12 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetadjustment.TargetAdjuster;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.HashSet;
import java.util.Set;
@ -91,12 +91,11 @@ class HourOfEternityEffect extends OneShotEffect {
for (Card card : cardsToExile) {
if (game.getState().getZone(card.getId()) == Zone.EXILED) {
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game);
Token token = CopyTokenFunction.createTokenCopy(card, game);
token.removePTCDA();
token.setPower(4);
token.setToughness(4);
token.getColor().setColor(ObjectColor.BLACK);
token.setColor(ObjectColor.BLACK);
token.removeAllCreatureTypes();
token.addSubType(SubType.ZOMBIE);
token.putOntoBattlefield(1, game, source, source.getControllerId());

View file

@ -15,11 +15,11 @@ import mage.filter.common.FilterArtifactPermanent;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.HashMap;
import java.util.Map;
@ -79,8 +79,7 @@ class MechanizedProductionEffect extends OneShotEffect {
if (sourceObject != null && sourceObject.getAttachedTo() != null) {
Permanent enchantedArtifact = game.getPermanentOrLKIBattlefield(sourceObject.getAttachedTo());
if (enchantedArtifact != null) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(enchantedArtifact, game);
Token token = CopyTokenFunction.createTokenCopy(enchantedArtifact, game);
token.putOntoBattlefield(1, game, source, source.getControllerId());
}
Map<String, Integer> countNames = new HashMap<>();

View file

@ -104,7 +104,7 @@ class MyrkulLordOfBonesEffect extends OneShotEffect {
return new CreateTokenCopyTargetEffect().setSavedPermanent(
new PermanentCard(CardUtil.getDefaultCardSideForBattlefield(game, card), source.getControllerId(), game)
).setPermanentModifier((token, g) -> {
token.getCardType().clear();
token.removeAllCardTypes();
token.addCardType(CardType.ENCHANTMENT);
token.retainAllEnchantmentSubTypes(g);
}).apply(game, source);

View file

@ -23,10 +23,11 @@ import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.List;
import java.util.Objects;
@ -99,8 +100,7 @@ class PhantomSteedEffect extends OneShotEffect {
return false;
}
for (Card card : exileZone.getCards(game)) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game);
Token token = CopyTokenFunction.createTokenCopy(card, game);
token.addSubType(SubType.ILLUSION);
token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true);
List<Permanent> permanents = token

View file

@ -21,10 +21,10 @@ import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.UUID;
@ -149,8 +149,7 @@ class PrototypePortalCreateTokenEffect extends OneShotEffect {
if (!permanent.getImprinted().isEmpty()) {
Card card = game.getCard(permanent.getImprinted().get(0));
if (card != null) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game);
Token token = CopyTokenFunction.createTokenCopy(card, game);
token.putOntoBattlefield(1, game, source, source.getControllerId());
return true;
}

View file

@ -11,9 +11,9 @@ import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.target.common.TargetCreaturePermanent;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.UUID;
@ -62,8 +62,7 @@ class SpittingImageEffect extends OneShotEffect {
permanent = (Permanent) game.getLastKnownInformation(source.getFirstTarget(), Zone.BATTLEFIELD);
}
if (permanent != null) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(permanent, game);
Token token = CopyTokenFunction.createTokenCopy(permanent, game);
token.putOntoBattlefield(1, game, source, source.getControllerId());
return true;
}

View file

@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType;
import java.util.Arrays;
import java.util.List;
/**
* @author TheElk801
*/
public final class MarchOfTheMachine extends ExpansionSet {
private static final List<String> unfinished = Arrays.asList("Assimilate Essence", "Blighted Burgeoning", "Bloated Processor", "Chrome Host Seedshark", "Compleated Huntmaster", "Converter Beast", "Corruption of Towashi", "Elesh Norn", "The Argent Etchings", "Elvish Vatkeeper", "Essence of Orthodoxy", "Eyes of Gitaxias", "Furnace Gremlin", "Gift of Compleation", "Glissa, Herald of Predation", "Glistening Dawn", "Ichor Drinker", "Infected Defector", "Injector Crocodile", "Marauding Dreadship", "Merciless Repurposing", "Norn's Inquisitor", "Phyrexian Awakening", "Progenitor Exarch", "Sculpted Perfection", "Searing Barb", "Sunder the Gateway", "Sunfall", "Tangled Skyline", "Tiller of Flesh", "Traumatic Revelation");
private static final MarchOfTheMachine instance = new MarchOfTheMachine();
public static MarchOfTheMachine getInstance() {
@ -488,8 +483,6 @@ public final class MarchOfTheMachine extends ExpansionSet {
cards.add(new SetCardInfo("Zimone and Dina", 318, Rarity.MYTHIC, mage.cards.z.ZimoneAndDina.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Zurgo and Ojutai", 258, Rarity.MYTHIC, mage.cards.z.ZurgoAndOjutai.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Zurgo and Ojutai", 319, Rarity.MYTHIC, mage.cards.z.ZurgoAndOjutai.class, NON_FULL_USE_VARIOUS));
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented
}
// @Override

View file

@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType;
import java.util.Arrays;
import java.util.List;
/**
* @author TheElk801
*/
public final class MarchOfTheMachineCommander extends ExpansionSet {
private static final List<String> unfinished = Arrays.asList("Blight Titan", "Brimaz, Blight of Oreskos", "Excise the Imperfect");
private static final MarchOfTheMachineCommander instance = new MarchOfTheMachineCommander();
public static MarchOfTheMachineCommander getInstance() {
@ -367,7 +362,5 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Workshop Elders", 245, Rarity.RARE, mage.cards.w.WorkshopElders.class));
cards.add(new SetCardInfo("Worthy Knight", 217, Rarity.RARE, mage.cards.w.WorthyKnight.class));
cards.add(new SetCardInfo("Yawgmoth's Vile Offering", 271, Rarity.RARE, mage.cards.y.YawgmothsVileOffering.class));
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented
}
}

View file

@ -0,0 +1,133 @@
package org.mage.test.cards.copy;
import mage.ObjectColor;
import mage.constants.PhaseStep;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class TokenCopyTest extends CardTestPlayerBase {
private static final String rite = "Rite of Replication";
private static final String prowler = "Kessig Prowler";
private static final String predator = "Sinuous Predator";
private static final String brink = "Back from the Brink";
private void checkProwlers(int prowlerCount, int predatorCount) {
assertPermanentCount(playerA, prowler, prowlerCount);
assertPermanentCount(playerA, predator, predatorCount);
for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) {
switch (permanent.getName()) {
case prowler:
Assert.assertEquals("Power of " + prowler + " should be 2", 2, permanent.getPower().getValue());
Assert.assertEquals("Toughness of " + prowler + " should be 1", 1, permanent.getToughness().getValue());
Assert.assertEquals(prowler + " should be green", ObjectColor.GREEN, permanent.getColor(currentGame));
Assert.assertTrue(prowler + " should be a Werewolf", permanent.hasSubtype(SubType.WEREWOLF, currentGame));
Assert.assertTrue(prowler + " should be a Horror", permanent.hasSubtype(SubType.HORROR, currentGame));
Assert.assertFalse(prowler + " should not be an Eldrazi", permanent.hasSubtype(SubType.ELDRAZI, currentGame));
Assert.assertEquals(prowler + " should have mana value 1", 1, permanent.getManaValue());
Assert.assertFalse(prowler + " should not be transformed", permanent.isTransformed());
break;
case predator:
Assert.assertEquals("Power of " + predator + " should be 4", 4, permanent.getPower().getValue());
Assert.assertEquals("Toughness of " + predator + " should be 4", 4, permanent.getToughness().getValue());
Assert.assertTrue(predator + " should be colorless", permanent.getColor(currentGame).isColorless());
Assert.assertTrue(predator + " should be an Eldrazi", permanent.hasSubtype(SubType.ELDRAZI, currentGame));
Assert.assertTrue(predator + " should be a Werewolf", permanent.hasSubtype(SubType.WEREWOLF, currentGame));
Assert.assertFalse(predator + " should not be a Horror", permanent.hasSubtype(SubType.HORROR, currentGame));
Assert.assertEquals(predator + " should have mana value 1", 1, permanent.getManaValue());
Assert.assertTrue(prowler + " should be transformed", permanent.isTransformed());
break;
}
}
}
@Test
public void testCopyDFC() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
addCard(Zone.BATTLEFIELD, playerA, prowler);
addCard(Zone.HAND, playerA, rite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite, prowler);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, rite, 1);
checkProwlers(1 + 1, 0);
}
@Test
public void testCopyDFCAndTransform() {
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 5 + 5 + 4);
addCard(Zone.BATTLEFIELD, playerA, prowler);
addCard(Zone.HAND, playerA, rite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite, prowler);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{G}");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{G}");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, rite, 1);
checkProwlers(0, 1 + 1);
}
@Test
public void testCopyTransformedDFC() {
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 5 + 4);
addCard(Zone.BATTLEFIELD, playerA, prowler);
addCard(Zone.HAND, playerA, rite);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{G}");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite, predator);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, rite, 1);
checkProwlers(0, 1 + 1);
}
@Test
public void testBackFromTheBrink() {
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 6 + 1);
addCard(Zone.BATTLEFIELD, playerA, brink);
addCard(Zone.GRAVEYARD, playerA, prowler);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertExileCount(playerA, prowler, 1);
checkProwlers(1, 0);
}
@Test
public void testBackFromTheBrinkTransform() {
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 6 + 1 + 5);
addCard(Zone.BATTLEFIELD, playerA, brink);
addCard(Zone.GRAVEYARD, playerA, prowler);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertExileCount(playerA, prowler, 1);
checkProwlers(0, 1);
}
}

View file

@ -246,6 +246,10 @@ public interface MageObject extends MageItem, Serializable, Copyable<MageObject>
getSuperType().add(superType);
}
default void removeSuperType(SuperType superType) {
getSuperType().remove(superType);
}
default boolean isBasic() {
return getSuperType().contains(SuperType.BASIC);
}

View file

@ -17,12 +17,12 @@ import mage.constants.*;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.target.targetpointer.FixedTarget;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import mage.util.functions.CopyApplier;
import mage.util.functions.CopyTokenFunction;
import mage.util.functions.EmptyCopyApplier;
import java.util.*;
@ -200,14 +200,13 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
}
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(copyFrom, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
Token token = CopyTokenFunction.createTokenCopy(copyFrom, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
applier.apply(game, token, source, targetId);
if (becomesArtifact) {
token.addCardType(CardType.ARTIFACT);
}
if (isntLegendary) {
token.getSuperType().remove(SuperType.LEGENDARY);
token.removeSuperType(SuperType.LEGENDARY);
}
if (startingLoyalty != -1) {
@ -238,7 +237,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
token.addSubType(additionalSubType);
}
if (color != null) {
token.getColor().setColor(color);
token.setColor(color);
}
additionalAbilities.stream().forEach(token::addAbility);
if (permanentModifier != null) {

View file

@ -13,9 +13,9 @@ import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.stream.Collectors;
@ -87,11 +87,10 @@ class EmbalmEffect extends OneShotEffect {
}
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.getColor().setColor(ObjectColor.WHITE);
Token token = CopyTokenFunction.createTokenCopy(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.setColor(ObjectColor.WHITE);
token.addSubType(SubType.ZOMBIE);
token.getManaCost().clear();
token.clearManaCost();
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.EMBALMED_CREATURE, token.getId(), source, controller.getId()));
token.putOntoBattlefield(1, game, source, controller.getId(), false, false, null);
// Probably it makes sense to remove also the Embalm ability (it's not shown on the token cards).

View file

@ -17,9 +17,9 @@ import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.HashSet;
import java.util.Iterator;
@ -85,8 +85,7 @@ class EncoreEffect extends OneShotEffect {
if (card == null) {
return false;
}
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game);
Token token = CopyTokenFunction.createTokenCopy(card, game);
Set<MageObjectReference> addedTokens = new HashSet<>();
int opponentCount = OpponentsCount.instance.calculate(game, source, this);
if (opponentCount < 1) {

View file

@ -13,9 +13,9 @@ import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.stream.Collectors;
@ -91,11 +91,10 @@ class EternalizeEffect extends OneShotEffect {
}
// create token and modify all attributes permanently (without game usage)
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.getColor().setColor(ObjectColor.BLACK);
Token token = CopyTokenFunction.createTokenCopy(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
token.setColor(ObjectColor.BLACK);
token.addSubType(SubType.ZOMBIE);
token.getManaCost().clear();
token.clearManaCost();
token.removePTCDA();
token.setPower(4);
token.setToughness(4);

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.common.SimpleStaticAbility;
@ -9,6 +10,7 @@ import mage.constants.*;
import mage.game.Game;
import mage.game.MageObjectAttribute;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.game.stack.Spell;
import mage.util.CardUtil;
@ -38,7 +40,7 @@ public class TransformAbility extends SimpleStaticAbility {
return "";
}
public static void transformPermanent(Permanent permanent, Card sourceCard, Game game, Ability source) {
public static void transformPermanent(Permanent permanent, MageObject sourceCard, Game game, Ability source) {
if (sourceCard == null) {
return;
}
@ -58,9 +60,10 @@ public class TransformAbility extends SimpleStaticAbility {
for (SuperType type : sourceCard.getSuperType()) {
permanent.addSuperType(type);
}
if (sourceCard instanceof Card) {
permanent.setExpansionSetCode(((Card) sourceCard).getExpansionSetCode());
}
CardUtil.copySetAndCardNumber(permanent, sourceCard);
permanent.getAbilities().clear();
for (Ability ability : sourceCard.getAbilities()) {
// source == null -- call from init card (e.g. own abilities)
@ -144,7 +147,12 @@ class TransformEffect extends ContinuousEffectImpl {
return true;
}
Card card = permanent.getSecondCardFace();
MageObject card;
if (permanent instanceof PermanentToken) {
card = ((PermanentToken) permanent).getToken().getBackFace();
} else {
card = permanent.getSecondCardFace();
}
if (card == null) {
return false;

View file

@ -18,11 +18,10 @@ import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.command.Emblem;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.custom.CreatureToken;
import mage.util.CardUtil;
import mage.util.RandomUtil;
import mage.util.functions.CopyTokenFunction;
import java.util.List;
@ -79,7 +78,7 @@ class MomirEffect extends OneShotEffect {
}
// search for a random non custom set creature
EmptyToken token = null;
Token token = null;
while (!options.isEmpty()) {
int index = RandomUtil.nextInt(options.size());
ExpansionSet expansionSet = Sets.findSet(options.get(index).getSetCode());
@ -88,8 +87,7 @@ class MomirEffect extends OneShotEffect {
} else {
Card card = options.get(index).getCard();
if (card != null) {
token = new EmptyToken();
CardUtil.copyTo(token).from(card, game);
token = CopyTokenFunction.createTokenCopy(card, game);
break;
} else {
options.remove(index);

View file

@ -609,7 +609,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return true;
}
private Card getOtherFace() {
protected MageObject getOtherFace() {
return transformed ? this.getMainCard() : this.getMainCard().getSecondCardFace();
}

View file

@ -5,6 +5,7 @@ import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.keyword.ChangelingAbility;
import mage.abilities.keyword.TransformAbility;
import mage.cards.Card;
import mage.constants.EmptyNames;
import mage.game.Game;
@ -28,6 +29,9 @@ public class PermanentToken extends PermanentImpl {
this.power = new MageInt(token.getPower().getModifiedBaseValue());
this.toughness = new MageInt(token.getToughness().getModifiedBaseValue());
this.copyFromToken(this.token, game, false); // needed to have at this time (e.g. for subtypes for entersTheBattlefield replacement effects)
if (this.token.isEntersTransformed()) {
TransformAbility.transformPermanent(this, this.token.getBackFace(), game, null);
}
// token's ZCC must be synced with original token to keep abilities settings
// Example: kicker ability and kicked status
@ -50,6 +54,14 @@ public class PermanentToken extends PermanentImpl {
this.toughness.resetToBaseValue();
}
@Override
public int getManaValue() {
if (this.isTransformed()) {
return token.getManaValue();
}
return super.getManaValue();
}
@Override
public String getName() {
if (name.isEmpty()) {
@ -121,6 +133,16 @@ public class PermanentToken extends PermanentImpl {
return this;
}
@Override
public boolean isTransformable() {
return token.getBackFace() != null;
}
@Override
protected MageObject getOtherFace() {
return this.transformed ? token : this.token.getBackFace();
}
@Override
public String getCardNumber() {
return token.getOriginalCardNumber();

View file

@ -1,4 +1,3 @@
package mage.game.permanent.token;
/**
@ -7,7 +6,14 @@ package mage.game.permanent.token;
public final class EmptyToken extends TokenImpl {
public EmptyToken() {
this(false);
}
public EmptyToken(boolean withBackFace) {
super(" Token", "");
if (withBackFace) {
this.backFace = new EmptyToken();
}
}
public EmptyToken(final EmptyToken token) {

View file

@ -1,5 +1,9 @@
package mage.game.permanent.token;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.TransformAbility;
import mage.constants.CardType;
import mage.constants.SubType;
@ -14,9 +18,12 @@ public final class IncubatorToken extends TokenImpl {
super("Incubator Token", "Incubator artifact token with \"{2}: Transform this artifact.\"");
cardType.add(CardType.ARTIFACT);
subtype.add(SubType.INCUBATOR);
this.backFace = new Phyrexian00Token();
// TODO: Implement this correctly
this.addAbility(new TransformAbility());
this.addAbility(new SimpleActivatedAbility(
new TransformSourceEffect().setText("transform this artifact"), new GenericManaCost(2)
));
availableImageSetCodes = Arrays.asList("MOM");
}

View file

@ -0,0 +1,33 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.Arrays;
/**
* @author TheElk801
*/
public class Phyrexian00Token extends TokenImpl {
Phyrexian00Token() {
super("Phyrexian Token", "0/0 Phyrexian artifact creature token");
cardType.add(CardType.ARTIFACT);
cardType.add(CardType.CREATURE);
subtype.add(SubType.PHYREXIAN);
power = new MageInt(0);
toughness = new MageInt(0);
availableImageSetCodes = Arrays.asList("MOM");
}
public Phyrexian00Token(final Phyrexian00Token token) {
super(token);
}
public Phyrexian00Token copy() {
return new Phyrexian00Token(this);
}
}

View file

@ -2,6 +2,7 @@
package mage.game.permanent.token;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.game.Game;
@ -58,4 +59,14 @@ public interface Token extends MageObject {
void setCopySourceCard(Card copySourceCard);
void setExpansionSetCodeForImage(String code);
Token getBackFace();
void setColor(ObjectColor color);
void clearManaCost();
void setEntersTransformed(boolean entersTransformed);
boolean isEntersTransformed();
}

View file

@ -3,6 +3,7 @@ package mage.game.permanent.token;
import mage.MageInt;
import mage.MageObject;
import mage.MageObjectImpl;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.Effect;
@ -12,9 +13,7 @@ import mage.cards.Card;
import mage.cards.repository.TokenInfo;
import mage.cards.repository.TokenRepository;
import mage.cards.repository.TokenType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.constants.*;
import mage.game.Game;
import mage.game.command.CommandObject;
import mage.game.events.CreateTokenEvent;
@ -45,21 +44,8 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
// list of set codes token images are available for
protected List<String> availableImageSetCodes = new ArrayList<>(); // TODO: delete
public enum Type {
FIRST(1),
SECOND(2);
int code;
Type(int code) {
this.code = code;
}
int getCode() {
return this.code;
}
}
protected Token backFace = null;
private boolean entersTransformed = false;
public TokenImpl() {
}
@ -69,7 +55,7 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
this.description = description;
}
public TokenImpl(final TokenImpl token) {
protected TokenImpl(final TokenImpl token) {
super(token);
this.description = token.description;
this.tokenType = token.tokenType;
@ -78,6 +64,8 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
this.originalExpansionSetCode = token.originalExpansionSetCode;
this.copySourceCard = token.copySourceCard; // will never be changed
this.availableImageSetCodes = token.availableImageSetCodes;
this.backFace = token.backFace != null ? token.backFace.copy() : null;
this.entersTransformed = token.entersTransformed;
}
@Override
@ -98,11 +86,17 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
ability.setSourceId(this.getId());
abilities.add(ability);
abilities.addAll(ability.getSubAbilities());
if (backFace != null) {
backFace.addAbility(ability);
}
}
// Directly from PermanentImpl
@Override
public void removeAbility(Ability abilityToRemove) {
if (backFace != null) {
backFace.removeAbility(abilityToRemove);
}
if (abilityToRemove == null) {
return;
}
@ -127,6 +121,9 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
}
abilitiesToRemove.forEach(a -> removeAbility(a));
if (backFace != null) {
backFace.removeAbilities(abilitiesToRemove);
}
}
@Override
@ -389,11 +386,17 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
@Override
public void setPower(int power) {
if (this.backFace != null) {
this.backFace.setPower(power);
}
this.power = new MageInt(power);
}
@Override
public void setToughness(int toughness) {
if (this.backFace != null) {
this.backFace.setToughness(toughness);
}
this.toughness = new MageInt(toughness);
}
@ -426,6 +429,21 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
return originalExpansionSetCode;
}
@Override
public void setStartingLoyalty(int startingLoyalty) {
if (backFace != null) {
backFace.setStartingLoyalty(startingLoyalty);
}
super.setStartingLoyalty(startingLoyalty);
}
public void setStartingDefense(int intArg) {
if (backFace != null) {
backFace.setStartingDefense(intArg);
}
super.setStartingDefense(intArg);
}
@Override
public void setOriginalExpansionSetCode(String originalExpansionSetCode) {
// TODO: delete
@ -451,4 +469,167 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
// TODO: delete
setOriginalExpansionSetCode(code);
}
@Override
public Token getBackFace() {
return backFace;
}
public void retainAllArtifactSubTypes(Game game) {
if (backFace != null) {
backFace.retainAllArtifactSubTypes(game);
}
super.retainAllArtifactSubTypes(game);
}
public void retainAllEnchantmentSubTypes(Game game) {
if (backFace != null) {
backFace.retainAllEnchantmentSubTypes(game);
}
super.retainAllEnchantmentSubTypes(game);
}
public void addSuperType(SuperType superType) {
if (backFace != null) {
backFace.addSuperType(superType);
}
super.addSuperType(superType);
}
public void removeSuperType(SuperType superType) {
if (backFace != null) {
backFace.removeSuperType(superType);
}
super.removeSuperType(superType);
}
public void addCardType(CardType... cardTypes) {
if (backFace != null) {
backFace.addCardType(cardTypes);
}
super.addCardType(cardTypes);
}
public void removeCardType(CardType... cardTypes) {
if (backFace != null) {
backFace.removeCardType(cardTypes);
}
super.removeCardType(cardTypes);
}
public void removeAllCardTypes() {
if (backFace != null) {
backFace.removeAllCardTypes();
}
super.removeAllCardTypes();
}
public void removeAllCardTypes(Game game) {
if (backFace != null) {
backFace.removeAllCardTypes(game);
}
super.removeAllCardTypes(game);
}
public void addSubType(SubType... subTypes) {
if (backFace != null) {
backFace.addSubType(subTypes);
}
super.addSubType(subTypes);
}
public void removeAllSubTypes(Game game, SubTypeSet subTypeSet) {
if (backFace != null) {
backFace.removeAllSubTypes(game, subTypeSet);
}
super.removeAllSubTypes(game, subTypeSet);
}
public void removeAllSubTypes(Game game) {
if (backFace != null) {
backFace.removeAllSubTypes(game);
}
super.removeAllSubTypes(game);
}
public void retainAllLandSubTypes(Game game) {
if (backFace != null) {
backFace.retainAllLandSubTypes(game);
}
super.retainAllLandSubTypes(game);
}
public void removeAllCreatureTypes(Game game) {
if (backFace != null) {
backFace.removeAllCreatureTypes(game);
}
super.removeAllCreatureTypes(game);
}
public void removeAllCreatureTypes() {
if (backFace != null) {
backFace.removeAllCreatureTypes();
}
super.removeAllCreatureTypes();
}
public void removeSubType(Game game, SubType subType) {
if (backFace != null) {
backFace.removeSubType(game, subType);
}
super.removeSubType(game, subType);
}
public void setIsAllCreatureTypes(boolean value) {
if (backFace != null) {
backFace.setIsAllCreatureTypes(value);
}
super.setIsAllCreatureTypes(value);
}
public void removePTCDA() {
if (backFace != null) {
backFace.removePTCDA();
}
super.removePTCDA();
}
public String getName() {
if (backFace != null) {
backFace.getName();
}
return super.getName();
}
public void setName(String name) {
if (backFace != null) {
backFace.setName(name);
}
super.setName(name);
}
public void setColor(ObjectColor color) {
if (backFace != null) {
backFace.setColor(color);
}
this.getColor().setColor(color);
}
@Override
public void clearManaCost() {
if (backFace != null) {
backFace.clearManaCost();
}
this.getManaCost().clear();
}
@Override
public void setEntersTransformed(boolean entersTransformed) {
this.entersTransformed = entersTransformed;
}
@Override
public boolean isEntersTransformed() {
return this.entersTransformed && this.backFace != null;
}
}

View file

@ -25,12 +25,13 @@ import mage.game.events.CopiedStackObjectEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.util.ManaUtil;
import mage.util.SubTypes;
import mage.util.functions.CopyTokenFunction;
import mage.util.functions.StackObjectCopyApplier;
import org.apache.log4j.Logger;
@ -275,8 +276,7 @@ public class Spell extends StackObjectImpl implements Card {
UUID permId;
boolean flag;
if (isCopy()) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game, this);
Token token = CopyTokenFunction.createTokenCopy(card, game, this);
// The token that a resolving copy of a spell becomes isnt said to have been created. (2020-09-25)
if (token.putOntoBattlefield(1, game, ability, getControllerId(), false, false, null, false)) {
permId = token.getLastAddedTokenIds().stream().findFirst().orElse(null);
@ -343,8 +343,7 @@ public class Spell extends StackObjectImpl implements Card {
return false;
}
} else if (isCopy()) {
EmptyToken token = new EmptyToken();
CardUtil.copyTo(token).from(card, game, this);
Token token = CopyTokenFunction.createTokenCopy(card, game, this);
// The token that a resolving copy of a spell becomes isnt said to have been created. (2020-09-25)
token.putOntoBattlefield(1, game, ability, getControllerId(), false, false, null, false);
return true;

View file

@ -42,7 +42,6 @@ import mage.players.Player;
import mage.target.Target;
import mage.target.TargetCard;
import mage.target.targetpointer.FixedTarget;
import mage.util.functions.CopyTokenFunction;
import org.apache.log4j.Logger;
import java.io.UnsupportedEncodingException;
@ -474,17 +473,6 @@ public final class CardUtil {
spellAbility.getManaCostsToPay().addAll(adjustedCost);
}
/**
* Returns function that copies params\abilities from one card to
* {@link Token}.
*
* @param target
* @return
*/
public static CopyTokenFunction copyTo(Token target) {
return new CopyTokenFunction(target);
}
/**
* Converts an integer number to string Numbers > 20 will be returned as
* digits
@ -1821,6 +1809,9 @@ public final class CardUtil {
needSetCode = ((Card) copyFromObject).getExpansionSetCode();
needCardNumber = ((Card) copyFromObject).getCardNumber();
needTokenType = 0;
} else if (copyFromObject instanceof Token) {
// TODO: make this work
return;
} else {
throw new IllegalStateException("Unsupported copyFromObject class: " + copyFromObject.getClass().getSimpleName());
}

View file

@ -7,64 +7,95 @@ import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.SuperType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.PermanentToken;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.game.stack.Spell;
/**
* @author nantuko
*/
public class CopyTokenFunction implements Function<Token, Card> {
public class CopyTokenFunction {
protected Token target;
protected final Token target;
public CopyTokenFunction(Token target) {
private CopyTokenFunction(Token target) {
if (target == null) {
throw new IllegalArgumentException("Target can't be null");
}
this.target = target;
}
@Override
public Token apply(Card source, Game game) {
public static Token createTokenCopy(Card source, Game game) {
return createTokenCopy(source, game, null);
}
public static Token createTokenCopy(Card source, Game game, Spell spell) {
return new CopyTokenFunction(new EmptyToken(source.isTransformable())).from(source, game, spell);
}
public void apply(Card source, Game game) {
if (target == null) {
throw new IllegalArgumentException("Target can't be null");
}
// A copy contains only the attributes of the basic card or basic Token that's the base of the permanent
// else gained abililies would be copied too.
MageObject sourceObj = source;
target.setEntersTransformed(source instanceof Permanent && ((Permanent) source).isTransformed());
MageObject sourceObj;
if (source instanceof PermanentToken) {
// create token from another token
sourceObj = ((PermanentToken) source).getToken();
Token sourceToken = ((PermanentToken) source).getToken();
sourceObj = sourceToken;
copyToToken(target, sourceObj, game);
if (sourceToken.getBackFace() != null) {
target.getBackFace().setOriginalExpansionSetCode(sourceToken.getOriginalExpansionSetCode());
target.getBackFace().setOriginalCardNumber(sourceToken.getOriginalCardNumber());
target.getBackFace().setCopySourceCard(sourceToken.getBackFace().getCopySourceCard());
copyToToken(target.getBackFace(), sourceToken.getBackFace(), game);
}
// to show the source image, the original values have to be used
target.setOriginalExpansionSetCode(((Token) sourceObj).getOriginalExpansionSetCode());
target.setOriginalCardNumber(((Token) sourceObj).getOriginalCardNumber());
target.setOriginalExpansionSetCode(sourceToken.getOriginalExpansionSetCode());
target.setOriginalCardNumber(sourceToken.getOriginalCardNumber());
target.setCopySourceCard(((PermanentToken) source).getToken().getCopySourceCard());
} else if (source instanceof PermanentCard) {
return;
}
if (source instanceof PermanentCard) {
// create token from non-token permanent
if (((PermanentCard) source).isMorphed() || ((PermanentCard) source).isManifested()) {
MorphAbility.setPermanentToFaceDownCreature(target, game);
return target;
} else {
if (((PermanentCard) source).isTransformed() && source.getSecondCardFace() != null) {
sourceObj = ((PermanentCard) source).getSecondCardFace();
} else {
sourceObj = ((PermanentCard) source).getCard();
}
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
target.setCopySourceCard((Card) sourceObj);
return;
}
} else {
// create token from non-permanent object like card (example: Embalm ability)
sourceObj = ((PermanentCard) source).getMainCard();
copyToToken(target, sourceObj, game);
if (((Card) sourceObj).isTransformable()) {
target.getBackFace().setOriginalExpansionSetCode(source.getExpansionSetCode());
target.getBackFace().setOriginalCardNumber(source.getCardNumber());
target.getBackFace().setCopySourceCard(((Card) sourceObj).getSecondCardFace());
copyToToken(target.getBackFace(), ((Card) sourceObj).getSecondCardFace(), game);
}
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
target.setCopySourceCard(source);
target.setCopySourceCard((Card) sourceObj);
return;
}
sourceObj = source;
copyToToken(target, sourceObj, game);
if (source.isTransformable()) {
target.getBackFace().setOriginalExpansionSetCode(source.getExpansionSetCode());
target.getBackFace().setOriginalCardNumber(source.getCardNumber());
target.getBackFace().setCopySourceCard(source.getSecondCardFace());
copyToToken(target.getBackFace(), source.getSecondCardFace(), game);
}
// create token from non-permanent object like card (example: Embalm ability)
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
target.setCopySourceCard(source);
}
private static Token copyToToken(Token target, MageObject sourceObj, Game game) {
// modify all attributes permanently (without game usage)
target.setName(sourceObj.getName());
target.getColor().setColor(sourceObj.getColor());
@ -101,11 +132,7 @@ public class CopyTokenFunction implements Function<Token, Card> {
return target;
}
public Token from(Card source, Game game) {
return from(source, game, null);
}
public Token from(Card source, Game game, Spell spell) {
private Token from(Card source, Game game, Spell spell) {
apply(source, game);
// token's ZCC must be synced with original card to keep abilities settings

View file

@ -1,11 +0,0 @@
package mage.util.functions;
import mage.game.Game;
/**
* @author nantuko
*/
@FunctionalInterface
public interface Function<X, Y> {
X apply(Y in, Game game);
}