implement [MKM] Cryptic Coat (#12164) and Cloak ability

This commit is contained in:
Susucre 2024-06-06 12:47:07 +02:00 committed by GitHub
parent 8172616449
commit ab280ad2ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 235 additions and 29 deletions

View file

@ -33,6 +33,7 @@ public class PermanentView extends CardView {
private final boolean morphed; private final boolean morphed;
private final boolean disguised; private final boolean disguised;
private final boolean manifested; private final boolean manifested;
private final boolean cloaked;
private final boolean attachedToPermanent; private final boolean attachedToPermanent;
// If this card is attached to a permanent which is controlled by a player other than the one which controls this permanent // If this card is attached to a permanent which is controlled by a player other than the one which controls this permanent
private final boolean attachedControllerDiffers; private final boolean attachedControllerDiffers;
@ -47,6 +48,7 @@ public class PermanentView extends CardView {
this.morphed = permanent.isMorphed(); this.morphed = permanent.isMorphed();
this.disguised = permanent.isDisguised(); this.disguised = permanent.isDisguised();
this.manifested = permanent.isManifested(); this.manifested = permanent.isManifested();
this.cloaked = permanent.isCloaked();
this.damage = permanent.getDamage(); this.damage = permanent.getDamage();
this.attachments = new ArrayList<>(permanent.getAttachments()); this.attachments = new ArrayList<>(permanent.getAttachments());
this.attachedTo = permanent.getAttachedTo(); this.attachedTo = permanent.getAttachedTo();
@ -143,6 +145,7 @@ public class PermanentView extends CardView {
this.morphed = permanentView.morphed; this.morphed = permanentView.morphed;
this.disguised = permanentView.disguised; this.disguised = permanentView.disguised;
this.manifested = permanentView.manifested; this.manifested = permanentView.manifested;
this.cloaked = permanentView.cloaked;
this.attachedToPermanent = permanentView.attachedToPermanent; this.attachedToPermanent = permanentView.attachedToPermanent;
this.attachedControllerDiffers = permanentView.attachedControllerDiffers; this.attachedControllerDiffers = permanentView.attachedControllerDiffers;
} }
@ -222,4 +225,8 @@ public class PermanentView extends CardView {
public boolean isManifested() { public boolean isManifested() {
return manifested; return manifested;
} }
public boolean isCloaked() {
return cloaked;
}
} }

View file

@ -0,0 +1,97 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.abilities.effects.common.combat.CantBeBlockedAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.keyword.ManifestEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.List;
import java.util.UUID;
/**
* @author Susucr
*/
public final class CrypticCoat extends CardImpl {
public CrypticCoat(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}");
this.subtype.add(SubType.EQUIPMENT);
// When Cryptic Coat enters the battlefield, cloak the top card of your library, then attach Cryptic Coat to it.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CrypticCoatEffect()));
// Equipped creature gets +1/+0 and can't be blocked.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0));
ability.addEffect(new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT)
.setText("and can't be blocked")
);
this.addAbility(ability);
// {1}{U}: Return Cryptic Coat to its owner's hand.
this.addAbility(new SimpleActivatedAbility(new ReturnToHandSourceEffect(), new ManaCostsImpl<>("{1}{U}")));
}
private CrypticCoat(final CrypticCoat card) {
super(card);
}
@Override
public CrypticCoat copy() {
return new CrypticCoat(this);
}
}
class CrypticCoatEffect extends OneShotEffect {
CrypticCoatEffect() {
super(Outcome.PutCreatureInPlay);
staticText = "cloak the top card of your library, then attach {this} to it";
}
private CrypticCoatEffect(final CrypticCoatEffect effect) {
super(effect);
}
@Override
public CrypticCoatEffect copy() {
return new CrypticCoatEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
List<Permanent> cloakedList = ManifestEffect.doManifestCards(
game, source, controller,
controller.getLibrary().getTopCards(game, 1), true
);
if (cloakedList.isEmpty()) {
return false;
}
Permanent cloaked = cloakedList.get(0);
new AttachEffect(Outcome.BoostCreature, "attach {this} to it")
.setTargetPointer(new FixedTarget(cloaked, game))
.apply(game, source);
return true;
}
}

View file

@ -79,6 +79,6 @@ class OrochiSoulReaverManifestEffect extends OneShotEffect {
return false; return false;
} }
return ManifestEffect.doManifestCards(game, source, controller, targetPlayer.getLibrary().getTopCards(game, 1)); return !ManifestEffect.doManifestCards(game, source, controller, targetPlayer.getLibrary().getTopCards(game, 1)).isEmpty();
} }
} }

View file

@ -67,6 +67,6 @@ class ScrollOfFateEffect extends OneShotEffect {
return false; return false;
} }
return ManifestEffect.doManifestCards(game, source, controller, new CardsImpl(targetCard.getTargets()).getCards(game)); return !ManifestEffect.doManifestCards(game, source, controller, new CardsImpl(targetCard.getTargets()).getCards(game)).isEmpty();
} }
} }

View file

@ -86,7 +86,7 @@ class ThievingAmalgamManifestEffect extends OneShotEffect {
return false; return false;
} }
return ManifestEffect.doManifestCards(game, source, controller, targetPlayer.getLibrary().getTopCards(game, 1)); return !ManifestEffect.doManifestCards(game, source, controller, targetPlayer.getLibrary().getTopCards(game, 1)).isEmpty();
} }
} }

View file

@ -162,6 +162,7 @@ class VesuvanShapeshifterFaceDownEffect extends OneShotEffect {
permanent.turnFaceDown(source, game, source.getControllerId()); permanent.turnFaceDown(source, game, source.getControllerId());
permanent.setManifested(false); permanent.setManifested(false);
permanent.setDisguised(false); permanent.setDisguised(false);
permanent.setCloaked(false);
permanent.setMorphed(true); // cause it morph card TODO: smells bad permanent.setMorphed(true); // cause it morph card TODO: smells bad
return permanent.isFaceDown(game); return permanent.isFaceDown(game);

View file

@ -92,6 +92,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet {
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));
cards.add(new SetCardInfo("Cryptex", 251, Rarity.RARE, mage.cards.c.Cryptex.class)); cards.add(new SetCardInfo("Cryptex", 251, Rarity.RARE, mage.cards.c.Cryptex.class));
cards.add(new SetCardInfo("Cryptic Coat", 50, Rarity.RARE, mage.cards.c.CrypticCoat.class));
cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class)); cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class));
cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class)); cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class));
cards.add(new SetCardInfo("Curious Inquiry", 51, Rarity.UNCOMMON, mage.cards.c.CuriousInquiry.class)); cards.add(new SetCardInfo("Curious Inquiry", 51, Rarity.UNCOMMON, mage.cards.c.CuriousInquiry.class));

View file

@ -0,0 +1,50 @@
package org.mage.test.cards.single.mkm;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class CrypticCoatTest extends CardTestPlayerBase {
/**
* {@link mage.cards.c.CrypticCoat Cryptic Coat} {2}{U}
* Artifact Equipment
* When Cryptic Coat enters the battlefield, cloak the top card of your library, then attach Cryptic Coat to it. (To cloak a card, put it onto the battlefield face down as a 2/2 creature with ward {2}. Turn it face up any time for its mana cost if its a creature card.)
* Equipped creature gets +1/+0 and cant be blocked.
* {1}{U}: Return Cryptic Coat to its owners hand.
*/
private static final String coat = "Cryptic Coat";
@Test
public void test_CloakCreature() {
skipInitShuffling();
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
addCard(Zone.HAND, playerA, coat);
addCard(Zone.LIBRARY, playerA, "Ancient Crab");
addCard(Zone.HAND, playerB, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, coat);
checkPT("Cloaked is 3/2", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 3, 2);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString());
setChoice(playerB, true); // pay for ward
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}{U}: Turn this face-down permanent face up.");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertPowerToughness(playerA, "Ancient Crab", 1 + 1, 5);
assertDamageReceived(playerA, "Ancient Crab", 3);
assertTappedCount("Mountain", true, 3);
assertTappedCount("Island", true, 6);
}
}

View file

@ -111,24 +111,29 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED) new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)
.setCostAdjuster(costAdjuster) .setCostAdjuster(costAdjuster)
); );
}
switch (faceDownType) { switch (faceDownType) {
case MORPHED: case MORPHED:
case MEGAMORPHED: case MEGAMORPHED:
if (turnFaceUpCosts != null) {
// face up rules replace for cost hide // face up rules replace for cost hide
this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect( this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect(
"Turn it face up any time for its morph cost." "Turn it face up any time for its morph cost."
))); )));
}
break; break;
case DISGUISED: case DISGUISED:
case CLOAKED: case CLOAKED:
// ward // Ward {2} -- should not be dependent on turnFaceUpCosts.
this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}"))); this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}")));
if (turnFaceUpCosts != null) {
// face up rules replace for cost hide // face up rules replace for cost hide
this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect( this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect(
"Turn it face up any time for its disguise/cloaked cost." "Turn it face up any time for its disguise/cloaked cost."
))); )));
}
break; break;
case MANUAL: case MANUAL:
case MANIFESTED: case MANIFESTED:
@ -137,7 +142,6 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
default: default:
throw new IllegalArgumentException("Un-supported face down type: " + faceDownType); throw new IllegalArgumentException("Un-supported face down type: " + faceDownType);
} }
}
staticText = "{this} becomes a 2/2 face-down creature, with no text, no name, no subtypes, and no mana cost"; staticText = "{this} becomes a 2/2 face-down creature, with no text, no name, no subtypes, and no mana cost";
foundPermanent = false; foundPermanent = false;
@ -209,6 +213,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
case DISGUISED: case DISGUISED:
permanent.setDisguised(true); permanent.setDisguised(true);
break; break;
case CLOAKED:
permanent.setCloaked(true);
break;
default: default:
throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType); throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType);
} }
@ -228,6 +235,8 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
return BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED; return BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED;
} else if (permanent.isManifested()) { } else if (permanent.isManifested()) {
return BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED; return BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED;
} else if (permanent.isCloaked()) {
return BecomesFaceDownCreatureEffect.FaceDownType.CLOAKED;
} else if (permanent.isFaceDown(game)) { } else if (permanent.isFaceDown(game)) {
return BecomesFaceDownCreatureEffect.FaceDownType.MANUAL; return BecomesFaceDownCreatureEffect.FaceDownType.MANUAL;
} else { } else {
@ -326,7 +335,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST; tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST;
break; break;
case CLOAKED: case CLOAKED:
tokenName = "TODO-CLOAKED"; tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_CLOAK;
break; break;
case MANUAL: case MANUAL:
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL; tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL;

View file

@ -18,7 +18,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.Set; import java.util.*;
/** /**
* Manifest * Manifest
@ -65,6 +65,7 @@ public class ManifestEffect extends OneShotEffect {
private final DynamicValue amount; private final DynamicValue amount;
private final boolean isPlural; private final boolean isPlural;
private final boolean cloakNotManifest;
public ManifestEffect(int amount) { public ManifestEffect(int amount) {
this(StaticValue.get(amount), amount > 1); this(StaticValue.get(amount), amount > 1);
@ -75,9 +76,14 @@ public class ManifestEffect extends OneShotEffect {
} }
private ManifestEffect(DynamicValue amount, boolean isPlural) { private ManifestEffect(DynamicValue amount, boolean isPlural) {
this(amount, isPlural, false);
}
private ManifestEffect(DynamicValue amount, boolean isPlural, boolean cloakNotManifest) {
super(Outcome.PutCreatureInPlay); super(Outcome.PutCreatureInPlay);
this.amount = amount; this.amount = amount;
this.isPlural = isPlural; this.isPlural = isPlural;
this.cloakNotManifest = cloakNotManifest;
this.staticText = setText(); this.staticText = setText();
} }
@ -85,6 +91,7 @@ public class ManifestEffect extends OneShotEffect {
super(effect); super(effect);
this.amount = effect.amount; this.amount = effect.amount;
this.isPlural = effect.isPlural; this.isPlural = effect.isPlural;
this.cloakNotManifest = effect.cloakNotManifest;
} }
@Override @Override
@ -100,12 +107,16 @@ public class ManifestEffect extends OneShotEffect {
} }
int manifestAmount = amount.calculate(game, source, this); int manifestAmount = amount.calculate(game, source, this);
return doManifestCards(game, source, controller, controller.getLibrary().getTopCards(game, manifestAmount)); return !doManifestCards(game, source, controller, controller.getLibrary().getTopCards(game, manifestAmount), cloakNotManifest).isEmpty();
} }
public static boolean doManifestCards(Game game, Ability source, Player manifestPlayer, Set<Card> cardsToManifest) { public static List<Permanent> doManifestCards(Game game, Ability source, Player manifestPlayer, Set<Card> cardsToManifest) {
return doManifestCards(game, source, manifestPlayer, cardsToManifest, false);
}
public static List<Permanent> doManifestCards(Game game, Ability source, Player manifestPlayer, Set<Card> cardsToManifest, boolean cloakNotManifest) {
if (cardsToManifest.isEmpty()) { if (cardsToManifest.isEmpty()) {
return false; return Collections.emptyList();
} }
// prepare source ability // prepare source ability
@ -132,9 +143,10 @@ public class ManifestEffect extends OneShotEffect {
// zcc + 1 for use case with Rally the Ancestors (see related test) // zcc + 1 for use case with Rally the Ancestors (see related test)
MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game); MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game);
game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, cloakNotManifest ? FaceDownType.CLOAKED : FaceDownType.MANIFESTED), newSource);
} }
List<Permanent> manifested = new ArrayList<>();
// move cards to battlefield as face down // move cards to battlefield as face down
// TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate) // TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate)
manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null); manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null);
@ -145,18 +157,25 @@ public class ManifestEffect extends OneShotEffect {
if (permanent != null) { if (permanent != null) {
// TODO: permanent already has manifested status, so code can be deleted later // TODO: permanent already has manifested status, so code can be deleted later
// TODO: add test with battlefield trigger/watcher (must not see normal card, must not see face down status without manifest) // TODO: add test with battlefield trigger/watcher (must not see normal card, must not see face down status without manifest)
if (cloakNotManifest) {
permanent.setCloaked(true);
} else {
permanent.setManifested(true); permanent.setManifested(true);
}
manifested.add(permanent);
} else { } else {
// TODO: looks buggy, card can't be moved to battlefield, but face down effect already active // TODO: looks buggy, card can't be moved to battlefield, but face down effect already active
// or it can be face down on another move to battlefield // or it can be face down on another move to battlefield
} }
} }
return true; return manifested;
} }
private String setText() { private String setText() {
StringBuilder sb = new StringBuilder("manifest the top "); StringBuilder sb = new StringBuilder();
sb.append(cloakNotManifest ? "cloak" : "manifest");
sb.append(" the top ");
if (isPlural) { if (isPlural) {
sb.append(CardUtil.numberToText(amount.toString())).append(" cards "); sb.append(CardUtil.numberToText(amount.toString())).append(" cards ");
} else { } else {

View file

@ -40,7 +40,7 @@ public class ManifestTargetPlayerEffect extends OneShotEffect {
return false; return false;
} }
return ManifestEffect.doManifestCards(game, source, targetPlayer, targetPlayer.getLibrary().getTopCards(game, amount)); return !ManifestEffect.doManifestCards(game, source, targetPlayer, targetPlayer.getLibrary().getTopCards(game, amount)).isEmpty();
} }
private String setText() { private String setText() {

View file

@ -23,6 +23,7 @@ public enum TokenRepository {
// - additional card name for controller like "Morph: face up name" // - additional card name for controller like "Morph: face up name"
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL = "Face Down"; public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL = "Face Down";
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST = "Manifest"; public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST = "Manifest";
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_CLOAK = "Cloak";
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MORPH = "Morph"; public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MORPH = "Morph";
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE = "Disguise"; public static final String XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE = "Disguise";
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL = "Foretell"; public static final String XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL = "Foretell";
@ -287,6 +288,10 @@ public enum TokenRepository {
// support only 1 image: https://scryfall.com/card/tmkm/21/a-mysterious-creature // support only 1 image: https://scryfall.com/card/tmkm/21/a-mysterious-creature
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE, 1, "https://api.scryfall.com/cards/tmkm/21/en?format=image")); res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE, 1, "https://api.scryfall.com/cards/tmkm/21/en?format=image"));
// Cloak
// support only 1 image: https://scryfall.com/card/tmkm/21/a-mysterious-creature
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_CLOAK, 1, "https://api.scryfall.com/cards/tmkm/21/en?format=image"));
// Foretell // Foretell
// https://scryfall.com/search?q=Foretell+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name // https://scryfall.com/search?q=Foretell+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, 1, "https://api.scryfall.com/cards/tkhm/23/en?format=image")); res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, 1, "https://api.scryfall.com/cards/tkhm/23/en?format=image"));

View file

@ -450,6 +450,10 @@ public interface Permanent extends Card, Controllable {
boolean isManifested(); boolean isManifested();
void setCloaked(boolean value);
boolean isCloaked();
boolean isRingBearer(); boolean isRingBearer();
void setRingBearer(Game game, boolean value); void setRingBearer(Game game, boolean value);

View file

@ -188,6 +188,7 @@ public class PermanentCard extends PermanentImpl {
setManifested(false); setManifested(false);
setMorphed(false); setMorphed(false);
setDisguised(false); setDisguised(false);
setCloaked(false);
return true; return true;
} }
return false; return false;

View file

@ -73,6 +73,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected boolean renowned; protected boolean renowned;
protected boolean suspected; protected boolean suspected;
protected boolean manifested = false; protected boolean manifested = false;
protected boolean cloaked = false;
protected boolean morphed = false; protected boolean morphed = false;
protected boolean disguised = false; protected boolean disguised = false;
protected boolean ringBearerFlag = false; protected boolean ringBearerFlag = false;
@ -189,6 +190,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.morphed = permanent.morphed; this.morphed = permanent.morphed;
this.disguised = permanent.disguised; this.disguised = permanent.disguised;
this.manifested = permanent.manifested; this.manifested = permanent.manifested;
this.cloaked = permanent.cloaked;
this.createOrder = permanent.createOrder; this.createOrder = permanent.createOrder;
this.prototyped = permanent.prototyped; this.prototyped = permanent.prototyped;
this.canBeSacrificed = permanent.canBeSacrificed; this.canBeSacrificed = permanent.canBeSacrificed;
@ -1905,6 +1907,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
manifested = value; manifested = value;
} }
@Override
public boolean isCloaked() {
return cloaked;
}
@Override
public void setCloaked(boolean value) {
cloaked = value;
}
@Override @Override
public boolean isMorphed() { public boolean isMorphed() {
return morphed; return morphed;