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

@ -111,32 +111,36 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)
.setCostAdjuster(costAdjuster)
);
}
switch (faceDownType) {
case MORPHED:
case MEGAMORPHED:
switch (faceDownType) {
case MORPHED:
case MEGAMORPHED:
if (turnFaceUpCosts != null) {
// face up rules replace for cost hide
this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect(
"Turn it face up any time for its morph cost."
)));
break;
case DISGUISED:
case CLOAKED:
// ward
this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}")));
}
break;
case DISGUISED:
case CLOAKED:
// Ward {2} -- should not be dependent on turnFaceUpCosts.
this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}")));
if (turnFaceUpCosts != null) {
// face up rules replace for cost hide
this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect(
"Turn it face up any time for its disguise/cloaked cost."
)));
break;
case MANUAL:
case MANIFESTED:
// no face up abilities
break;
default:
throw new IllegalArgumentException("Un-supported face down type: " + faceDownType);
}
}
break;
case MANUAL:
case MANIFESTED:
// no face up abilities
break;
default:
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";
@ -209,6 +213,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
case DISGUISED:
permanent.setDisguised(true);
break;
case CLOAKED:
permanent.setCloaked(true);
break;
default:
throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType);
}
@ -228,6 +235,8 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
return BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED;
} else if (permanent.isManifested()) {
return BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED;
} else if (permanent.isCloaked()) {
return BecomesFaceDownCreatureEffect.FaceDownType.CLOAKED;
} else if (permanent.isFaceDown(game)) {
return BecomesFaceDownCreatureEffect.FaceDownType.MANUAL;
} else {
@ -326,7 +335,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST;
break;
case CLOAKED:
tokenName = "TODO-CLOAKED";
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_CLOAK;
break;
case 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.util.CardUtil;
import java.util.Set;
import java.util.*;
/**
* Manifest
@ -65,6 +65,7 @@ public class ManifestEffect extends OneShotEffect {
private final DynamicValue amount;
private final boolean isPlural;
private final boolean cloakNotManifest;
public ManifestEffect(int amount) {
this(StaticValue.get(amount), amount > 1);
@ -75,9 +76,14 @@ public class ManifestEffect extends OneShotEffect {
}
private ManifestEffect(DynamicValue amount, boolean isPlural) {
this(amount, isPlural, false);
}
private ManifestEffect(DynamicValue amount, boolean isPlural, boolean cloakNotManifest) {
super(Outcome.PutCreatureInPlay);
this.amount = amount;
this.isPlural = isPlural;
this.cloakNotManifest = cloakNotManifest;
this.staticText = setText();
}
@ -85,6 +91,7 @@ public class ManifestEffect extends OneShotEffect {
super(effect);
this.amount = effect.amount;
this.isPlural = effect.isPlural;
this.cloakNotManifest = effect.cloakNotManifest;
}
@Override
@ -100,12 +107,16 @@ public class ManifestEffect extends OneShotEffect {
}
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()) {
return false;
return Collections.emptyList();
}
// prepare source ability
@ -132,9 +143,10 @@ public class ManifestEffect extends OneShotEffect {
// zcc + 1 for use case with Rally the Ancestors (see related test)
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
// 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);
@ -145,18 +157,25 @@ public class ManifestEffect extends OneShotEffect {
if (permanent != null) {
// 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)
permanent.setManifested(true);
if (cloakNotManifest) {
permanent.setCloaked(true);
} else {
permanent.setManifested(true);
}
manifested.add(permanent);
} else {
// 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
}
}
return true;
return manifested;
}
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) {
sb.append(CardUtil.numberToText(amount.toString())).append(" cards ");
} else {

View file

@ -40,7 +40,7 @@ public class ManifestTargetPlayerEffect extends OneShotEffect {
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() {

View file

@ -23,6 +23,7 @@ public enum TokenRepository {
// - 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_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_DISGUISE = "Disguise";
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
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
// 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"));

View file

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

View file

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

View file

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