game: improved visible rules of face down cards, removed visible face up cost (part of #10653, #11884)

This commit is contained in:
Oleg Agafonov 2024-03-01 16:47:42 +04:00
parent 9ea3356b77
commit 55f1d36695
10 changed files with 128 additions and 58 deletions

View file

@ -29,8 +29,8 @@ public class TurnFaceUpAbility extends SpecialAction {
this.addCost(costs);
this.usesStack = false;
this.abilityType = AbilityType.SPECIAL_ACTION;
this.setRuleVisible(false); // will be made visible only to controller in CardView
this.setWorksFaceDown(true);
this.setRuleVisible(false); // hide in face up, but show in face down view (it will be enabled as default ability)
}
protected TurnFaceUpAbility(final TurnFaceUpAbility ability) {

View file

@ -15,6 +15,8 @@ import mage.game.permanent.Permanent;
import java.util.*;
/**
* TODO: must be reworked to use same face down logic as BecomesFaceDownCreatureEffect
*
* @author LevelX2
*/

View file

@ -4,12 +4,14 @@ import mage.MageObject;
import mage.MageObjectReference;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.TurnFaceUpAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.keyword.WardAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -29,6 +31,7 @@ import java.util.UUID;
/**
* Support different face down types: morph/manifest and disguise/cloak
*
* <p>
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
* no subtypes, and no mana cost, if it's face down on the battlefield. And it
@ -81,15 +84,36 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
this.objectReference = objectReference;
this.zoneChangeCounter = Integer.MIN_VALUE;
// additional abilities
// face up
// add additional face up and information abilities
if (turnFaceUpCosts != null) {
// face up for all
this.additionalAbilities.add(new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED));
}
// ward
if (faceDownType == FaceDownType.DISGUISED
|| faceDownType == faceDownType.CLOAKED) {
this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}")));
switch (faceDownType) {
case MORPHED:
case MEGAMORPHED:
// 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}")));
// 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);
}
}
staticText = "{this} becomes a 2/2 face-down creature, with no text, no name, no subtypes, and no mana cost";
@ -216,16 +240,17 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
// If a card with morph is manifested, its controller may turn that card face up using
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
// or the procedure described above to turn a manifested permanent face up.
//
// so keep all tune face up abilities and other face down compatible
// keep face down abilities active, but hide it from rules description
if (ability.getWorksFaceDown()) {
// keep face down abilities active, but hide it from rules description
// example: When Dog Walker is turned face up, create two tapped 1/1 white Dog creature tokens
ability.setRuleVisible(false);
// but do not hide default ability (becomes a 2/2 face-down creature)
if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
// becomes a 2/2 face-down creature - it hides a real ability too, but adds fake rule, see
if (!ability.getEffects().isEmpty()) {
if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) {
// enable for stack
ability.setRuleVisible(true);
}
}
@ -236,7 +261,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
abilitiesToRemove.add(ability);
}
// add additional abilities like face up
// add additional abilities like face up (real ability hidden and duplicated with information without cost data)
if (object instanceof Permanent) {
// as permanent
Permanent permanentObject = (Permanent) object;
@ -244,7 +269,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
if (additionalAbilities != null) {
additionalAbilities.forEach(blueprintAbility -> {
Ability newAbility = blueprintAbility.copy();
newAbility.setRuleVisible(true);
newAbility.setRuleVisible(CardUtil.isInformationAbility(newAbility));
permanentObject.addAbility(newAbility, sourceId, game);
});
}
@ -255,7 +280,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
if (additionalAbilities != null) {
additionalAbilities.forEach(blueprintAbility -> {
Ability newAbility = blueprintAbility.copy();
newAbility.setRuleVisible(true);
newAbility.setRuleVisible(CardUtil.isInformationAbility(newAbility));
cardObject.addAbility(newAbility);
});
}

View file

@ -100,9 +100,9 @@ public class DisguiseAbility extends SpellAbility {
@Override
public String getRule() {
boolean isMana = disguiseCosts.get(0) instanceof ManaCost;
String costInfo = this.disguiseCosts.getText() + (isMana ? " " : ". ");
return ABILITY_KEYWORD + (isMana ? " " : "&mdash;")
+ this.disguiseCosts.getText()
+ (isMana ? ' ' : ". ")
+ costInfo
+ " <i>(" + REMINDER_TEXT + ")</i>";
}
}

View file

@ -8,9 +8,6 @@ import mage.cards.Card;
import mage.game.Game;
import mage.game.stack.Spell;
import java.util.Collections;
import java.util.List;
/**
* @author LevelX2
*/
@ -20,18 +17,13 @@ public enum SpellAbilityCastMode {
FLASHBACK("Flashback"),
BESTOW("Bestow"),
PROTOTYPE("Prototype"),
MORPH("Morph", false, true, SpellAbilityCastMode.MORPH_ADDITIONAL_RULE),
MEGAMORPH("Megamorph", false, true, SpellAbilityCastMode.MORPH_ADDITIONAL_RULE),
DISGUISE("Disguise", false, true, SpellAbilityCastMode.DISGUISE_ADDITIONAL_RULE),
MORPH("Morph", false, true),
MEGAMORPH("Megamorph", false, true),
DISGUISE("Disguise", false, true),
TRANSFORMED("Transformed", true),
DISTURB("Disturb", true),
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true);
private static final String MORPH_ADDITIONAL_RULE = "You may cast this card as a 2/2 face-down creature, with no text,"
+ " no name, no subtypes, and no mana cost by paying {3} rather than paying its mana cost.";
private static final String DISGUISE_ADDITIONAL_RULE = "You may cast this card face down for {3} as a 2/2 creature with "
+ "ward {2}. Turn it face up any time for its disguise cost.";
private final String text;
// should the cast mode use the second face?
@ -39,10 +31,6 @@ public enum SpellAbilityCastMode {
private final boolean isFaceDown;
// use it to add additional info in stack object cause face down has nothing
// TODO: is it possible to use InfoEffect or CardHint instead that?
private final List<String> additionalRulesOnStack;
public boolean isTransformed() {
return this.isTransformed;
}
@ -52,24 +40,19 @@ public enum SpellAbilityCastMode {
}
SpellAbilityCastMode(String text, boolean isTransformed) {
this(text, isTransformed, false, null);
this(text, isTransformed, false);
}
SpellAbilityCastMode(String text, boolean isTransformed, boolean isFaceDown, String additionalRulesOnStack) {
SpellAbilityCastMode(String text, boolean isTransformed, boolean isFaceDown) {
this.text = text;
this.isTransformed = isTransformed;
this.isFaceDown = isFaceDown;
this.additionalRulesOnStack = additionalRulesOnStack == null ? null : Collections.singletonList(additionalRulesOnStack);
}
public boolean isFaceDown() {
return this.isFaceDown;
}
public List<String> getAdditionalRulesOnStack() {
return additionalRulesOnStack;
}
@Override
public String toString() {
return text;

View file

@ -8,6 +8,7 @@ import mage.abilities.keyword.special.JohanVigilanceAbility;
import mage.cards.Card;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.util.CardUtil;
import java.util.Objects;
@ -43,11 +44,18 @@ public enum NoAbilityPredicate implements Predicate<MageObject> {
// (2007-05-01)
for (Ability ability : abilities) {
// ignore inner face down abilities like turn up and becomes creature
if (ability.getWorksFaceDown()) {
// inner face down abilities like turn up and becomes creature
continue;
}
if (!Objects.equals(ability.getClass(), SpellAbility.class) && !ability.getClass().equals(JohanVigilanceAbility.class)) {
// ignore information abilities
if (CardUtil.isInformationAbility(ability)) {
continue;
}
if (!Objects.equals(ability.getClass(), SpellAbility.class)
&& !ability.getClass().equals(JohanVigilanceAbility.class)) {
return false;
}
}

View file

@ -13,6 +13,7 @@ import mage.abilities.dynamicvalue.common.SavedDamageValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.asthought.CanPlayCardControllerEffect;
import mage.abilities.effects.common.asthought.YouMaySpendManaAsAnyColorToCastTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
@ -2237,4 +2238,12 @@ public final class CardUtil {
public static boolean canShowAsControlled(Card card, UUID createdForPlayer) {
return card.getControllerOrOwnerId().equals(createdForPlayer);
}
/**
* Ability used for information only, e.g. adds additional rule texts
*/
public static boolean isInformationAbility(Ability ability) {
return !ability.getEffects().isEmpty()
&& ability.getEffects().stream().allMatch(e -> e instanceof InfoEffect);
}
}