Merge branch 'illusionary_mask' into 'master'

Illusionary mask

See merge request core-developers/forge!3591
This commit is contained in:
Michael Kamensky
2021-01-16 16:33:39 +00:00
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);
}