Merge master

This commit is contained in:
Lyu Zong-Hong
2021-02-09 17:15:06 +09:00
345 changed files with 340 additions and 174 deletions

View File

@@ -108,9 +108,6 @@ public class ComputerUtil {
if (chooseTargets != null) { if (chooseTargets != null) {
chooseTargets.run(); chooseTargets.run();
} }
if (sa.isBestow()) {
sa.getHostCard().animateBestow();
}
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
// TODO: update mana color conversion for Daxos of Meletis // TODO: update mana color conversion for Daxos of Meletis

View File

@@ -1,6 +1,9 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.*; import forge.ai.*;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardTypeView; import forge.card.CardTypeView;
@@ -14,11 +17,13 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent; import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -55,6 +60,21 @@ public class PlayAi extends SpellAbilityAi {
} }
} }
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
final Iterator<Card> itr = cards.iterator();
while (itr.hasNext()) {
final Card c = itr.next();
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa)));
if (validSA.size() == 0) {
itr.remove();
}
}
if (cards.isEmpty()) {
return false;
}
}
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) { if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
// Additional logic for MoJhoSto: // Additional logic for MoJhoSto:
// Do not activate Jhoira too early, usually there are few good targets // Do not activate Jhoira too early, usually there are few good targets

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.util.CardTranslation;
import forge.util.ComparableOp; import forge.util.ComparableOp;
import forge.util.PredicateString; import forge.util.PredicateString;
@@ -352,16 +353,16 @@ public final class CardRulesPredicates {
case NAME: case NAME:
boolean otherName = false; boolean otherName = false;
if (card.getOtherPart() != null) { if (card.getOtherPart() != null) {
otherName = op(card.getOtherPart().getName(), this.operand); otherName = (op(CardTranslation.getTranslatedName(card.getOtherPart().getName()), this.operand) || op(card.getOtherPart().getName(), this.operand));
} }
return otherName || op(card.getName(), this.operand); return otherName || (op(CardTranslation.getTranslatedName(card.getName()), this.operand) || op(card.getName(), this.operand));
case SUBTYPE: case SUBTYPE:
shouldContain = (this.getOperator() == StringOp.CONTAINS) || (this.getOperator() == StringOp.EQUALS); shouldContain = (this.getOperator() == StringOp.CONTAINS) || (this.getOperator() == StringOp.EQUALS);
return shouldContain == card.getType().hasSubtype(this.operand); return shouldContain == card.getType().hasSubtype(this.operand);
case ORACLE_TEXT: case ORACLE_TEXT:
return op(card.getOracleText(), operand); return (op(CardTranslation.getTranslatedOracle(card.getName()), operand) || op(card.getOracleText(), this.operand));
case JOINED_TYPE: case JOINED_TYPE:
return op(card.getType().toString(), operand); return (op(CardTranslation.getTranslatedType(card.getName(), card.getType().toString()), operand) || op(card.getType().toString(), operand));
case COST: case COST:
final String cost = card.getManaCost().toString(); final String cost = card.getManaCost().toString();
return op(cost, operand); return op(cost, operand);

View File

@@ -28,6 +28,7 @@ import forge.StaticData;
import forge.card.CardDb; import forge.card.CardDb;
import forge.card.CardRarity; import forge.card.CardRarity;
import forge.card.CardRules; import forge.card.CardRules;
import forge.util.CardTranslation;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -187,7 +188,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
*/ */
@Override @Override
public String toString() { public String toString() {
return name; return CardTranslation.getTranslatedName(name);
// cannot still decide, if this "name|set" format is needed anymore // cannot still decide, if this "name|set" format is needed anymore
// return String.format("%s|%s", name, cardSet); // return String.format("%s|%s", name, cardSet);
} }

View File

@@ -177,6 +177,12 @@ public class ForgeScript {
return sa.getActivatingPlayer().equals(sourceController); return sa.getActivatingPlayer().equals(sourceController);
} else if (property.equals("OppCtrl")) { } else if (property.equals("OppCtrl")) {
return sa.getActivatingPlayer().isOpponentOf(sourceController); return sa.getActivatingPlayer().isOpponentOf(sourceController);
} else if (property.startsWith("cmc")) {
int y = sa.getPayCosts().getTotalMana().getCMC();
int x = AbilityUtils.calculateAmount(spellAbility.getHostCard(), property.substring(5), spellAbility);
if (!Expressions.compare(y, property, x)) {
return false;
}
} else if (sa.getHostCard() != null) { } else if (sa.getHostCard() != null) {
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility); return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
} }

View File

@@ -106,6 +106,14 @@ public class GameAction {
} }
private Card changeZoneWrapped(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause, Map<AbilityKey, Object> params) { private Card changeZoneWrapped(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause, Map<AbilityKey, Object> params) {
// 111.11. A copy of a permanent spell becomes a token as it resolves.
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (c.isCopiedSpell() && zoneTo.is(ZoneType.Battlefield) && c.isPermanent() && cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
c.setCopiedSpell(false);
c.setToken(true);
}
if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) { if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) {
// Remove Effect from command immediately, this is essential when some replacement // Remove Effect from command immediately, this is essential when some replacement
// effects happen during the resolving of a spellability ("the next time ..." effect) // effects happen during the resolving of a spellability ("the next time ..." effect)
@@ -637,8 +645,11 @@ public class GameAction {
} }
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final Zone stack = game.getStackZone(); Card result = moveTo(game.getStackZone(), c, cause, params);
return moveTo(stack, c, cause, params); if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) {
result.setSplitStateToPlayAbility(cause);
}
return result;
} }
public final Card moveToGraveyard(final Card c, SpellAbility cause) { public final Card moveToGraveyard(final Card c, SpellAbility cause) {

View File

@@ -3,7 +3,10 @@ package forge.game.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -1820,7 +1823,11 @@ public class AbilityUtils {
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) { public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
List<SpellAbility> sas = new ArrayList<>(); List<SpellAbility> sas = new ArrayList<>();
for (SpellAbility s : tgtCard.getBasicSpells()) { List<SpellAbility> list = Lists.newArrayList(tgtCard.getBasicSpells());
if (tgtCard.isModal()) {
list.addAll(Lists.newArrayList(tgtCard.getBasicSpells(tgtCard.getState(CardStateName.Modal))));
}
for (SpellAbility s : list) {
final Spell newSA = (Spell) s.copy(); final Spell newSA = (Spell) s.copy();
newSA.setActivatingPlayer(controller); newSA.setActivatingPlayer(controller);
SpellAbilityRestriction res = new SpellAbilityRestriction(); SpellAbilityRestriction res = new SpellAbilityRestriction();

View File

@@ -10,6 +10,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
@@ -25,27 +26,27 @@ public class AttachEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame();
if (host.isAura() && sa.isSpell()) { if (host.isAura() && sa.isSpell()) {
final Player ap = sa.getActivatingPlayer(); CardZoneTable table = new CardZoneTable();
host.setController(sa.getActivatingPlayer(), 0);
ZoneType previousZone = host.getZone().getZoneType();
// The Spell_Permanent (Auras) version of this AF needs to // The Spell_Permanent (Auras) version of this AF needs to
// move the card into play before Attaching // move the card into play before Attaching
final Card c = game.getAction().moveToPlay(host, sa);
host.setController(ap, 0);
// 111.11. A copy of a permanent spell becomes a token as it resolves.
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (host.isCopiedSpell()) {
host.setCopiedSpell(false);
host.setToken(true);
}
final Card c = ap.getGame().getAction().moveToPlay(host, ap, sa);
sa.setHostCard(c); sa.setHostCard(c);
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game);
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Game game = source.getGame();
CardCollection attachments; CardCollection attachments;
final List<GameObject> targets = getDefinedOrTargeted(sa, "Defined"); final List<GameObject> targets = getDefinedOrTargeted(sa, "Defined");

View File

@@ -2,10 +2,12 @@ package forge.game.ability.effects;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class PermanentEffect extends SpellAbilityEffect { public class PermanentEffect extends SpellAbilityEffect {
@@ -18,25 +20,26 @@ public class PermanentEffect extends SpellAbilityEffect {
*/ */
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
Player p = sa.getActivatingPlayer();
sa.getHostCard().setController(p, 0);
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame();
CardZoneTable table = new CardZoneTable();
ZoneType previousZone = host.getZone().getZoneType();
// 111.11. A copy of a permanent spell becomes a token as it resolves. host.setController(sa.getActivatingPlayer(), 0);
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (host.isCopiedSpell()) {
host.setCopiedSpell(false);
host.setToken(true);
}
final Card c = p.getGame().getAction().moveToPlay(host, p, sa); final Card c = game.getAction().moveToPlay(host, sa);
sa.setHostCard(c); sa.setHostCard(c);
// some extra for Dashing // some extra for Dashing
if (sa.isDash()) { if (sa.isDash() && c.isInPlay()) {
c.setSVar("EndOfTurnLeavePlay", "Dash"); c.setSVar("EndOfTurnLeavePlay", "Dash");
registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c)); registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c));
} }
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game);
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -28,6 +29,7 @@ import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AlternativeCost; import forge.game.spellability.AlternativeCost;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -82,7 +84,7 @@ public class PlayEffect extends SpellAbilityEffect {
tgtCards = new CardCollection( tgtCards = new CardCollection(
AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("Valid"), sa) AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("Valid"), sa)
); );
if ( sa.hasParam("ShowCards") ) { if (sa.hasParam("ShowCards")) {
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa)); showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa));
} }
} }
@@ -102,7 +104,7 @@ public class PlayEffect extends SpellAbilityEffect {
final CardCollection choice = new CardCollection(); final CardCollection choice = new CardCollection();
final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1";
int ncopied = AbilityUtils.calculateAmount(source, num, sa); int ncopied = AbilityUtils.calculateAmount(source, num, sa);
while(ncopied > 0) { while (ncopied > 0) {
final PaperCard cp = Aggregates.random(copysource); final PaperCard cp = Aggregates.random(copysource);
final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer()); final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer());
// Need to temporarily set the Owner so the Game is set // Need to temporarily set the Owner so the Game is set
@@ -145,6 +147,21 @@ public class PlayEffect extends SpellAbilityEffect {
return; return;
} }
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
final Iterator<Card> itr = tgtCards.iterator();
while (itr.hasNext()) {
final Card c = itr.next();
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa)));
if (validSA.size() == 0) {
itr.remove();
}
}
if (tgtCards.isEmpty()) {
return;
}
}
if (sa.hasParam("Amount") && sa.getParam("Amount").equals("All")) { if (sa.hasParam("Amount") && sa.getParam("Amount").equals("All")) {
amount = tgtCards.size(); amount = tgtCards.size();
} }
@@ -214,7 +231,12 @@ public class PlayEffect extends SpellAbilityEffect {
} }
// get basic spells (no flashback, etc.) // get basic spells (no flashback, etc.)
final List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller); List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller);
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
}
if (sas.isEmpty()) { if (sas.isEmpty()) {
continue; continue;
} }
@@ -233,6 +255,10 @@ public class PlayEffect extends SpellAbilityEffect {
// For Illusionary Mask effect // For Illusionary Mask effect
tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard); tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard);
} }
// in case player canceled from choice dialog
if (tgtSA == null) {
continue;
}
final boolean noManaCost = sa.hasParam("WithoutManaCost"); final boolean noManaCost = sa.hasParam("WithoutManaCost");
if (noManaCost) { if (noManaCost) {

View File

@@ -3,24 +3,40 @@ package forge.game.card;
import java.io.Serializable; import java.io.Serializable;
public class CardFaceView implements Serializable, Comparable<CardFaceView> { public class CardFaceView implements Serializable, Comparable<CardFaceView> {
private String name; /**
*
*/
private static final long serialVersionUID = 1874016432028306386L;
private String displayName;
private String oracleName;
public CardFaceView(String faceName) { public CardFaceView(String displayName) {
this.name = faceName; this(displayName, displayName);
} }
public String getName() { return name;} public CardFaceView(String displayFaceName, String oracleFaceName ) {
this.displayName = displayFaceName;
this.oracleName = oracleFaceName;
}
public String getName() { return displayName;}
public void setName(String name) { public void setName(String name) {
this.name = name; this.displayName = name;
}
public String getOracleName() { return oracleName; }
public void setOracleName(String name) {
this.oracleName = name;
} }
public String toString() { public String toString() {
return name; return displayName;
} }
@Override @Override
public int compareTo(CardFaceView o) { public int compareTo(CardFaceView o) {
return this.getName().compareTo(o.getName()); return this.getOracleName().compareTo(o.getOracleName());
} }
} }

View File

@@ -651,25 +651,29 @@ public class CardProperty {
if ((property.endsWith("Source") || property.equals("DamagedBy")) && if ((property.endsWith("Source") || property.equals("DamagedBy")) &&
!card.getReceivedDamageFromThisTurn().containsKey(source)) { !card.getReceivedDamageFromThisTurn().containsKey(source)) {
return false; return false;
} else if (property.endsWith("Remembered")) { } else {
boolean matched = false; String prop = property.substring("DamagedBy".length());
for (final Object obj : source.getRemembered()) {
if (!(obj instanceof Card)) { boolean found = false;
continue; for (Card d : card.getReceivedDamageFromThisTurn().keySet()) {
if (d.isValid(prop, sourceController, source, spellAbility)) {
found = true;
break;
} }
matched |= card.getReceivedDamageFromThisTurn().containsKey(obj);
} }
if (!matched)
return false; if (!found) {
} else if (property.endsWith("Equipped")) { for (Card d : AbilityUtils.getDefinedCards(source, prop, spellAbility)) {
final Card equipee = source.getEquipping(); if (card.getReceivedDamageFromThisTurn().containsKey(d)) {
if (equipee == null || !card.getReceivedDamageFromThisTurn().containsKey(equipee)) found = true;
return false; break;
} else if (property.endsWith("Enchanted")) { }
final Card equipee = source.getEnchantingCard(); }
if (equipee == null || !card.getReceivedDamageFromThisTurn().containsKey(equipee)) }
if (!found) {
return false; return false;
} }
}
} else if (property.startsWith("Damaged")) { } else if (property.startsWith("Damaged")) {
if (!card.getDealtDamageToThisTurn().containsKey(source)) { if (!card.getDealtDamageToThisTurn().containsKey(source)) {
return false; return false;
@@ -1035,17 +1039,21 @@ public class CardProperty {
} }
} }
return false; return false;
} else if (property.startsWith("ThisTurnEntered")) { } else if (property.equals("ThisTurnEntered")) {
final String restrictions = property.split("ThisTurnEntered_")[1]; // only check if it entered the Zone this turn
final String[] res = restrictions.split("_"); if (card.getTurnInZone() != game.getPhaseHandler().getTurn()) {
final ZoneType destination = ZoneType.smartValueOf(res[0]); return false;
ZoneType origin = null;
if (res.length > 1 && res[1].equals("from")) {
origin = ZoneType.smartValueOf(res[2]);
} }
List<Card> cards = CardUtil.getThisTurnEntered(destination, } else if (property.startsWith("ThisTurnEnteredFrom")) {
origin, "Card", source); final String restrictions = property.split("ThisTurnEnteredFrom_")[1];
if (!cards.contains(card)) { final String[] res = restrictions.split("_");
final ZoneType origin = ZoneType.smartValueOf(res[0]);
if (card.getTurnInZone() != game.getPhaseHandler().getTurn()) {
return false;
}
if (!card.getZone().isCardAddedThisTurn(card, origin)) {
return false; return false;
} }
} else if (property.equals("DiscardedThisTurn")) { } else if (property.equals("DiscardedThisTurn")) {

View File

@@ -25,6 +25,7 @@ import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import forge.util.CardTranslation;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.EnumSet; import java.util.EnumSet;
@@ -933,7 +934,7 @@ public class CardView extends GameEntityView {
} }
} }
} }
return (zone + ' ' + name + " (" + getId() + ")").trim(); return (zone + ' ' + CardTranslation.getTranslatedName(name) + " (" + getId() + ")").trim();
} }
public class CardStateView extends TrackableObject { public class CardStateView extends TrackableObject {

View File

@@ -211,6 +211,13 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
return getCardsAdded(cardsAddedLastTurn, origin); return getCardsAdded(cardsAddedLastTurn, origin);
} }
public final boolean isCardAddedThisTurn(final Card card, final ZoneType origin) {
if (!cardsAddedThisTurn.containsKey(origin)) {
return false;
}
return cardsAddedThisTurn.get(origin).contains(card);
}
private static List<Card> getCardsAdded(final MapOfLists<ZoneType, Card> cardsAdded, final ZoneType origin) { private static List<Card> getCardsAdded(final MapOfLists<ZoneType, Card> cardsAdded, final ZoneType origin) {
if (origin != null) { if (origin != null) {
final Collection<Card> cards = cardsAdded.get(origin); final Collection<Card> cards = cardsAdded.get(origin);

View File

@@ -167,7 +167,7 @@ public class GuiChoose {
if (sel instanceof ICardFace) { if (sel instanceof ICardFace) {
faceName = ((ICardFace) sel).getName(); faceName = ((ICardFace) sel).getName();
} else { } else {
faceName = ((CardFaceView) sel).getName(); faceName = ((CardFaceView) sel).getOracleName();
} }
PaperCard paper = FModel.getMagicDb().getCommonCards().getUniqueByName(faceName); PaperCard paper = FModel.getMagicDb().getCommonCards().getUniqueByName(faceName);
if (paper == null) { if (paper == null) {

View File

@@ -1,6 +1,5 @@
#Add one announcement per line #Add one announcement per line
Keyboard shortcut change: to help improve accessibility, the arrow key shortcuts to move between deck editor tables now require control or Command, depending on which platform you're on. Sorry for the long wait between releases. Pandemic + Technology issues caused some issues we couldn't hurdle.
Jumpstart sealed! Get in the discord if you aren't yet.
Double Masters (including double pick) Lots of new cards including Kaldheim.
Lots of art updates and fixes
No update on Mutate yet. Trust us, you'll know when it's here. No update on Mutate yet. Trust us, you'll know when it's here.

View File

@@ -0,0 +1,8 @@
Name:Aegar, the Freezing Flame
ManaCost:1 U R
Types:Legendary Creature Giant Wizard
PT:3/3
T:Mode$ ExcessDamage | ValidTarget$ Creature.OppCtrl+DamagedByGiant.YouCtrl,Creature.OppCtrl+DamagedByWizard.YouCtrl,Creature.OppCtrl+DamagedBySpell.YouCtrl,Planeswalker.OppCtrl+DamagedByGiant.YouCtrl,Planeswalker.OppCtrl+DamagedByWizard.YouCtrl,Planeswalker.OppCtrl+DamagedBySpell.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a creature or planeswalker an opponent controls is dealt excess damage, if a Giant, Wizard, or spell you controlled dealt damage to it this turn, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
Oracle:Whenever a creature or planeswalker an opponent controls is dealt excess damage, if a Giant, Wizard, or spell you controlled dealt damage to it this turn, draw a card.

View File

@@ -2,7 +2,7 @@ Name:Bag of Holding
ManaCost:1 ManaCost:1
Types:Artifact Types:Artifact
T:Mode$ Discarded | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever you discard a card, exile that card from your graveyard. T:Mode$ Discarded | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever you discard a card, exile that card from your graveyard.
SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCardLKICopy | Origin$ Graveyard | Destination$ Exile SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile
A:AB$ Draw | Cost$ 2 T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard A:AB$ Draw | Cost$ 2 T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard
SVar:DBDiscard:DB$Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose SVar:DBDiscard:DB$Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
AI:RemoveDeck:All AI:RemoveDeck:All

View File

@@ -1,7 +1,7 @@
Name:Baral's Expertise Name:Baral's Expertise
ManaCost:3 U U ManaCost:3 U U
Types:Sorcery Types:Sorcery
A:SP$ ChangeZone | Cost$ 3 U U | TargetMin$ 0 | TargetMax$ 3 | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBPlay | SpellDescription$ Return up to three target artifacts and/or creatures to their owners' hands. You may cast a card with converted mana cost 4 or less from your hand without paying its mana cost. A:SP$ ChangeZone | Cost$ 3 U U | TargetMin$ 0 | TargetMax$ 3 | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBPlay | SpellDescription$ Return up to three target artifacts and/or creatures to their owners' hands. You may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost.
SVar:DBPlay:DB$ Play | Valid$ Card.nonLand+YouOwn+cmcLE4 | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True SVar:DBPlay:DB$ Play | Valid$ Card.nonLand+YouOwn | ValidSA$ Spell.cmcLE4 | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/barals_expertise.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/barals_expertise.jpg
Oracle:Return up to three target artifacts and/or creatures to their owners' hands.\nYou may cast a card with converted mana cost 4 or less from your hand without paying its mana cost. Oracle:Return up to three target artifacts and/or creatures to their owners' hands.\nYou may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost.

View File

@@ -1,5 +1,5 @@
Name:Brought Back Name:Brought Back
ManaCost:W W ManaCost:W W
Types:Instant Types:Instant
A:SP$ ChangeZone | Cost$ W W | ValidTgts$ Permanent.YouOwn+ThisTurnEntered_Graveyard_from_Battlefield | TgtPrompt$ Select up to two target permanent cards in your graveyard that were put there from the battlefield this turn | TargetMin$ 0 | TargetMax$ 2 | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True | SpellDescription$ Choose up to two target permanent cards in your graveyard that were put there from the battlefield this turn. Return them to the battlefield tapped. A:SP$ ChangeZone | Cost$ W W | ValidTgts$ Permanent.YouOwn+ThisTurnEnteredFrom_Battlefield | TgtPrompt$ Select up to two target permanent cards in your graveyard that were put there from the battlefield this turn | TargetMin$ 0 | TargetMax$ 2 | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True | SpellDescription$ Choose up to two target permanent cards in your graveyard that were put there from the battlefield this turn. Return them to the battlefield tapped.
Oracle:Choose up to two target permanent cards in your graveyard that were put there from the battlefield this turn. Return them to the battlefield tapped. Oracle:Choose up to two target permanent cards in your graveyard that were put there from the battlefield this turn. Return them to the battlefield tapped.

View File

@@ -4,7 +4,7 @@ Types:Sorcery
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | References$ X | Description$ This spell costs {1} less to cast for each creature in your party. S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | References$ X | Description$ This spell costs {1} less to cast for each creature in your party.
SVar:X:Count$Party SVar:X:Count$Party
A:SP$ ChangeZone | Cost$ 4 B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ SpellDescription | SpellDescription$ Search your library for a card, put it into your hand, then shuffle your library. If you have a full party, you may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost. A:SP$ ChangeZone | Cost$ 4 B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ SpellDescription | SpellDescription$ Search your library for a card, put it into your hand, then shuffle your library. If you have a full party, you may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost.
SVar:DBPlay:DB$ Play | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ4 | References$ X | Optional$ True | Valid$ Card.nonLand+YouOwn+cmcLE4 | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | StackDescription$ None SVar:DBPlay:DB$ Play | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ4 | References$ X | Optional$ True | Valid$ Card.nonLand+YouOwn | ValidSA$ Spell.cmcLE4 | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | StackDescription$ None
DeckHas:Ability$Party DeckHas:Ability$Party
DeckHints:Type$Cleric|Rogue|Warrior|Wizard DeckHints:Type$Cleric|Rogue|Warrior|Wizard
Oracle:This spell costs {1} less to cast for each creature in your party. (Your party consists of up to one each of Cleric, Rogue, Warrior, and Wizard.)\nSearch your library for a card, put it into your hand, then shuffle your library. If you have a full party, you may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost. Oracle:This spell costs {1} less to cast for each creature in your party. (Your party consists of up to one each of Cleric, Rogue, Warrior, and Wizard.)\nSearch your library for a card, put it into your hand, then shuffle your library. If you have a full party, you may cast a spell with converted mana cost 4 or less from your hand without paying its mana cost.

View File

@@ -2,5 +2,5 @@ Name:Cry of the Carnarium
ManaCost:1 B B ManaCost:1 B B
Types:Sorcery Types:Sorcery
A:SP$ PumpAll | Cost$ 1 B B | ValidCards$ Creature | NumAtt$ -2 | NumDef$ -2 | IsCurse$ True | ReplaceDyingValid$ Creature | SubAbility$ ExileSomeCreatures | SpellDescription$ All creatures get -2/-2 until end of turn. Exile all creature cards in all graveyards that were put there from the battlefield this turn. If a creature would die this turn, exile it instead. A:SP$ PumpAll | Cost$ 1 B B | ValidCards$ Creature | NumAtt$ -2 | NumDef$ -2 | IsCurse$ True | ReplaceDyingValid$ Creature | SubAbility$ ExileSomeCreatures | SpellDescription$ All creatures get -2/-2 until end of turn. Exile all creature cards in all graveyards that were put there from the battlefield this turn. If a creature would die this turn, exile it instead.
SVar:ExileSomeCreatures:DB$ ChangeZone | Defined$ ThisTurnEntered_Graveyard_from_Battlefield_Creature.nonToken | Origin$ Graveyard | Destination$ Exile | SpellDescription$ Exile all creature cards in all graveyards that were put there from the battlefield this turn. SVar:ExileSomeCreatures:DB$ ChangeZone | Defined$ ValidGraveyard Creature.nonToken+ThisTurnEnteredFrom_Battlefield | Origin$ Graveyard | Destination$ Exile | SpellDescription$ Exile all creature cards in all graveyards that were put there from the battlefield this turn.
Oracle:All creatures get -2/-2 until end of turn. Exile all creature cards in all graveyards that were put there from the battlefield this turn. If a creature would die this turn, exile it instead. Oracle:All creatures get -2/-2 until end of turn. Exile all creature cards in all graveyards that were put there from the battlefield this turn. If a creature would die this turn, exile it instead.

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