Merge branch 'master' into AFC_Cards_20210717

This commit is contained in:
Wendell Wilkerson
2021-07-18 11:53:10 -05:00
334 changed files with 245 additions and 131 deletions

View File

@@ -229,12 +229,12 @@ public class GameCopier {
private static final boolean USE_FROM_PAPER_CARD = true; private static final boolean USE_FROM_PAPER_CARD = true;
private Card createCardCopy(Game newGame, Player newOwner, Card c) { private Card createCardCopy(Game newGame, Player newOwner, Card c) {
if (c.isToken() && !c.isEmblem()) { if (c.isToken() && !c.isImmutable()) {
Card result = new TokenInfo(c).makeOneToken(newOwner); Card result = new TokenInfo(c).makeOneToken(newOwner);
CardFactory.copyCopiableCharacteristics(c, result); CardFactory.copyCopiableCharacteristics(c, result);
return result; return result;
} }
if (USE_FROM_PAPER_CARD && !c.isEmblem() && c.getPaperCard() != null) { if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) {
Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner); Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner);
newCard.setCommander(c.isCommander()); newCard.setCommander(c.isCommander());
return newCard; return newCard;

View File

@@ -58,7 +58,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
Conspiracy(false, "conspiracies"), Conspiracy(false, "conspiracies"),
Creature(true, "creatures"), Creature(true, "creatures"),
Dungeon(false, "dungeons"), Dungeon(false, "dungeons"),
Emblem(false, "emblems"),
Enchantment(true, "enchantments"), Enchantment(true, "enchantments"),
Instant(false, "instants"), Instant(false, "instants"),
Land(true, "lands"), Land(true, "lands"),
@@ -437,11 +436,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return coreTypes.contains(CoreType.Phenomenon); return coreTypes.contains(CoreType.Phenomenon);
} }
@Override
public boolean isEmblem() {
return coreTypes.contains(CoreType.Emblem);
}
@Override @Override
public boolean isTribal() { public boolean isTribal() {
return coreTypes.contains(CoreType.Tribal); return coreTypes.contains(CoreType.Tribal);
@@ -547,7 +541,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (!isInstant() && !isSorcery()) { if (!isInstant() && !isSorcery()) {
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE); Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
} }
if (!isPlaneswalker() && !isEmblem()) { if (!isPlaneswalker()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE); Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
} }
} }

View File

@@ -42,7 +42,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean isBasicLand(); boolean isBasicLand();
boolean isPlane(); boolean isPlane();
boolean isPhenomenon(); boolean isPhenomenon();
boolean isEmblem();
boolean isTribal(); boolean isTribal();
boolean isDungeon(); boolean isDungeon();
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes); CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);

View File

@@ -153,8 +153,7 @@ public final class GameActionUtil {
final StringBuilder sb = new StringBuilder(sa.getDescription()); final StringBuilder sb = new StringBuilder(sa.getDescription());
if (!source.equals(host)) { if (!source.equals(host)) {
sb.append(" by "); sb.append(" by ");
if ((host.isEmblem() || host.getType().hasSubtype("Effect")) if ((host.isImmutable()) && host.getEffectSource() != null) {
&& host.getEffectSource() != null) {
sb.append(host.getEffectSource()); sb.append(host.getEffectSource());
} else { } else {
sb.append(host); sb.append(host);
@@ -542,7 +541,6 @@ public final class GameActionUtil {
final Card eff = new Card(game.nextCardId(), game); final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp()); eff.setTimestamp(game.getNextTimestamp());
eff.setName(sourceCard.getName() + "'s Effect"); eff.setName(sourceCard.getName() + "'s Effect");
eff.addType("Effect");
eff.setOwner(controller); eff.setOwner(controller);
eff.setImageKey(sourceCard.getImageKey()); eff.setImageKey(sourceCard.getImageKey());

View File

@@ -112,9 +112,9 @@ public class AbilityUtils {
// Probably will move to One function solution sometime in the future // Probably will move to One function solution sometime in the future
public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) { public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) {
CardCollection cards = new CardCollection(); CardCollection cards = new CardCollection();
String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self String changedDef = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
final String[] incR = defined.split("\\.", 2); final String[] incR = changedDef.split("\\.", 2);
defined = incR[0]; String defined = incR[0];
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
Card c = null; Card c = null;
@@ -132,7 +132,7 @@ public class AbilityUtils {
} }
} }
else if (defined.equals("EffectSource")) { else if (defined.equals("EffectSource")) {
if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) { if (hostCard.isImmutable()) {
c = findEffectRoot(hostCard); c = findEffectRoot(hostCard);
} }
} }
@@ -346,6 +346,23 @@ public class AbilityUtils {
cards.add(game.getCardState(cardByID)); cards.add(game.getCardState(cardByID));
} }
} }
} else if (defined.startsWith("Valid")) {
Iterable<Card> candidates;
String validDefined;
if (defined.startsWith("Valid ")) {
candidates = game.getCardsIn(ZoneType.Battlefield);
validDefined = changedDef.substring("Valid ".length());
} else if (defined.startsWith("ValidAll ")) {
candidates = game.getCardsInGame();
validDefined = changedDef.substring("ValidAll ".length());
} else {
String[] s = changedDef.split(" ", 2);
String zone = s[0].substring("Valid".length());
candidates = game.getCardsIn(ZoneType.smartValueOf(zone));
validDefined = s[1];
}
cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa));
return cards;
} else { } else {
CardCollection list = null; CardCollection list = null;
if (sa instanceof SpellAbility) { if (sa instanceof SpellAbility) {
@@ -377,19 +394,6 @@ public class AbilityUtils {
} }
} }
if (defined.startsWith("Valid ")) {
String validDefined = defined.substring("Valid ".length());
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validDefined.split(","), hostCard.getController(), hostCard, sa);
} else if (defined.startsWith("ValidAll ")) {
String validDefined = defined.substring("ValidAll ".length());
list = CardLists.getValidCards(game.getCardsInGame(), validDefined.split(","), hostCard.getController(), hostCard, sa);
} else if (defined.startsWith("Valid")) {
String[] s = defined.split(" ");
String zone = s[0].substring("Valid".length());
String validDefined = s[1];
list = CardLists.getValidCards(game.getCardsIn(ZoneType.smartValueOf(zone)), validDefined.split(","), hostCard.getController(), hostCard, sa);
}
if (list != null) { if (list != null) {
cards.addAll(list); cards.addAll(list);
} }
@@ -421,7 +425,7 @@ public class AbilityUtils {
private static Card findEffectRoot(Card startCard) { private static Card findEffectRoot(Card startCard) {
Card cc = startCard.getEffectSource(); Card cc = startCard.getEffectSource();
if (cc != null) { if (cc != null) {
if (cc.isEmblem() || cc.getType().hasSubtype("Effect")) { if (cc.isImmutable()) {
return findEffectRoot(cc); return findEffectRoot(cc);
} }
return cc; return cc;

View File

@@ -13,7 +13,6 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Table; import com.google.common.collect.Table;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -457,25 +456,19 @@ public abstract class SpellAbilityEffect {
final Card eff = new Card(game.nextCardId(), game); final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp()); eff.setTimestamp(game.getNextTimestamp());
eff.setName(name); eff.setName(name);
eff.setColor(hostCard.determineColor().getColor());
// if name includes emblem then it should be one // if name includes emblem then it should be one
eff.addType(name.startsWith("Emblem") ? "Emblem" : "Effect"); if (name.startsWith("Emblem")) {
// add Planeswalker types into Emblem for fun eff.setEmblem(true);
if (name.startsWith("Emblem") && hostCard.isPlaneswalker()) { // Emblem needs to be colorless
for (final String type : hostCard.getType().getSubtypes()) { eff.setColor(MagicColor.COLORLESS);
if (CardType.isAPlaneswalkerType(type)) {
eff.addType(type);
}
}
} }
eff.setOwner(controller); eff.setOwner(controller);
eff.setSVars(sa.getSVars()); eff.setSVars(sa.getSVars());
eff.setImageKey(image); eff.setImageKey(image);
if (eff.getType().hasType(CardType.CoreType.Emblem)) {
eff.setColor(MagicColor.COLORLESS);
} else {
eff.setColor(hostCard.determineColor().getColor());
}
eff.setImmutable(true); eff.setImmutable(true);
eff.setEffectSource(sa); eff.setEffectSource(sa);

View File

@@ -190,11 +190,6 @@ public class AnimateEffect extends AnimateEffectBase {
} }
} }
// Restore immutable to effect
if (sa.hasParam("Immutable")) {
c.setImmutable(true);
}
game.fireEvent(new GameEventCardStatsChanged(c)); game.fireEvent(new GameEventCardStatsChanged(c));
} }

View File

@@ -164,14 +164,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa); final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
addedTriggers.add(parsedTrigger); addedTriggers.add(parsedTrigger);
} }
if (sa.hasParam("GainsTriggeredAbilitiesOf")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("GainsTriggeredAbilitiesOf"), sa);
for (final Card card : cards) {
for (Trigger t : card.getTriggers()) {
addedTriggers.add(t.copy(c, false));
}
}
}
// give replacement effects // give replacement effects
final List<ReplacementEffect> addedReplacements = Lists.newArrayList(); final List<ReplacementEffect> addedReplacements = Lists.newArrayList();

View File

@@ -12,7 +12,6 @@ public class DetachedCardEffect extends Card {
card = card0; card = card0;
setName(name0); setName(name0);
addType("Effect");
setOwner(card0.getOwner()); setOwner(card0.getOwner());
setImmutable(true); setImmutable(true);

View File

@@ -140,10 +140,6 @@ public class EffectEffect extends SpellAbilityEffect {
final Card eff = createEffect(sa, controller, name, image); final Card eff = createEffect(sa, controller, name, image);
eff.setSetCode(sa.getHostCard().getSetCode()); eff.setSetCode(sa.getHostCard().getSetCode());
eff.setRarity(sa.getHostCard().getRarity()); eff.setRarity(sa.getHostCard().getRarity());
// For Raging River effect to add attacker "left" or "right" pile later
if (sa.hasParam("Mutable")) {
eff.setImmutable(false);
}
// Abilities and triggers work the same as they do for Token // Abilities and triggers work the same as they do for Token
// Grant abilities // Grant abilities

View File

@@ -37,7 +37,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
// Play the Regen sound // Play the Regen sound
game.fireEvent(new GameEventCardRegenerated()); game.fireEvent(new GameEventCardRegenerated());
if (host.getType().hasStringType("Effect")) { if (host.isImmutable()) {
c.subtractShield(host); c.subtractShield(host);
host.removeRemembered(c); host.removeRemembered(c);
} }

View File

@@ -47,7 +47,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
prevent -= n; prevent -= n;
if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) { if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
if (card.getType().hasStringType("Effect") && prevent <= 0) { if (card.isImmutable() && prevent <= 0) {
game.getAction().exile(card, null); game.getAction().exile(card, null);
} else { } else {
card.setSVar(varValue, "Number$" + prevent); card.setSVar(varValue, "Number$" + prevent);

View File

@@ -45,7 +45,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
dmg -= n; dmg -= n;
prevent -= n; prevent -= n;
if (card.getType().hasStringType("Effect") && prevent <= 0) { if (card.isImmutable() && prevent <= 0) {
game.getAction().exile(card, null); game.getAction().exile(card, null);
} else if (!StringUtils.isNumeric(varValue)) { } else if (!StringUtils.isNumeric(varValue)) {
sa.setSVar(varValue, "Number$" + prevent); sa.setSVar(varValue, "Number$" + prevent);

View File

@@ -31,6 +31,7 @@ import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public abstract class TokenEffectBase extends SpellAbilityEffect { public abstract class TokenEffectBase extends SpellAbilityEffect {
@@ -138,6 +139,15 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
tok.addEtbCounter(cType, cAmount, creator); tok.addEtbCounter(cType, cAmount, creator);
} }
if (sa.hasParam("AddTriggersFrom")) {
final List<Card> cards = AbilityUtils.getDefinedCards(host, sa.getParam("AddTriggersFrom"), sa);
for (final Card card : cards) {
for (final Trigger trig : card.getTriggers()) {
tok.addTrigger(trig.copy(tok, false));
}
}
}
if (clone) { if (clone) {
tok.setCopiedPermanent(prototype); tok.setCopiedPermanent(prototype);
} }

View File

@@ -264,6 +264,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// for Vanguard / Manapool / Emblems etc. // for Vanguard / Manapool / Emblems etc.
private boolean isImmutable = false; private boolean isImmutable = false;
private boolean isEmblem = false;
private int exertThisTurn = 0; private int exertThisTurn = 0;
private PlayerCollection exertedByPlayer = new PlayerCollection(); private PlayerCollection exertedByPlayer = new PlayerCollection();
@@ -4598,15 +4599,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public final boolean isPermanent() { public final boolean isPermanent() {
return !isImmutable && (isInZone(ZoneType.Battlefield) || getType().isPermanent()); return !isImmutable() && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
} }
public final boolean isSpell() { public final boolean isSpell() {
return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield)))); return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield))));
} }
public final boolean isEmblem() { return getType().isEmblem(); }
public final boolean isLand() { return getType().isLand(); } public final boolean isLand() { return getType().isLand(); }
public final boolean isBasicLand() { return getType().isBasicLand(); } public final boolean isBasicLand() { return getType().isBasicLand(); }
public final boolean isSnow() { return getType().isSnow(); } public final boolean isSnow() { return getType().isSnow(); }
@@ -4865,11 +4864,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// Takes one argument like Permanent.Blue+withFlying // Takes one argument like Permanent.Blue+withFlying
@Override @Override
public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) { public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) {
if (isImmutable() && source != null && !source.isRemembered(this) &&
!(restriction.startsWith("Emblem") || restriction.startsWith("Effect"))) { // special case exclusion
return false;
}
// Inclusive restrictions are Card types // Inclusive restrictions are Card types
final String[] incR = restriction.split("\\.", 2); final String[] incR = restriction.split("\\.", 2);
@@ -4879,14 +4873,27 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
incR[0] = incR[0].substring(1); // consume negation sign incR[0] = incR[0].substring(1); // consume negation sign
} }
if (incR[0].equals("Spell") && !isSpell()) { if (incR[0].equals("Spell")) {
if (!isSpell()) {
return testFailed; return testFailed;
} }
if (incR[0].equals("Permanent") && !isPermanent()) { } else if (incR[0].equals("Permanent")) {
if (!isPermanent()) {
return testFailed; return testFailed;
} }
if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell") } else if (incR[0].equals("Effect")) {
&& !incR[0].equals("Permanent") && !getType().hasStringType(incR[0])) { if (!isImmutable()) {
return testFailed;
}
} else if (incR[0].equals("Emblem")) {
if (!isEmblem()) {
return testFailed;
}
} else if (incR[0].equals("card") || incR[0].equals("Card")) {
if (isImmutable()) {
return testFailed;
}
} else if (!getType().hasStringType(incR[0])) {
return testFailed; // Check for wrong type return testFailed; // Check for wrong type
} }
@@ -4916,6 +4923,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public final void setImmutable(final boolean isImmutable0) { public final void setImmutable(final boolean isImmutable0) {
isImmutable = isImmutable0; isImmutable = isImmutable0;
view.updateImmutable(this);
}
public final boolean isEmblem() {
return isEmblem;
}
public final void setEmblem(final boolean isEmblem0) {
isEmblem = isEmblem0;
view.updateEmblem(this);
} }
/* /*

View File

@@ -357,7 +357,7 @@ public class CardProperty {
return false; return false;
} }
} else if (property.equals("EffectSource")) { } else if (property.equals("EffectSource")) {
if (!source.isEmblem() && !source.getType().hasSubtype("Effect")) { if (!source.isImmutable()) {
return false; return false;
} }

View File

@@ -242,6 +242,7 @@ public final class CardUtil {
newCopy.setToken(in.isToken()); newCopy.setToken(in.isToken());
newCopy.setCopiedSpell(in.isCopiedSpell()); newCopy.setCopiedSpell(in.isCopiedSpell());
newCopy.setImmutable(in.isImmutable()); newCopy.setImmutable(in.isImmutable());
newCopy.setEmblem(in.isEmblem());
// lock in the current P/T // lock in the current P/T
newCopy.setBasePower(in.getCurrentPower()); newCopy.setBasePower(in.getCurrentPower());

View File

@@ -233,6 +233,20 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Token, c.isToken()); set(TrackableProperty.Token, c.isToken());
} }
public boolean isImmutable() {
return get(TrackableProperty.IsImmutable);
}
public void updateImmutable(Card c) {
set(TrackableProperty.IsImmutable, c.isImmutable());
}
public boolean isEmblem() {
return get(TrackableProperty.IsEmblem);
}
public void updateEmblem(Card c) {
set(TrackableProperty.IsEmblem, c.isEmblem());
}
public boolean isTokenCard() { return get(TrackableProperty.TokenCard); } public boolean isTokenCard() { return get(TrackableProperty.TokenCard); }
void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); } void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); }

View File

@@ -3176,6 +3176,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (monarchEffect == null) { if (monarchEffect == null) {
monarchEffect = new Card(game.nextCardId(), null, game); monarchEffect = new Card(game.nextCardId(), null, game);
monarchEffect.setOwner(this); monarchEffect.setOwner(this);
monarchEffect.setImmutable(true);
if (set != null) { if (set != null) {
monarchEffect.setImageKey("t:monarch_" + set.toLowerCase()); monarchEffect.setImageKey("t:monarch_" + set.toLowerCase());
monarchEffect.setSetCode(set); monarchEffect.setSetCode(set);
@@ -3183,7 +3184,6 @@ public class Player extends GameEntity implements Comparable<Player> {
monarchEffect.setImageKey("t:monarch"); monarchEffect.setImageKey("t:monarch");
} }
monarchEffect.setName("The Monarch"); monarchEffect.setName("The Monarch");
monarchEffect.addType("Effect");
{ {
final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " + final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " +
@@ -3280,8 +3280,7 @@ public class Player extends GameEntity implements Comparable<Player> {
blessingEffect.setOwner(this); blessingEffect.setOwner(this);
blessingEffect.setImageKey("t:blessing"); blessingEffect.setImageKey("t:blessing");
blessingEffect.setName("City's Blessing"); blessingEffect.setName("City's Blessing");
blessingEffect.addType("Effect"); blessingEffect.setImmutable(true);
blessingEffect.updateStateForView(); blessingEffect.updateStateForView();
@@ -3353,7 +3352,6 @@ public class Player extends GameEntity implements Comparable<Player> {
keywordEffect.setOwner(this); keywordEffect.setOwner(this);
keywordEffect.setName("Keyword Effects"); keywordEffect.setName("Keyword Effects");
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD); keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
keywordEffect.addType("Effect");
keywordEffect.updateStateForView(); keywordEffect.updateStateForView();

View File

@@ -220,7 +220,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this); String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -95,8 +95,7 @@ public class LandAbility extends Ability {
Card source = sta.getHostCard(); Card source = sta.getHostCard();
if (!source.equals(getHostCard())) { if (!source.equals(getHostCard())) {
sb.append(" by "); sb.append(" by ");
if ((source.isEmblem() || source.getType().hasSubtype("Effect")) if (source.isImmutable() && source.getEffectSource() != null) {
&& source.getEffectSource() != null) {
sb.append(source.getEffectSource()); sb.append(source.getEffectSource());
} else { } else {
sb.append(source); sb.append(source);

View File

@@ -854,7 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (node.getHostCard() != null) { if (node.getHostCard() != null) {
String currentName; String currentName;
// if alternate state is viewed while card uses original // if alternate state is viewed while card uses original
if (node.isIntrinsic() && !node.getHostCard().isMutated() && node.cardState != null) { if (node.isIntrinsic() && node.cardState != null && node.cardState.getCard() == node.getHostCard()) {
currentName = node.cardState.getName(); currentName = node.cardState.getName();
} }
else { else {

View File

@@ -209,7 +209,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
public final String toString() { public final String toString() {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -132,7 +132,7 @@ public abstract class Trigger extends TriggerReplacementBase {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -24,6 +24,9 @@ public enum TrackableProperty {
Controller(TrackableTypes.PlayerViewType), Controller(TrackableTypes.PlayerViewType),
Zone(TrackableTypes.EnumType(ZoneType.class)), Zone(TrackableTypes.EnumType(ZoneType.class)),
IsImmutable(TrackableTypes.BooleanType),
IsEmblem(TrackableTypes.BooleanType),
Flipped(TrackableTypes.BooleanType), Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType), Facedown(TrackableTypes.BooleanType),
Foretold(TrackableTypes.BooleanType), Foretold(TrackableTypes.BooleanType),

View File

@@ -311,7 +311,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
final CardStateView state = getCard().getCurrentState(); final CardStateView state = getCard().getCurrentState();
final CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode()); final CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
boolean colorIsSet = false; boolean colorIsSet = false;
if (state.getType().isEmblem() || state.getType().hasStringType("Effect")) { if (getCard().isImmutable()) {
// Effects are drawn with orange border // Effects are drawn with orange border
g2d.setColor(Color.ORANGE); g2d.setColor(Color.ORANGE);
colorIsSet = true; colorIsSet = true;

View File

@@ -362,8 +362,7 @@ public class CardImageRenderer {
drawDetails(g, card, gameView, altState, x, y, w, h); drawDetails(g, card, gameView, altState, x, y, w, h);
return; return;
} }
if(card.isToken() && card.getCurrentState().getType().hasSubtype("Effect") if(card.isImmutable() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
&& FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
drawDetails(g, card, gameView, altState, x, y, w, h); drawDetails(g, card, gameView, altState, x, y, w, h);
return; return;
} }

View File

@@ -0,0 +1,16 @@
Name:Catti-brie of Mithral Hall
ManaCost:G W
Types:Legendary Creature Human Archer
PT:2/2
K:First Strike
K:Reach
T:Mode$ Attacks | ValidCard$ Creature.Self | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME attacks, put a +1/+1 counter on it for each Equipment attached to it.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Y
SVar:Y:Count$Valid Equipment.Attached
A:AB$ DealDamage | Cost$ 1 SubCounter<All/P1P1> | NumDmg$ X | ValidTgts$ Creature.attacking+OppCtrl,Creature.blocking+OppCtrl | TgtPrompt$ Select target attacking or blocking creature an opponent controls | SpellDescription$ It deals X damage to target attacking or blocking creature an opponent controls, where X is the number of counters removed this way.
SVar:X:SVar$CostCountersRemoved
SVar:HasAttackEffect:TRUE
SVar:EquipMe:Multiple
DeckNeeds:Type$Equipment
DeckHas:Ability$Counters
Oracle:First strike, reach\nWhenever Catti-brie of Mithral Hall attacks, put a +1/+1 counter on it for each Equipment attached to it.\n{1}, Remove all +1/+1 counters from Catti-brie: It deals X damage to target attacking or blocking creature an opponent controls, where X is the number of counters removed this way.

View File

@@ -5,9 +5,11 @@ PT:3/2
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBLoop | TriggerDescription$ Whenever CARDNAME attacks, choose target creature you control, then ABILITY T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBLoop | TriggerDescription$ Whenever CARDNAME attacks, choose target creature you control, then ABILITY
SVar:DBLoop:DB$ Repeat | ValidTgts$ Creature.YouCtrl | RepeatCheckSVar$ RepeatCheck | RepeatSVarCompare$ GT0 | RepeatSubAbility$ DBRollDice | RepeatOptional$ True SVar:DBLoop:DB$ Repeat | ValidTgts$ Creature.YouCtrl | RepeatCheckSVar$ RepeatCheck | RepeatSVarCompare$ GT0 | RepeatSubAbility$ DBRollDice | RepeatOptional$ True
SVar:DBRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-14:DBCopy,15-20:DBCopyRepeat | SpellDescription$ roll a d20. SVar:DBRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-14:DBCopy,15-20:DBCopyRepeat | SpellDescription$ roll a d20.
SVar:DBCopy:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AtEOT$ ExileCombat | SubAbility$ DBNotRepeat | SpellDescription$ 1-14 VERT Create a tapped and attacking token that's a copy of that creature, except it's not legendary and it has "Exile this creature at end of combat. SVar:DBCopy:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AddSVars$ DelinaTrigExile | AddTriggers$ TrigPhase | SubAbility$ DBNotRepeat | SpellDescription$ 1-14 VERT Create a tapped and attacking token that's a copy of that creature, except it's not legendary and it has "Exile this creature at end of combat.
SVar:DBNotRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 0 SVar:DBNotRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 0
SVar:DBCopyRepeat:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AtEOT$ ExileCombat | SubAbility$ DBRepeat | SpellDescription$ 15-20 VERT Create one of those tokens. You may roll again. SVar:DBCopyRepeat:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AddSVars$ DelinaTrigExile | AddTriggers$ TrigPhase | SubAbility$ DBRepeat | SpellDescription$ 15-20 VERT Create one of those tokens. You may roll again.
SVar:TrigPhase:Mode$ Phase | Phase$ EndCombat | TriggerZones$ Battlefield | Execute$ DelinaTrigExile | TriggerDescription$ Exile this creature at end of combat.
SVar:DelinaTrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
SVar:DBRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 1 SVar:DBRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 1
SVar:RepeatCheck:Number$1 SVar:RepeatCheck:Number$1
SVar:HasAttackEffect:TRUE SVar:HasAttackEffect:TRUE

View File

@@ -5,7 +5,7 @@ PT:3/4
T:Mode$ Phase | Phase$ EndCombat | Execute$ TrigCounter | TriggerZones$ Battlefield | TriggerDescription$ At end of combat, put a paralyzation counter on each creature blocking or blocked by CARDNAME and tap those creatures. Each of those creatures doesn't untap during its controller's untap step for as long as it has a paralyzation counter on it. Each of those creatures gains "{4}: Remove a paralyzation counter from this creature." T:Mode$ Phase | Phase$ EndCombat | Execute$ TrigCounter | TriggerZones$ Battlefield | TriggerDescription$ At end of combat, put a paralyzation counter on each creature blocking or blocked by CARDNAME and tap those creatures. Each of those creatures doesn't untap during its controller's untap step for as long as it has a paralyzation counter on it. Each of those creatures gains "{4}: Remove a paralyzation counter from this creature."
SVar:TrigCounter:DB$ PutCounterAll | CounterType$ PARALYZATION | CounterNum$ 1 | ValidCards$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBTap SVar:TrigCounter:DB$ PutCounterAll | CounterType$ PARALYZATION | CounterNum$ 1 | ValidCards$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBTap
SVar:DBTap:DB$ TapAll | ValidCards$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBEffect SVar:DBTap:DB$ TapAll | ValidCards$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | RememberObjects$ Valid Creature.blockedBySource,Valid Creature.blockingSource | StaticAbilities$ DontUntap | Duration$ Permanent | ConditionPresent$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBAnimate | ForgetOnMoved$ Battlefield | ForgetCounter$ PARALYZATION SVar:DBEffect:DB$ Effect | RememberObjects$ Valid Creature.blockedBySource,Creature.blockingSource | StaticAbilities$ DontUntap | Duration$ Permanent | ConditionPresent$ Creature.blockedBySource,Creature.blockingSource | SubAbility$ DBAnimate | ForgetOnMoved$ Battlefield | ForgetCounter$ PARALYZATION
SVar:DBAnimate:DB$ AnimateAll | ValidCards$ Creature.blockedBySource,Creature.blockingSource | Abilities$ ABRemoveCounter | Duration$ Permanent SVar:DBAnimate:DB$ AnimateAll | ValidCards$ Creature.blockedBySource,Creature.blockingSource | Abilities$ ABRemoveCounter | Duration$ Permanent
SVar:ABRemoveCounter:AB$ RemoveCounter | Defined$ Self | Cost$ 4 | CounterType$ PARALYZATION | CounterNum$ 1 | SpellDescription$ Remove a paralyzation counter from this creature. SVar:ABRemoveCounter:AB$ RemoveCounter | Defined$ Self | Cost$ 4 | CounterType$ PARALYZATION | CounterNum$ 1 | SpellDescription$ Remove a paralyzation counter from this creature.
SVar:DontUntap:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Each of those creatures doesn't untap during its controller's untap step for as long as it has a paralyzation counter on it. SVar:DontUntap:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Each of those creatures doesn't untap during its controller's untap step for as long as it has a paralyzation counter on it.

View File

@@ -0,0 +1,11 @@
Name:Druid of Purification
ManaCost:3 G
Types:Creature Human Druid
PT:2/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoice | TriggerDescription$ When CARDNAME enters the battlefield, starting with you, each player may choose an artifact or enchantment you dont control. Destroy each permanent chosen this way.
SVar:TrigChoice:DB$ RepeatEach | RepeatPlayers$ Player | StartingWithActivator$ True | RepeatSubAbility$ DBChoosePermanent | SubAbility$ DBDestroy
SVar:DBChoosePermanent:DB$ ChooseCard | Defined$ Remembered | Choices$ Artifact.YouDontCtrl,Enchantment.YouDontCtrl | Optional$ True | ChoiceTitle$ Choose an artifact or enchantment | RememberChosen$ True
SVar:DBDestroy:DB$ Destroy | Defined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:Random
Oracle:When Druid of Purification enters the battlefield, starting with you, each player may choose an artifact or enchantment you don't control. Destroy each permanent chosen this way.

Some files were not shown because too many files have changed in this diff Show More