Illusionary mask

This commit is contained in:
Alumi
2021-01-16 16:33:38 +00:00
committed by Michael Kamensky
parent acc04122fd
commit f3ecad7895
13 changed files with 310 additions and 14 deletions

View File

@@ -357,6 +357,15 @@ public abstract class SpellAbilityEffect {
addedTrigger.setIntrinsic(true);
}
protected static void addExileOnCounteredTrigger(final Card card) {
String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True";
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
}
protected static void addForgetCounterTrigger(final Card card, final String counterType) {
String trig = "Mode$ CounterRemoved | TriggerZones$ Command | ValidCard$ Card.IsRemembered | CounterType$ " + counterType + " | NewCounterAmount$ 0 | Static$ True";

View File

@@ -7,6 +7,7 @@ import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -15,6 +16,7 @@ import forge.game.card.CardDamageMap;
import forge.game.card.CardUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.Localizer;
@@ -128,6 +130,16 @@ public class DamageDealEffect extends DamageBaseEffect {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
final List<Card> definedSources = AbilityUtils.getDefinedCards(hostCard, sa.getParam("DamageSource"), sa);
if (definedSources == null || definedSources.isEmpty()) {
return;
}
for (Card source : definedSources) {
// Run replacement effects
game.getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(source));
}
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(hostCard, damage, sa);
@@ -172,11 +184,6 @@ public class DamageDealEffect extends DamageBaseEffect {
usedDamageMap = true;
}
final List<Card> definedSources = AbilityUtils.getDefinedCards(hostCard, sa.getParam("DamageSource"), sa);
if (definedSources == null || definedSources.isEmpty()) {
return;
}
for (Card source : definedSources) {
final Card sourceLKI = hostCard.getGame().getChangeZoneLKIInfo(source);

View File

@@ -9,6 +9,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.Localizer;
@@ -154,6 +155,10 @@ public class FightEffect extends DamageBaseEffect {
usedDamageMap = false;
}
// Run replacement effects
fighterA.getGame().getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(fighterA));
fighterB.getGame().getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(fighterB));
// 701.12c If a creature fights itself, it deals damage to itself equal to twice its power.
final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();

View File

@@ -20,6 +20,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
@@ -216,8 +217,16 @@ public class PlayEffect extends SpellAbilityEffect {
tgtCards.remove(original);
}
// only one mode can be used
SpellAbility tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas);
SpellAbility tgtSA;
if (!sa.hasParam("CastFaceDown")) {
// only one mode can be used
tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas);
} else {
// For Illusionary Mask effect
tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard);
}
final boolean noManaCost = sa.hasParam("WithoutManaCost");
if (noManaCost) {
tgtSA = tgtSA.copyWithNoManaCost();
@@ -257,7 +266,12 @@ public class PlayEffect extends SpellAbilityEffect {
if (sa.hasParam("ReplaceGraveyard")) {
addReplaceGraveyardEffect(tgtCard, sa, sa.getParam("ReplaceGraveyard"));
}
// For Illusionary Mask effect
if (sa.hasParam("ReplaceIlluMask")) {
addIllusionaryMaskReplace(tgtCard, sa);
}
tgtSA.setSVar("IsCastFromPlayEffect", "True");
if (controller.getController().playSaFromPlayEffect(tgtSA)) {
@@ -279,7 +293,7 @@ public class PlayEffect extends SpellAbilityEffect {
}
} // end resolve
protected void addReplaceGraveyardEffect(Card c, SpellAbility sa, String zone) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
@@ -326,4 +340,42 @@ public class PlayEffect extends SpellAbilityEffect {
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
protected void addIllusionaryMaskReplace(Card c, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
final Player controller = sa.getActivatingPlayer();
final String name = hostCard.getName() + "'s Effect";
final String image = hostCard.getImageKey();
final Card eff = createEffect(sa, controller, name, image);
eff.addRemembered(c);
String [] repeffstrs = {
"Event$ AssignDealDamage | ValidCard$ Card.IsRemembered+faceDown " +
"| Description$ If the creature that spell becomes as it resolves has not been turned face up" +
" and would assign or deal damage, be dealt damage, or become tapped, instead it's turned face up" +
" and assigns or deals damage, is dealt damage, or becomes tapped.",
"Event$ DealtDamage | ValidCard$ Card.IsRemembered+faceDown",
"Event$ Tap | ValidCard$ Card.IsRemembered+faceDown"
};
String effect = "DB$ SetState | Defined$ ReplacedCard | Mode$ TurnFace";
for (int i = 0; i < 3; ++i) {
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstrs[i], eff, true);
re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
eff.addReplacementEffect(re);
}
addExileOnMovedTrigger(eff, "Battlefield");
addExileOnCounteredTrigger(eff);
eff.updateStateForView();
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
}

View File

@@ -3677,6 +3677,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void tap(boolean attacker) {
if (tapped) { return; }
// Run replacement effects
getGame().getReplacementHandler().run(ReplacementType.Tap, AbilityKey.mapFromAffected(this));
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
runParams.put(AbilityKey.Attacker, attacker);
@@ -5096,6 +5099,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return 0;
}
// Run replacement effects
getGame().getReplacementHandler().run(ReplacementType.DealtDamage, AbilityKey.mapFromAffected(this));
addReceivedDamageFromThisTurn(source, damageIn);
source.addDealtDamageToThisTurn(this, damageIn);

View File

@@ -3,6 +3,7 @@ package forge.game.card;
import com.google.common.collect.Iterables;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.Direction;
import forge.game.EvenOdd;
import forge.game.Game;
@@ -14,6 +15,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.mana.Mana;
import forge.game.player.Player;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility;
@@ -24,6 +26,7 @@ import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -1831,6 +1834,61 @@ public class CardProperty {
if (card.equals(obj)) {
return false;
}
} else if (property.equals("CanPayManaCost")) {
final class CheckCanPayManaCost {
private List<Mana> manaPaid;
private List<ManaCostShard> manaCost;
// check shards recursively
boolean checkShard(int index) {
if (index >= manaCost.size()) {
return true;
}
ManaCostShard shard = manaCost.get(index);
// ignore X cost
if (shard == ManaCostShard.X) {
return checkShard(index + 1);
}
for (int i = 0; i < manaPaid.size(); i++) {
Mana mana = manaPaid.get(i);
if (shard.isColor(mana.getColor()) || (shard.isSnow() && mana.isSnow())) {
manaPaid.remove(i);
if (checkShard(index + 1)) {
return true;
}
manaPaid.add(i, mana);
}
if (shard.isGeneric() && !shard.isSnow()) {
// Handle 2 generic mana
if (shard.getCmc() == 2) {
manaCost.add(ManaCostShard.GENERIC);
}
manaPaid.remove(i);
if (checkShard(index + 1)) {
return true;
}
manaPaid.add(i, mana);
if (shard.getCmc() == 2) {
manaCost.remove(manaCost.size() - 1);
}
}
}
return false;
}
boolean check() {
manaPaid = new ArrayList<>(spellAbility.getPayingMana());
manaCost = new ArrayList<>();
card.getManaCost().forEach(manaCost::add);
Collections.sort(manaCost);
//It seems the above codes didn't add generic mana cost ?
//Add generic cost below to fix it.
int genericCost = card.getManaCost().getGenericCost();
while (genericCost-- > 0) {
manaCost.add(ManaCostShard.GENERIC);
}
return checkShard(0);
}
}
return new CheckCanPayManaCost().check();
} else {
// StringType done in CardState
if (!card.getCurrentState().hasProperty(property, sourceController, source, spellAbility)) {

View File

@@ -24,6 +24,7 @@ import forge.game.ability.AbilityKey;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.trigger.TriggerType;
import forge.util.CardTranslation;
@@ -673,7 +674,10 @@ public class Combat {
if (firstStrikeDamage) {
combatantsThatDealtFirstStrikeDamage.add(blocker);
}
// Run replacement effects
blocker.getGame().getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(blocker));
CardCollection attackers = attackersOrderedForDamageAssignment.get(blocker);
final int damage = blocker.getNetCombatDamage();
@@ -709,7 +713,10 @@ public class Combat {
if (firstStrikeDamage) {
combatantsThatDealtFirstStrikeDamage.add(attacker);
}
// Run replacement effects
attacker.getGame().getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(attacker));
// If potential damage is 0, continue along
final int damageDealt = attacker.getNetCombatDamage();
if (damageDealt <= 0) {

View File

@@ -0,0 +1,47 @@
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
/**
* TODO: Write javadoc for this type.
*
*/
public class ReplaceAssignDealDamage extends ReplacementEffect {
/**
* Instantiates a new replace tap.
*
* @param params the params
* @param host the host
*/
public ReplaceAssignDealDamage(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (hasParam("ValidCard")) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Card, runParams.get(AbilityKey.Affected));
}
}

View File

@@ -0,0 +1,47 @@
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
/**
* TODO: Write javadoc for this type.
*
*/
public class ReplaceDealtDamage extends ReplacementEffect {
/**
* Instantiates a new replace tap.
*
* @param params the params
* @param host the host
*/
public ReplaceDealtDamage(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (hasParam("ValidCard")) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Card, runParams.get(AbilityKey.Affected));
}
}

View File

@@ -0,0 +1,47 @@
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
/**
* TODO: Write javadoc for this type.
*
*/
public class ReplaceTap extends ReplacementEffect {
/**
* Instantiates a new replace tap.
*
* @param params the params
* @param host the host
*/
public ReplaceTap(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (hasParam("ValidCard")) {
if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Card, runParams.get(AbilityKey.Affected));
}
}

View File

@@ -6,17 +6,19 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
/**
/**
* TODO: Write javadoc for this type.
*
*/
public enum ReplacementType {
AddCounter(ReplaceAddCounter.class),
AssignDealDamage(ReplaceAssignDealDamage.class),
Attached(ReplaceAttached.class),
Counter(ReplaceCounter.class),
CopySpell(ReplaceCopySpell.class),
CreateToken(ReplaceToken.class),
DamageDone(ReplaceDamage.class),
DealtDamage(ReplaceDealtDamage.class),
Destroy(ReplaceDestroy.class),
Discard(ReplaceDiscard.class),
Draw(ReplaceDraw.class),
@@ -29,6 +31,7 @@ public enum ReplacementType {
Scry(ReplaceScry.class),
SetInMotion(ReplaceSetInMotion.class),
Surveil(ReplaceSurveil.class),
Tap(ReplaceTap.class),
TurnFaceUp(ReplaceTurnFaceUp.class),
Untap(ReplaceUntap.class);
@@ -46,7 +49,7 @@ public enum ReplacementType {
}
throw new RuntimeException("Element " + value + " not found in TriggerType enum");
}
/**
* TODO: Write javadoc for this method.
* @param mapParams

View File

@@ -243,7 +243,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
source.setController(activator, 0);
final Spell spell = (Spell) sp;
if (spell.isCastFaceDown()) {
source.turnFaceDown();
// Need to override for double faced cards
source.turnFaceDown(true);
} else if (source.isFaceDown()) {
source.turnFaceUp(null);
}

View File

@@ -0,0 +1,7 @@
Name:Illusionary Mask
ManaCost:2
Types:Artifact
A:AB$ Play | Cost$ X | Valid$ Card.Creature+YouOwn+CanPayManaCost | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True | CastFaceDown$ True | ReplaceIlluMask$ True | References$ X | SorcerySpeed$ True
SVar:X:Count$xPaid
AI:RemoveDeck:All
Oracle:{X}: You may choose a creature card in your hand whose mana cost could be paid by some amount of, or all of, the mana you spent on X. If you do, you may cast that card face down as a 2/2 creature spell without paying its mana cost. If the creature that spell becomes as it resolves has not been turned face up and would assign or deal damage, be dealt damage, or become tapped, instead it's turned face up and assigns or deals damage, is dealt damage, or becomes tapped. Activate this ability only any time you could cast a sorcery.