Merge remote-tracking branch 'upstream/master' into deck-importer-decks-file-format

This commit is contained in:
leriomaggio
2021-10-31 08:39:22 +00:00
172 changed files with 3323 additions and 748 deletions

View File

@@ -730,7 +730,23 @@ public class AiController {
if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Modal, false);
}
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
// Account for possible Ward after the spell is fully targeted
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
// one is warded and can't be paid for.
if (sa.usesTargeting()) {
for (Card tgt : sa.getTargets().getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD)) {
int amount = tgt.getKeywordMagnitude(Keyword.WARD);
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player)) {
return AiPlayDecision.CantAfford;
}
}
}
}
if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Original, false);
}
@@ -1892,6 +1908,12 @@ public class AiController {
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
} else if ("Vermin".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0));
} else if ("SweepCreatures".equals(logic)) {
int maxCreatures = 0;
for (Player opp : player.getOpponents()) {
maxCreatures = Math.max(maxCreatures, opp.getCreaturesInPlay().size());
}
return Math.min(13, maxCreatures);
}
return max;
}

View File

@@ -452,7 +452,7 @@ public class ComputerUtil {
int mana = ComputerUtilMana.getAvailableManaEstimate(ai, false);
boolean cantAffordSoon = activate.getCMC() > mana + 1;
boolean wrongColor = !activate.determineColor().hasNoColorsExcept(ColorSet.fromNames(ComputerUtilCost.getAvailableManaColors(ai, ImmutableList.of())).getColor());
boolean wrongColor = !activate.getColor().hasNoColorsExcept(ColorSet.fromNames(ComputerUtilCost.getAvailableManaColors(ai, ImmutableList.of())).getColor());
// Only do this for spells, not activated abilities
// We can't pay for this spell even if we play another land, or have wrong colors

View File

@@ -904,7 +904,7 @@ public class ComputerUtilCard {
}
for (final Card crd : list) {
ColorSet color = crd.determineColor();
ColorSet color = crd.getColor();
if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1));
if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1));
if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1));
@@ -1676,7 +1676,7 @@ public class ComputerUtilCard {
}
}
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp, 0);
pumped.setPTBoost(c.getPTBoostTable());
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);

View File

@@ -589,6 +589,15 @@ public class ComputerUtilCost {
}
}
// Ward - will be accounted for when rechecking a targeted ability
if (sa.usesTargeting()) {
for (Card tgt : sa.getTargets().getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD)) {
extraManaNeeded += tgt.getKeywordMagnitude(Keyword.WARD);
}
}
}
// TODO: Alternate costs which involve both paying mana and tapping a card, e.g. Zahid, Djinn of the Lamp
// Current AI decides on each part separately, thus making it possible for the AI to cheat by
// tapping a mana source for mana and for the tap cost at the same time. Until this is improved, AI

View File

@@ -1962,7 +1962,7 @@ public class ComputerUtilMana {
if (!improvise) {
for (ManaCostShard toPay : cost) {
for (Card c : list) {
final int mask = c.determineColor().getColor() & toPay.getColorMask();
final int mask = c.getColor().getColor() & toPay.getColorMask();
if (mask != 0) {
convoked = c;
convoke.put(c, toPay);

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import forge.game.GameEntity;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
@@ -616,6 +617,28 @@ public class SpecialCardAi {
}
}
// Goblin Polka Band
public static class GoblinPolkaBand {
public static boolean consider(final Player ai, final SpellAbility sa) {
int maxPotentialTgts = Lists.newArrayList(Iterables.filter(ai.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED)).size();
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R");
int numTgts = Math.min(maxPotentialPayment, maxPotentialTgts);
if (numTgts == 0) {
return false;
}
// Set Announce
sa.getHostCard().setSVar("TgtNum", String.valueOf(numTgts));
// Simulate random targeting
List<GameEntity> validTgts = sa.getTargetRestrictions().getAllCandidates(sa, true);
sa.resetTargets();
sa.getTargets().addAll(Aggregates.random(validTgts, numTgts));
return true;
}
}
// Guilty Conscience
public static class GuiltyConscience {
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
@@ -1157,6 +1180,33 @@ public class SpecialCardAi {
}
}
// Power Struggle
public static class PowerStruggle {
public static boolean considerFirstTarget(final Player ai, final SpellAbility sa) {
Card firstTgt = (Card)Aggregates.random(sa.getTargetRestrictions().getAllCandidates(sa, true));
if (firstTgt != null) {
sa.getTargets().add(firstTgt);
return true;
} else {
return false;
}
}
public static boolean considerSecondTarget(final Player ai, final SpellAbility sa) {
Card firstTgt = sa.getParent().getTargetCard();
Iterable<Card> candidates = Iterables.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield),
Predicates.and(CardPredicates.sharesCardTypeWith(firstTgt), CardPredicates.isTargetableBy(sa)));
Card secondTgt = Aggregates.random(candidates);
if (secondTgt != null) {
sa.resetTargets();
sa.getTargets().add(secondTgt);
return true;
} else {
return false;
}
}
}
// Price of Progress
public static class PriceOfProgress {
public static boolean consider(final Player ai, final SpellAbility sa) {
@@ -1251,6 +1301,51 @@ public class SpecialCardAi {
}
}
// Savior of Ollenbock
public static class SaviorOfOllenbock {
public static boolean consider(final Player ai, final SpellAbility sa) {
CardCollection oppTargetables = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection threats = CardLists.filter(oppTargetables, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !ComputerUtilCard.isUselessCreature(card.getController(), card);
}
});
CardCollection ownTgts = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES);
// TODO: improve the conditions for when the AI is considered threatened (check the possibility of being attacked?)
int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
boolean threatened = !threats.isEmpty() && ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife()) || ai.getLifeLostLastTurn() + ai.getLifeLostThisTurn() > 0);
if (threatened) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threats));
} else if (!ownTgts.isEmpty()) {
Card target = ComputerUtilCard.getBestCreatureAI(ownTgts);
sa.getTargets().add(target);
int ownExiledValue = ComputerUtilCard.evaluateCreature(target), oppExiledValue = 0;
for (Card c : ai.getGame().getCardsIn(ZoneType.Exile)) {
if (c.getExiledWith() == sa.getHostCard()) {
if (c.getOwner() == ai) {
ownExiledValue += ComputerUtilCard.evaluateCreature(c);
} else {
oppExiledValue += ComputerUtilCard.evaluateCreature(c);
}
}
}
if (ownExiledValue > oppExiledValue + 150) {
sa.getHostCard().setSVar("SacMe", "5");
} else {
sa.getHostCard().removeSVar("SacMe");
}
} else if (!threats.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threats));
}
return sa.isTargetNumberValid();
}
}
// Sorin, Vengeful Bloodlord
public static class SorinVengefulBloodlord {
public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -5,6 +5,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.*;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -442,16 +443,15 @@ public class AnimateAi extends SpellAbilityAi {
}
// colors to be added or changed to
String tmpDesc = "";
ColorSet finalColors = ColorSet.getNullColor();
if (sa.hasParam("Colors")) {
final String colors = sa.getParam("Colors");
if (colors.equals("ChosenColor")) {
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
finalColors = ColorSet.fromNames(source.getChosenColors());
} else {
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
finalColors = ColorSet.fromNames(colors.split(","));
}
}
final String finalDesc = tmpDesc;
// abilities to add to the animated being
final List<String> abilities = Lists.newArrayList();
@@ -483,7 +483,7 @@ public class AnimateAi extends SpellAbilityAi {
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
}
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc,
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalColors,
keywords, removeKeywords, hiddenKeywords,
abilities, triggers, replacements, stAbs,
timestamp);

View File

@@ -195,14 +195,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
if (sa.isReplacementAbility() && "Command".equals(sa.getParam("Destination")) && "ReplacedCard".equals(sa.getParam("Defined"))) {
// Process the commander replacement effect ("return to Command zone instead")
return doReturnCommanderLogic(sa, aiPlayer);
}
if ("Always".equals(sa.getParam("AILogic"))) {
if ("Always".equals(aiLogic)) {
return true;
} else if ("IfNotBuffed".equals(sa.getParam("AILogic"))) {
} else if ("IfNotBuffed".equals(aiLogic)) {
if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) {
return true; // debuffed by opponent's auras to the level that it becomes useless
}
@@ -215,6 +217,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
return delta <= 0;
} else if ("SaviorOfOllenbock".equals(aiLogic)) {
return SpecialCardAi.SaviorOfOllenbock.consider(aiPlayer, sa);
}
if (sa.isHidden()) {

View File

@@ -24,6 +24,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
@@ -146,6 +147,16 @@ public class ChooseCardAi extends SpellAbilityAi {
return checkApiLogic(ai, sa);
}
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
if (aiLogic.equals("AtOppEOT")) {
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/

View File

@@ -27,6 +27,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
@@ -52,6 +53,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
}
} else if ("GideonBlackblade".equals(aiLogic)) {
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
} else if ("AtOppEOT".equals(aiLogic)) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
} else if ("Always".equals(aiLogic)) {
return true;
}
@@ -244,7 +248,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
//if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (imprinted.determineColor().hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
if (imprinted.getColor().hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}

View File

@@ -11,9 +11,21 @@ public class ChooseNumberAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
if (aiLogic.isEmpty()) {
return false;
} else if (aiLogic.equals("SweepCreatures")) {
int ownCreatureCount = aiPlayer.getCreaturesInPlay().size();
int oppMaxCreatureCount = 0;
for (Player opp : aiPlayer.getOpponents()) {
oppMaxCreatureCount = Math.max(oppMaxCreatureCount, opp.getCreaturesInPlay().size());
}
// TODO: maybe check if the AI is actually pressured and/or check the total value of the creatures on both sides of the board
return ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < oppMaxCreatureCount;
}
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();

View File

@@ -4,6 +4,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -80,6 +81,10 @@ public class ControlExchangeAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ("PowerStruggle".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.PowerStruggle.considerSecondTarget(aiPlayer, sa);
}
// for TrigTwoTargets logic, only get the opponents' cards for the first target
CardCollectionView unfilteredList = "TrigTwoTargets".equals(sa.getParam("AILogic")) ?
aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield) :

View File

@@ -143,6 +143,7 @@ public class PermanentCreatureAi extends PermanentAi {
boolean willDiscardNow = isOwnEOT && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
boolean willDieNow = combat != null && ComputerUtilCombat.lifeInSeriousDanger(ai, combat);
boolean wantToCastInMain1 = ph.is(PhaseType.MAIN1, ai) && ComputerUtil.castPermanentInMain1(ai, sa);
boolean isCommander = card.isCommander();
// figure out if the card might be a valuable blocker
boolean valuableBlocker = false;
@@ -178,6 +179,9 @@ public class PermanentCreatureAi extends PermanentAi {
if (hasFloatMana || willDiscardNow || willDieNow) {
// Will lose mana in pool or about to discard a card in cleanup or about to die in combat, so use this opportunity
return true;
} else if (isCommander && isMyMain1OrLater) {
// Don't hold out specifically if this card is a commander, since otherwise it leads to stupid AI choices
return true;
} else if (wantToCastInMain1) {
// Would rather cast it in Main 1 or as soon as possible anyway, so go for it
return isMyMain1OrLater;

View File

@@ -436,6 +436,11 @@ public class PumpAi extends PumpAiBase {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
if ("PowerStruggle".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.PowerStruggle.considerFirstTarget(ai, sa);
}
if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);

View File

@@ -1,11 +1,6 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
@@ -48,6 +43,10 @@ public class TapAi extends TapAiBase {
final Card source = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
if ("GoblinPolkaBand".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.GoblinPolkaBand.consider(ai, sa);
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
return false;
}

View File

@@ -98,9 +98,14 @@ public class TokenAi extends SpellAbilityAi {
sa.getRootAbility().setXManaCostPaid(x);
}
if (x <= 0) {
if ("RandomPT".equals(sa.getParam("AILogic"))) {
// e.g. Necropolis of Azar - we're guaranteed at least 1 toughness from the ability
x = 1;
} else {
return false; // 0 tokens or 0 toughness token(s)
}
}
}
if (canInterruptSacrifice(ai, sa, actualToken, tokenAmount)) {
return true;

View File

@@ -276,12 +276,9 @@ public class GameCopier {
zoneOwner = playerMap.get(c.getController());
newCard.setController(zoneOwner, 0);
int setPower = c.getSetPower();
int setToughness = c.getSetToughness();
if (setPower != Integer.MAX_VALUE || setToughness != Integer.MAX_VALUE) {
// TODO: Copy the full list with timestamps.
newCard.addNewPT(setPower, setToughness, newGame.getNextTimestamp());
}
newCard.setPTTable(c.getSetPTTable());
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
newCard.setPTBoost(c.getPTBoostTable());
newCard.setDamage(c.getDamage());

View File

@@ -266,7 +266,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
}
deferredSections = null; // set to null, just in case!
if (includeCardsFromUnspecifiedSet && smartCardArtSelection)
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition, true);
}
private void validateDeferredSections() {
@@ -351,7 +351,15 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return String.format("%d %s", amount, originalRequestCandidate);
return String.format("%d %s", amount, poolCardRequest);
}
public void optimizeMainCardArt() {
Map<DeckSection, ArrayList<String>> cardsWithNoEdition = new EnumMap<>(DeckSection.class);
List<String> mainCards = new ArrayList<>();
for (Entry<PaperCard, Integer> e: getMain())
mainCards.add(e.getKey().getName());
cardsWithNoEdition.put(DeckSection.Main, getAllCardNamesWithNoSpecifiedEdition(mainCards));
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition, false);
}
private ArrayList<String> getAllCardNamesWithNoSpecifiedEdition(List<String> cardsInSection) {
ArrayList<String> cardNamesWithNoEdition = new ArrayList<>();
List<Pair<String, Integer>> cardRequests = CardPool.processCardList(cardsInSection);
@@ -364,7 +372,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return cardNamesWithNoEdition;
}
private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition) {
private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition, boolean multiArtPrint) {
StaticData data = StaticData.instance();
// Get current Card Art Preference Settings
boolean isCardArtPreferenceLatestArt = data.cardArtPreferenceIsLatest();
@@ -397,13 +405,13 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
int totalToAddToPool = cp.getValue();
// A. Skip cards not requiring any update, because they add the edition specified!
if (!cardNamesWithNoEditionInSection.contains(card.getName())) {
addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint);
continue;
}
// B. Determine if current card requires update
boolean cardArtNeedsOptimisation = this.isCardArtUpdateRequired(card, releaseDatePivotEdition);
if (!cardArtNeedsOptimisation) {
addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint);
continue;
}
PaperCard alternativeCardPrint = data.getAlternativeCardPrint(card, releaseDatePivotEdition,
@@ -412,20 +420,22 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
isExpansionTheMajorityInThePool,
isPoolModernFramed);
if (alternativeCardPrint == null) // no alternative found, add original card in Pool
addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint);
else
addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil());
addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil(), multiArtPrint);
}
parts.put(deckSection, newPool);
}
}
private void addCardToPool(CardPool pool, PaperCard card, int totalToAdd, boolean isFoil) {
private void addCardToPool(CardPool pool, PaperCard card, int totalToAdd, boolean isFoil, boolean multiArtPrint) {
StaticData data = StaticData.instance();
if (card.getArtIndex() != IPaperCard.NO_ART_INDEX && card.getArtIndex() != IPaperCard.DEFAULT_ART_INDEX)
pool.add(isFoil ? card.getFoiled() : card, totalToAdd); // art index requested, keep that way!
else {
int artCount = data.getCardArtCount(card);
int artCount = 1;
if (multiArtPrint)
artCount = data.getCardArtCount(card);
if (artCount > 1)
addAlternativeCardPrintInPoolWithMultipleArt(card, pool, totalToAdd, artCount);
else

View File

@@ -24,7 +24,7 @@ public class ForgeScript {
public static boolean cardStateHasProperty(CardState cardState, String property, Player sourceController,
Card source, CardTraitBase spellAbility) {
final boolean isColorlessSource = cardState.getCard().hasKeyword("Colorless Damage Source", cardState);
final ColorSet colors = cardState.getCard().determineColor(cardState);
final ColorSet colors = cardState.getCard().getColor(cardState);
if (property.contains("White") || property.contains("Blue") || property.contains("Black")
|| property.contains("Red") || property.contains("Green")) {
boolean mustHave = !property.startsWith("non");

View File

@@ -108,7 +108,7 @@ public final class GameActionUtil {
if (lkicheck) {
// double freeze tracker, so it doesn't update view
game.getTracker().freeze();
source.clearChangedCardKeywords(false);
source.clearStaticChangedCardKeywords(false);
CardCollection preList = new CardCollection(source);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
}
@@ -189,6 +189,11 @@ public final class GameActionUtil {
desc.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(desc.toString());
newSA.putParam("AfterDescription", "(Disturbed)");
final String type = source.getAlternateState().getType().toString();
if (!type.contains("Creature")) {
final String name = source.getAlternateState().getName();
newSA.putParam("StackDescription", name + "" + type + " (Disturbed)");
}
newSA.setAlternativeCost(AlternativeCost.Disturb);
newSA.getRestrictions().setZone(ZoneType.Graveyard);
@@ -361,7 +366,25 @@ public final class GameActionUtil {
if (sa == null || !sa.isSpell()) {
return costs;
}
final Card source = sa.getHostCard();
Card source = sa.getHostCard();
final Game game = source.getGame();
boolean lkicheck = false;
Card newHost = ((Spell)sa).getAlternateHost(source);
if (newHost != null) {
source = newHost;
lkicheck = true;
}
if (lkicheck) {
// double freeze tracker, so it doesn't update view
game.getTracker().freeze();
source.clearStaticChangedCardKeywords(false);
CardCollection preList = new CardCollection(source);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
}
for (KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Buyback")) {
@@ -404,6 +427,16 @@ public final class GameActionUtil {
// Surge while having OptionalCost is none of them
}
// reset static abilities
if (lkicheck) {
game.getAction().checkStaticAbilities(false);
// clear delayed changes, this check should not have updated the view
game.getTracker().clearDelayed();
// need to unfreeze tracker
game.getTracker().unfreeze();
}
return costs;
}

View File

@@ -211,7 +211,7 @@ public class StaticEffect {
// remove set P/T
if (hasParam("SetPower") || hasParam("SetToughness")) {
affectedCard.removeNewPT(getTimestamp());
affectedCard.removeNewPT(getTimestamp(), ability.getId());
}
// remove P/T bonus
@@ -261,7 +261,15 @@ public class StaticEffect {
}
if (hasParam("GainTextOf")) {
affectedCard.removeTextChangeState(getTimestamp());
affectedCard.removeChangedName(getTimestamp(), ability.getId());
affectedCard.removeChangedManaCost(getTimestamp(), ability.getId());
affectedCard.removeColor(getTimestamp(), ability.getId());
affectedCard.removeChangedCardTypes(getTimestamp(), ability.getId());
affectedCard.removeChangedCardTraits(getTimestamp(), ability.getId());
affectedCard.removeChangedCardKeywords(getTimestamp(), ability.getId());
affectedCard.removeNewPT(getTimestamp(), ability.getId());
affectedCard.updateChangedText();
}
if (hasParam("Goad")) {

View File

@@ -165,6 +165,15 @@ public class AbilityUtils {
c = sacrificed.getFirst().getEnchantingCard();
}
}
} else if (defined.equals("TopOfGraveyard")) {
final CardCollectionView grave = hostCard.getController().getCardsIn(ZoneType.Graveyard);
if (grave.size() > 0) { // TopOfLibrary or BottomOfLibrary
c = grave.getLast();
} else {
// we don't want this to fall through and return the "Self"
return cards;
}
}
else if (defined.endsWith("OfLibrary")) {
final CardCollectionView lib = hostCard.getController().getCardsIn(ZoneType.Library);
@@ -1789,7 +1798,7 @@ public class AbilityUtils {
if (sq[0].contains("HasNumChosenColors")) {
int sum = 0;
for (Card card : getDefinedCards(c, sq[1], sa)) {
sum += card.determineColor().getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors();
sum += card.getColor().getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors();
}
return sum;
}
@@ -1990,7 +1999,7 @@ public class AbilityUtils {
// Count$CardMulticolor.<numMC>.<numNotMC>
if (sq[0].contains("CardMulticolor")) {
final boolean isMulti = c.determineColor().isMulticolor();
final boolean isMulti = c.getColor().isMulticolor();
return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), expr, c, ctb);
}
// Count$Madness.<True>.<False>
@@ -2046,7 +2055,7 @@ public class AbilityUtils {
}
if (sq[0].contains("CardNumColors")) {
return doXMath(c.determineColor().countColors(), expr, c, ctb);
return doXMath(c.getColor().countColors(), expr, c, ctb);
}
if (sq[0].contains("CardNumAttacksThisTurn")) {
return doXMath(c.getDamageHistory().getCreatureAttacksThisTurn(), expr, c, ctb);
@@ -2369,7 +2378,7 @@ public class AbilityUtils {
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
byte n = 0;
for (final Card card : list) {
n |= card.determineColor().getColor();
n |= card.getColor().getColor();
}
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
}
@@ -2827,7 +2836,7 @@ public class AbilityUtils {
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
byte n = 0;
for (final Card card : list) {
n |= card.determineColor().getColor();
n |= card.getColor().getColor();
}
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
}
@@ -3766,7 +3775,7 @@ public class AbilityUtils {
someCards = CardLists.filter(someCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.determineColor().isMulticolor();
return c.getColor().isMulticolor();
}
});
}
@@ -3775,7 +3784,7 @@ public class AbilityUtils {
someCards = CardLists.filter(someCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.determineColor().isMonoColor();
return c.getColor().isMonoColor();
}
});
}

View File

@@ -462,7 +462,7 @@ public abstract class SpellAbilityEffect {
final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp());
eff.setName(name);
eff.setColor(hostCard.determineColor().getColor());
eff.setColor(hostCard.getColor().getColor());
// if name includes emblem then it should be one
if (name.startsWith("Emblem")) {
eff.setEmblem(true);

View File

@@ -8,12 +8,12 @@ import com.google.common.collect.ImmutableList;
import forge.GameCommand;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -88,16 +88,15 @@ public class AnimateAllEffect extends AnimateEffectBase {
}
// colors to be added or changed to
String tmpDesc = "";
ColorSet finalColors = ColorSet.getNullColor();
if (sa.hasParam("Colors")) {
final String colors = sa.getParam("Colors");
if (colors.equals("ChosenColor")) {
tmpDesc = CardUtil.getShortColorsString(host.getChosenColors());
finalColors = ColorSet.fromNames(host.getChosenColors());
} else {
tmpDesc = CardUtil.getShortColorsString(new ArrayList<>(Arrays.asList(colors.split(","))));
finalColors = ColorSet.fromNames(colors.split(","));
}
}
final String finalDesc = tmpDesc;
// abilities to add to the animated being
final List<String> abilities = new ArrayList<>();
@@ -134,7 +133,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa);
for (final Card c : list) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors,
keywords, removeKeywords, hiddenKeywords,
abilities, triggers, replacements, ImmutableList.of(),
timestamp);

View File

@@ -6,10 +6,10 @@ import java.util.List;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
@@ -100,17 +100,15 @@ public class AnimateEffect extends AnimateEffectBase {
}
// colors to be added or changed to
String tmpDesc = "";
ColorSet finalColors = ColorSet.getNullColor();
if (sa.hasParam("Colors")) {
final String colors = sa.getParam("Colors");
if (colors.equals("ChosenColor")) {
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
finalColors = ColorSet.fromNames(source.getChosenColors());
} else {
tmpDesc = CardUtil.getShortColorsString(Arrays.asList(colors.split(",")));
finalColors = ColorSet.fromNames(Arrays.asList(colors.split(",")));
}
}
final String finalDesc = tmpDesc;
// abilities to add to the animated being
final List<String> abilities = Lists.newArrayList();
@@ -156,12 +154,12 @@ public class AnimateEffect extends AnimateEffectBase {
}
for (final Card c : tgts) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors,
keywords, removeKeywords, hiddenKeywords,
abilities, triggers, replacements, stAbs, timestamp);
if (sa.hasParam("Name")) {
c.addChangedName(sa.getParam("Name"), timestamp);
c.addChangedName(sa.getParam("Name"), false, timestamp, 0);
}
// give sVars

View File

@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.Game;
@@ -42,7 +43,7 @@ import forge.game.trigger.TriggerHandler;
public abstract class AnimateEffectBase extends SpellAbilityEffect {
public static void doAnimate(final Card c, final SpellAbility sa, final Integer power, final Integer toughness,
final CardType addType, final CardType removeType, final String colors,
final CardType addType, final CardType removeType, final ColorSet colors,
final List<String> keywords, final List<String> removeKeywords, final List<String> hiddenKeywords,
List<String> abilities, final List<String> triggers, final List<String> replacements, final List<String> stAbs,
final long timestamp) {
@@ -89,7 +90,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
}
if ((power != null) || (toughness != null)) {
c.addNewPT(power, toughness, timestamp);
c.addNewPT(power, toughness, timestamp, 0);
}
if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) {
@@ -159,7 +160,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
public void run() {
doUnanimate(c, timestamp);
c.removeChangedName(timestamp);
c.removeChangedName(timestamp, 0);
c.updateStateForView();
game.fireEvent(new GameEventCardStatsChanged(c));
@@ -218,7 +219,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
* a long.
*/
static void doUnanimate(final Card c, final long timestamp) {
c.removeNewPT(timestamp);
c.removeNewPT(timestamp, 0);
c.removeChangedCardKeywords(timestamp, 0);

View File

@@ -390,6 +390,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (sa.hasParam("Adapt")) {
game.getTriggerHandler().runTrigger(TriggerType.Adapt, AbilityKey.mapFromCard(gameCard), false);
}
if (sa.hasParam("Training")) {
game.getTriggerHandler().runTrigger(TriggerType.Trains, AbilityKey.mapFromCard(gameCard), false);
}
} else {
// adding counters to something like re-suspend cards
// etbcounter should apply multiplier

View File

@@ -98,13 +98,18 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect {
}
private Card getNeighboringCard(Card c, int direction) {
// Currently gets the nearest (in zone order) card to the left or to the right of the designated one by type
// Currently gets the nearest (in zone order) card to the left or to the right of the designated one by type,
// as well as cards attachments by the same controller that are visually located next to the requested card.
Player controller = c.getController();
ArrayList<Card> ownAttachments = Lists.newArrayList();
ArrayList<Card> cardsOTB = Lists.newArrayList(CardLists.filter(
controller.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (c.isCreature()) {
if (card.isAttachedToEntity(c) && card.getController() == controller) {
ownAttachments.add(card);
return true;
} else if (c.isCreature()) {
return card.isCreature();
} else if (c.isPlaneswalker() || c.isArtifact() || (c.isEnchantment() && !c.isAura())) {
return card.isPlaneswalker() || card.isArtifact() || (c.isEnchantment() && !c.isAura());
@@ -118,6 +123,12 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect {
}
));
// Chance to hit an attachment
float hitAttachment = 0.50f;
if (!ownAttachments.isEmpty() && direction < 0 && MyRandom.getRandom().nextFloat() <= hitAttachment) {
return Aggregates.random(ownAttachments);
}
int loc = cardsOTB.indexOf(c);
if (direction < 0 && loc > 0) {
return cardsOTB.get(loc - 1);

View File

@@ -70,12 +70,12 @@ public class LifeExchangeVariantEffect extends SpellAbilityEffect {
if ((pLife > num) && p.canLoseLife()) {
final int diff = pLife - num;
p.loseLife(diff, false, false);
source.addNewPT(power, toughness, timestamp);
source.addNewPT(power, toughness, timestamp, 0);
game.fireEvent(new GameEventCardStatsChanged(source));
} else if ((num > pLife) && p.canGainLife()) {
final int diff = num - pLife;
p.gainLife(diff, source, sa);
source.addNewPT(power, toughness, timestamp);
source.addNewPT(power, toughness, timestamp, 0);
game.fireEvent(new GameEventCardStatsChanged(source));
} else {
// do nothing if they are equal

View File

@@ -189,7 +189,7 @@ public class ManaEffect extends SpellAbilityEffect {
res, sa.getActivatingPlayer(), card, sa);
byte colors = 0;
for (Card c : list) {
colors |= c.determineColor().getColor();
colors |= c.getColor().getColor();
}
if (colors == 0) return;
abMana.setExpressChoice(ColorSet.fromMask(colors));

View File

@@ -34,7 +34,7 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
}
// current color of source card
final ColorSet color = source.determineColor();
final ColorSet color = source.getColor();
if (color.isColorless()) {
return;
}

View File

@@ -56,8 +56,8 @@ public class PowerExchangeEffect extends SpellAbilityEffect {
final long timestamp = game.getNextTimestamp();
c1.addNewPT(power2, null, timestamp);
c2.addNewPT(power1, null, timestamp);
c1.addNewPT(power2, null, timestamp, 0);
c2.addNewPT(power1, null, timestamp, 0);
game.fireEvent(new GameEventCardStatsChanged(c1));
game.fireEvent(new GameEventCardStatsChanged(c2));
@@ -70,8 +70,8 @@ public class PowerExchangeEffect extends SpellAbilityEffect {
@Override
public void run() {
c1.removeNewPT(timestamp);
c2.removeNewPT(timestamp);
c1.removeNewPT(timestamp, 0);
c2.removeNewPT(timestamp, 0);
game.fireEvent(new GameEventCardStatsChanged(c1));
game.fireEvent(new GameEventCardStatsChanged(c2));
}

View File

@@ -64,7 +64,7 @@ public class ProtectAllEffect extends SpellAbilityEffect {
}
} else if (sa.getParam("Gains").equals("TargetedCardColor")) {
for (final Card c : sa.getSATargetingCard().getTargets().getTargetCards()) {
ColorSet cs = c.determineColor();
ColorSet cs = c.getColor();
for (byte col : MagicColor.WUBRG) {
if (cs.hasAnyColor(col))
gains.add(MagicColor.toLongString(col).toLowerCase());

View File

@@ -171,7 +171,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
}
triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
creator.addTokensCreatedThisTurn();
creator.addTokensCreatedThisTurn(tok);
if (clone) {
moved.setCloneOrigin(host);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
package forge.game.card;
public class CardChangedName {
protected String newName;
protected boolean addNonLegendaryCreatureNames = false;
public CardChangedName(String newName, boolean addNonLegendaryCreatureNames) {
this.newName = newName;
this.addNonLegendaryCreatureNames = addNonLegendaryCreatureNames;
}
public String getNewName() {
return newName;
}
public boolean isOverwrite() {
return newName != null;
}
public boolean isAddNonLegendaryCreatureNames() {
return addNonLegendaryCreatureNames;
}
}

View File

@@ -1,21 +0,0 @@
package forge.game.card;
public class CardChangedWord {
private final String originalWord,
newWord;
public CardChangedWord(final String originalWord, final String newWord) {
this.originalWord = originalWord;
this.newWord = newWord;
}
public String getOriginalWord() {
return originalWord;
}
public String getNewWord() {
return newWord;
}
}

View File

@@ -1,15 +1,31 @@
package forge.game.card;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
public final class CardChangedWords {
public final class CardChangedWords extends ForwardingMap<String, String> {
private final SortedMap<Long, CardChangedWord> map = Maps.newTreeMap();
class WordHolder {
public String oldWord;
public String newWord;
public boolean clear = false;
WordHolder() {
this.clear = true;
}
WordHolder(String oldWord, String newWord) {
this.oldWord = oldWord;
this.newWord = newWord;
}
}
private final Table<Long, Long, WordHolder> map = TreeBasedTable.create();
private boolean isDirty = false;
private Map<String, String> resultCache = Maps.newHashMap();
@@ -17,19 +33,28 @@ public final class CardChangedWords {
public CardChangedWords() {
}
public Long add(final long timestamp, final String originalWord, final String newWord) {
public Long addEmpty(final long timestamp, final long staticId) {
final Long stamp = Long.valueOf(timestamp);
map.put(stamp, new CardChangedWord(originalWord, newWord));
map.put(stamp, staticId, new WordHolder()); // Table doesn't allow null value
isDirty = true;
return stamp;
}
public boolean remove(final Long timestamp) {
public Long add(final long timestamp, final long staticId, final String originalWord, final String newWord) {
final Long stamp = Long.valueOf(timestamp);
map.put(stamp, staticId, new WordHolder(originalWord, newWord));
isDirty = true;
return map.remove(timestamp) != null;
return stamp;
}
public void removeAll() {
public boolean remove(final Long timestamp, final long staticId) {
isDirty = true;
return map.remove(timestamp, staticId) != null;
}
@Override
public void clear() {
super.clear();
map.clear();
isDirty = true;
}
@@ -46,26 +71,33 @@ public final class CardChangedWords {
* @return a map of strings to strings, where each changed word in this
* object is mapped to its corresponding replacement word.
*/
public Map<String, String> toMap() {
@Override
protected Map<String, String> delegate() {
refreshCache();
return resultCache;
}
private void refreshCache() {
if (isDirty) {
resultCache = Maps.newHashMap();
for (final CardChangedWord ccw : this.map.values()) {
resultCache.clear();
for (final WordHolder ccw : this.map.values()) {
// is empty pair is for resetting the data, it is done for Volraths Shapeshifter
if (ccw.clear) {
resultCache.clear();
continue;
}
// changes because a->b and b->c (resulting in a->c)
final Map<String, String> toBeChanged = Maps.newHashMap();
for (final Entry<String, String> e : resultCache.entrySet()) {
if (e.getValue().equals(ccw.getOriginalWord())) {
toBeChanged.put(e.getKey(), ccw.getNewWord());
if (e.getValue().equals(ccw.oldWord)) {
toBeChanged.put(e.getKey(), ccw.newWord);
}
}
resultCache.putAll(toBeChanged);
// the actual change (b->c)
resultCache.put(ccw.getOriginalWord(), ccw.getNewWord());
resultCache.put(ccw.oldWord, ccw.newWord);
}
// TODO should that be removed?

View File

@@ -17,8 +17,7 @@
*/
package forge.game.card;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.card.ColorSet;
/**
* <p>
@@ -39,15 +38,8 @@ public class CardColor {
return this.additional;
}
private final long timestamp;
public final long getTimestamp() {
return this.timestamp;
}
CardColor(final String colors, final boolean addToColors, final long timestamp) {
final ManaCost mc = new ManaCost(new ManaCostParser(colors));
this.colorMask = mc.getColorProfile();
CardColor(final ColorSet colors, final boolean addToColors) {
this.colorMask = colors.getColor();
this.additional = addToColors;
this.timestamp = timestamp;
}
}

View File

@@ -136,14 +136,13 @@ public class CardFactory {
// change the color of the copy (eg: Fork)
if (sourceSA.hasParam("CopyIsColor")) {
String tmp = "";
ColorSet finalColors = ColorSet.getNullColor();
final String newColor = sourceSA.getParam("CopyIsColor");
if (newColor.equals("ChosenColor")) {
tmp = CardUtil.getShortColorsString(source.getChosenColors());
finalColors = ColorSet.fromNames(source.getChosenColors());
} else {
tmp = CardUtil.getShortColorsString(Lists.newArrayList(newColor.split(",")));
finalColors = ColorSet.fromNames(newColor.split(","));
}
final String finalColors = tmp;
c.addColor(finalColors, !sourceSA.hasParam("OverwriteColors"), c.getTimestamp(), 0, false);
}
@@ -622,24 +621,24 @@ public class CardFactory {
// if something is cloning a flip card, copy both original and
// flipped state
final CardState ret1 = new CardState(out, CardStateName.Original);
ret1.copyFrom(in.getState(CardStateName.Original, true), false);
ret1.copyFrom(in.getState(CardStateName.Original), false);
result.put(CardStateName.Original, ret1);
final CardState ret2 = new CardState(out, CardStateName.Flipped);
ret2.copyFrom(in.getState(CardStateName.Flipped, true), false);
ret2.copyFrom(in.getState(CardStateName.Flipped), false);
result.put(CardStateName.Flipped, ret2);
} else if (in.isAdventureCard()) {
final CardState ret1 = new CardState(out, CardStateName.Original);
ret1.copyFrom(in.getState(CardStateName.Original, true), false);
ret1.copyFrom(in.getState(CardStateName.Original), false);
result.put(CardStateName.Original, ret1);
final CardState ret2 = new CardState(out, CardStateName.Adventure);
ret2.copyFrom(in.getState(CardStateName.Adventure, true), false);
ret2.copyFrom(in.getState(CardStateName.Adventure), false);
result.put(CardStateName.Adventure, ret2);
} else {
// in all other cases just copy the current state to original
final CardState ret = new CardState(out, CardStateName.Original);
ret.copyFrom(in.getState(in.getCurrentStateName(), true), false);
ret.copyFrom(in.getState(in.getCurrentStateName()), false);
result.put(CardStateName.Original, ret);
}
@@ -832,7 +831,7 @@ public class CardFactory {
final CardStateName state = top.getCurrentStateName();
final CardState ret = new CardState(card, state);
if (top.isCloned()) {
ret.copyFrom(top.getState(state, true), false);
ret.copyFrom(top.getState(state), false);
} else {
ret.copyFrom(top.getOriginalState(state), false);
}
@@ -852,7 +851,7 @@ public class CardFactory {
// For face down, flipped, transformed, melded or MDFC card, also copy the original state to avoid crash
if (state != CardStateName.Original) {
final CardState ret1 = new CardState(card, CardStateName.Original);
ret1.copyFrom(top.getState(CardStateName.Original, true), false);
ret1.copyFrom(top.getState(CardStateName.Original), false);
result.put(CardStateName.Original, ret1);
}

View File

@@ -338,7 +338,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
ColorSet color = crd.determineColor();
ColorSet color = crd.getColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i]))
map[i]++;
@@ -376,7 +376,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
ColorSet color = crd.determineColor();
ColorSet color = crd.getColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i]))
map[i]++;
@@ -407,7 +407,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
ColorSet color = crd.determineColor();
ColorSet color = crd.getColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(colorRestrictions.get(i))) {
map[i]++;
@@ -1842,7 +1842,7 @@ public class CardFactoryUtil {
if (card.isPermanent()) {
final String abPump = "DB$ Pump | Defined$ Remembered | KW$ Haste | PumpZone$ Stack "
+ "| ConditionDefined$ Remembered | ConditionPresent$ Creature | Duration$ UntilLoseControlOfHost";
final AbilitySub saPump = (AbilitySub)AbilityFactory.getAbility(abPump, card);
final AbilitySub saPump = (AbilitySub) AbilityFactory.getAbility(abPump, card);
String dbClean = "DB$ Cleanup | ClearRemembered$ True";
final AbilitySub saCleanup = (AbilitySub) AbilityFactory.getAbility(dbClean, card);
@@ -1856,6 +1856,21 @@ public class CardFactoryUtil {
inst.addTrigger(parsedUpkeepTrig);
inst.addTrigger(parsedPlayTrigger);
} else if (keyword.equals("Training")) {
final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True | " +
"IsPresent$ Creature.attacking+Other+powerGTX | TriggerDescription$ Training (" +
inst.getReminderText() + ")";
final String effect = "DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | Defined$ Self | Training$ True";
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
SpellAbility sa = AbilityFactory.getAbility(effect, card);
trigger.setSVar("X", "Count$CardPower");
sa.setIntrinsic(intrinsic);
trigger.setOverridingAbility(sa);
inst.addTrigger(trigger);
} else if (keyword.startsWith("Tribute")) {
// use hardcoded ability name
final String abStr = "TrigNotTribute";

View File

@@ -264,7 +264,7 @@ public final class CardPredicates {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.determineColor().hasAnyColor(color);
return c.getColor().hasAnyColor(color);
}
};
} // getColor()
@@ -273,7 +273,7 @@ public final class CardPredicates {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.determineColor().hasExactlyColor(color);
return c.getColor().hasExactlyColor(color);
}
};
}
@@ -282,7 +282,7 @@ public final class CardPredicates {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.determineColor().isColorless();
return c.getColor().isColorless();
}
};
}

View File

@@ -689,7 +689,7 @@ public class CardProperty {
break;
case "MostProminentColor":
byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield));
if (!card.determineColor().hasAnyColor(mask))
if (!card.getColor().hasAnyColor(mask))
return false;
break;
case "LastCastThisTurn":
@@ -703,7 +703,7 @@ public class CardProperty {
if (castSA == null) {
return false;
}
if (!card.determineColor().hasAnyColor(castSA.getPayingColors().getColor())) {
if (!card.getColor().hasAnyColor(castSA.getPayingColors().getColor())) {
return false;
}
break;

View File

@@ -120,4 +120,22 @@ public class CardTraitChanges implements Cloneable {
throw new RuntimeException("CardTraitChanges : clone() error", ex);
}
}
public void changeText() {
for (SpellAbility sa : this.abilities) {
sa.changeText();
}
for (Trigger tr : this.triggers) {
tr.changeText();
}
for (ReplacementEffect re : this.replacements) {
re.changeText();
}
for (StaticAbility sa : this.staticAbilities) {
sa.changeText();
}
}
}

View File

@@ -247,7 +247,7 @@ public final class CardUtil {
newCopy.setCounters(Maps.newHashMap(in.getCounters()));
newCopy.setColor(in.determineColor().getColor());
newCopy.setColor(in.getColor().getColor());
newCopy.setPhasedOut(in.isPhasedOut());
newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn());
@@ -338,7 +338,7 @@ public final class CardUtil {
byte combinedColor = 0;
for (Card tgt : tgts) {
ColorSet cs = tgt.determineColor();
ColorSet cs = tgt.getColor();
for (byte color : MagicColor.WUBRG) {
if(!cs.hasAnyColor(color))
continue;
@@ -362,7 +362,7 @@ public final class CardUtil {
public static ColorSet getColorsYouCtrl(final Player p) {
byte b = 0;
for (Card c : p.getCardsIn(ZoneType.Battlefield)) {
b |= c.determineColor().getColor();
b |= c.getColor().getColor();
}
return ColorSet.fromMask(b);
}

View File

@@ -1115,16 +1115,16 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.RightSplitColors);
}
void updateColors(Card c) {
set(TrackableProperty.Colors, c.determineColor());
set(TrackableProperty.Colors, c.getColor());
}
void updateColors(CardState c) {
set(TrackableProperty.Colors, ColorSet.fromMask(c.getColor()));
}
void setOriginalColors(Card c) {
set(TrackableProperty.OriginalColors, c.determineColor());
set(TrackableProperty.OriginalColors, c.getColor());
if (c.isSplitCard()) {
set(TrackableProperty.LeftSplitColors, c.determineColor(c.getState(CardStateName.LeftSplit)));
set(TrackableProperty.RightSplitColors, c.determineColor(c.getState(CardStateName.RightSplit)));
set(TrackableProperty.LeftSplitColors, c.getColor(c.getState(CardStateName.LeftSplit)));
set(TrackableProperty.RightSplitColors, c.getColor(c.getState(CardStateName.RightSplit)));
}
}
void updateHasChangeColors(boolean hasChangeColor) {

View File

@@ -71,6 +71,8 @@ public enum CounterEnumType {
CORRUPTION("CRPTN", 210, 121, 210),
CROAK("CROAK", 155, 255, 5),
CREDIT("CRDIT", 188, 197, 234),
CRYSTAL("CRYST", 255, 85, 206),
@@ -171,6 +173,8 @@ public enum CounterEnumType {
INTERVENTION("INTRV", 205, 203, 105),
INVITATION("INVIT", 205, 0, 26),
ISOLATION("ISOLT", 250, 190, 0),
JAVELIN("JAVLN", 180, 206, 172),

View File

@@ -158,7 +158,7 @@ public class TokenInfo {
if (!colorMap.isEmpty()) {
if (!result.isColorless()) {
// change Token Colors
byte color = result.determineColor().getColor();
byte color = result.getColor().getColor();
for (final Map.Entry<String, String> e : colorMap.entrySet()) {
byte v = MagicColor.fromName(e.getValue());

View File

@@ -157,6 +157,7 @@ public enum Keyword {
SURGE("Surge", KeywordWithCost.class, false, "You may cast this spell for its surge cost if you or a teammate has cast another spell this turn."),
SUSPEND("Suspend", Suspend.class, false, "Rather than cast this card from your hand, you may pay %s and exile it with {%d:time counter} on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost."),
TOTEM_ARMOR("Totem armor", SimpleKeyword.class, true, "If enchanted permanent would be destroyed, instead remove all damage marked on it and destroy this Aura."),
TRAINING("Training", SimpleKeyword.class, false, "Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature."),
TRAMPLE("Trample", Trample.class, true, "This creature can deal excess combat damage to the player or planeswalker it's attacking."),
TRANSFIGURE("Transfigure", KeywordWithCost.class, false, "%s, Sacrifice this creature: Search your library for a creature card with the same mana value as this creature and put that card onto the battlefield, then shuffle. Transfigure only as a sorcery."),
TRANSMUTE("Transmute", KeywordWithCost.class, false, "%s, Discard this card: Search your library for a card with the same mana value as this card, reveal it, and put it into your hand, then shuffle. Transmute only as a sorcery."),

View File

@@ -181,6 +181,23 @@ public class KeywordCollection implements Iterable<KeywordInterface> {
return view;
}
public void applyChanges(Iterable<KeywordsChange> changes) {
for (final KeywordsChange ck : changes) {
if (ck.isRemoveAllKeywords()) {
clear();
}
else if (ck.getRemoveKeywords() != null) {
removeAll(ck.getRemoveKeywords());
}
removeInstances(ck.getRemovedKeywordInstances());
if (ck.getKeywords() != null) {
insertAll(ck.getKeywords());
}
}
}
public class KeywordCollectionView implements Iterable<KeywordInterface> {
protected KeywordCollectionView() {

View File

@@ -303,4 +303,23 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
sa.setHostCard(host);
}
}
@Override
public void setIntrinsic(final boolean value) {
for (SpellAbility sa : this.abilities) {
sa.setIntrinsic(value);
}
for (Trigger tr : this.triggers) {
tr.setIntrinsic(value);
}
for (ReplacementEffect re : this.replacements) {
re.setIntrinsic(value);
}
for (StaticAbility sa : this.staticAbilities) {
sa.setIntrinsic(value);
}
}
}

View File

@@ -33,6 +33,7 @@ public interface KeywordInterface extends Cloneable {
void addStaticAbility(final StaticAbility st);
void setHostCard(final Card host);
void setIntrinsic(final boolean value);
/**
* @return the triggers

View File

@@ -138,10 +138,6 @@ public class KeywordsChange implements Cloneable {
return keywords.remove(keyword);
}
public final void addKeyword(final String keyword) {
keywords.add(keyword);
}
public void setHostCard(final Card host) {
keywords.setHostCard(host);
for (KeywordInterface k : removeKeywordInterfaces) {

View File

@@ -22,7 +22,7 @@ public class AchievementTracker {
if (sa.isPwAbility() && sa.hasParam("Ultimate")) {
activatedUltimates.add(card.getName());
}
if (card.determineColor().equals(ColorSet.ALL_COLORS)) {
if (card.getColor().equals(ColorSet.ALL_COLORS)) {
challengesCompleted.add("Chromatic");
}
}

View File

@@ -1604,11 +1604,12 @@ public class Player extends GameEntity implements Comparable<Player> {
return numTokenCreatedThisTurn;
}
public final void addTokensCreatedThisTurn() {
public final void addTokensCreatedThisTurn(Card token) {
numTokenCreatedThisTurn++;
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, this);
runParams.put(AbilityKey.Num, numTokenCreatedThisTurn);
runParams.put(AbilityKey.Card, token);
game.getTriggerHandler().runTrigger(TriggerType.TokenCreated, runParams, false);
}

View File

@@ -299,9 +299,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (first == null) {
return false;
}
byte firstColor = first.determineColor().getColor();
byte firstColor = first.getColor().getColor();
for (Card c : tgts) {
if (c.determineColor().getColor() != firstColor) {
if (c.getColor().getColor() != firstColor) {
return false;
}
}

View File

@@ -144,7 +144,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
layers.add(StaticAbilityLayer.CONTROL);
}
if (hasParam("ChangeColorWordsTo") || hasParam("GainTextOf")) {
if (hasParam("ChangeColorWordsTo") || hasParam("GainTextOf") || hasParam("AddNames")) {
layers.add(StaticAbilityLayer.TEXT);
}

View File

@@ -32,6 +32,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
@@ -45,13 +46,14 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
@@ -116,9 +118,6 @@ public final class StaticAbilityContinuous {
se.setParams(params);
se.setTimestamp(hostCard.getTimestamp());
String changeColorWordsTo = null;
Card gainTextSource = null;
String addP = "";
int powerBonus = 0;
String addT = "";
@@ -136,7 +135,7 @@ public final class StaticAbilityContinuous {
String[] addSVars = null;
List<String> addTypes = null;
List<String> removeTypes = null;
String addColors = null;
ColorSet addColors = null;
String[] addTriggers = null;
String[] addStatics = null;
boolean removeAllAbilities = false;
@@ -166,23 +165,6 @@ public final class StaticAbilityContinuous {
effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule")));
}
if (layer == StaticAbilityLayer.TEXT && params.containsKey("GainTextOf")) {
final String valid = params.get("GainTextOf");
CardCollection allValid = CardLists.getValidCards(game.getCardsInGame(), valid, hostCard.getController(), hostCard, stAb);
if (allValid.size() > 1) {
// TODO: if ever necessary, support gaining text of multiple cards at the same time
System.err.println("Error: GainTextOf parameter was not defined as a unique card for " + hostCard);
} else if (allValid.size() == 1) {
gainTextSource = allValid.get(0);
} else {
gainTextSource = null;
}
}
if (layer == StaticAbilityLayer.TEXT && params.containsKey("ChangeColorWordsTo")) {
changeColorWordsTo = params.get("ChangeColorWordsTo");
}
if (layer == StaticAbilityLayer.SETPT &&params.containsKey("SetPower")) {
setP = params.get("SetPower");
setPower = AbilityUtils.calculateAmount(hostCard, setP, stAb);
@@ -471,22 +453,22 @@ public final class StaticAbilityContinuous {
if (params.containsKey("AddColor")) {
final String colors = params.get("AddColor");
if (colors.equals("ChosenColor")) {
addColors = CardUtil.getShortColorsString(hostCard.getChosenColors());
addColors = ColorSet.fromNames(hostCard.getChosenColors());
} else if (colors.equals("All")) {
addColors = "W U B R G";
addColors = ColorSet.ALL_COLORS;
} else {
addColors = CardUtil.getShortColorsString(Arrays.asList(colors.split(" & ")));
addColors = ColorSet.fromNames(colors.split(" & "));
}
}
if (params.containsKey("SetColor")) {
final String colors = params.get("SetColor");
if (colors.equals("ChosenColor")) {
addColors = CardUtil.getShortColorsString(hostCard.getChosenColors());
addColors = ColorSet.fromNames(hostCard.getChosenColors());
} else if (colors.equals("All")) {
addColors = "W U B R G";
addColors = ColorSet.ALL_COLORS;
} else {
addColors = CardUtil.getShortColorsString(Arrays.asList(colors.split(" & ")));
addColors = ColorSet.fromNames(colors.split(" & "));
}
overwriteColors = true;
}
@@ -603,16 +585,84 @@ public final class StaticAbilityContinuous {
// Gain text from another card
if (layer == StaticAbilityLayer.TEXT) {
if (gainTextSource != null) {
affectedCard.addTextChangeState(
CardFactory.getCloneStates(gainTextSource, affectedCard, stAb), se.getTimestamp()
);
if (params.containsKey("GainTextOf")) {
CardCollection allValid = AbilityUtils.getDefinedCards(hostCard, params.get("GainTextOf"), stAb);
if (!allValid.isEmpty()) {
Card first = allValid.getFirst();
// for Volraths Shapeshifter, respect flipped state if able?
CardState state = first.getState(affectedCard.isFlipped() && first.isFlipCard() ? CardStateName.Flipped : first.getCurrentStateName());
List<SpellAbility> spellAbilities = Lists.newArrayList();
List<Trigger> trigger = Lists.newArrayList();
List<ReplacementEffect> replacementEffects = Lists.newArrayList();
List<StaticAbility> staticAbilities = Lists.newArrayList();
List<KeywordInterface> keywords = Lists.newArrayList();
for(SpellAbility sa : state.getSpellAbilities()) {
SpellAbility newSA = sa.copy(affectedCard, false);
newSA.setOriginalAbility(sa); // need to be set to get the Once Per turn Clause correct
newSA.setGrantorStatic(stAb);
//newSA.setIntrinsic(false); needs to be changed by CardTextChanges
spellAbilities.add(newSA);
}
if (params.containsKey("GainTextAbilities")) {
for (String ability : params.get("GainTextAbilities").split(" & ")) {
final SpellAbility sa = AbilityFactory.getAbility(AbilityUtils.getSVar(stAb, ability), affectedCard, stAb);
sa.setIntrinsic(true); // needs to be affected by Text
sa.setGrantorStatic(stAb);
spellAbilities.add(sa);
}
}
for (Trigger tr : state.getTriggers()) {
Trigger newTr = tr.copy(affectedCard, false);
//newTr.setIntrinsic(false); needs to be changed by CardTextChanges
trigger.add(newTr);
}
for (ReplacementEffect re : state.getReplacementEffects()) {
ReplacementEffect newRE = re.copy(affectedCard, false);
//newRE.setIntrinsic(false); needs to be changed by CardTextChanges
replacementEffects.add(newRE);
}
for (StaticAbility sa : state.getStaticAbilities()) {
StaticAbility newST = sa.copy(affectedCard, false);
//newST.setIntrinsic(false); needs to be changed by CardTextChanges
staticAbilities.add(newST);
}
for (KeywordInterface ki : state.getIntrinsicKeywords()) {
KeywordInterface newKi = ki.copy(affectedCard, false);
//newKi.setIntrinsic(false); needs to be changed by CardTextChanges
keywords.add(newKi);
}
// Volraths Shapeshifter has that cards name, mana cost, color, types, abilities, power, and toughness.
// name
affectedCard.addChangedName(state.getName(), false, se.getTimestamp(), stAb.getId());
// Mana cost
affectedCard.addChangedManaCost(state.getManaCost(), se.getTimestamp(), stAb.getId());
// color
affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), i, i);
// type
affectedCard.addChangedCardTypesByText(new CardType(state.getType()), se.getTimestamp(), stAb.getId());
// abilities
affectedCard.addChangedCardTraitsByText(spellAbilities, trigger, replacementEffects, staticAbilities, se.getTimestamp(), stAb.getId());
affectedCard.addChangedCardKeywordsByText(keywords, se.getTimestamp(), stAb.getId(), false);
// power and toughness
affectedCard.addNewPTByText(state.getBasePower(), state.getBaseToughness(), se.getTimestamp(), stAb.getId());
}
}
if (stAb.hasParam("AddNames")) { // currently only for AllNonLegendaryCreatureNames
affectedCard.addChangedName(null, true, se.getTimestamp(), stAb.getId());
}
// Change color words
if (changeColorWordsTo != null) {
if (params.containsKey("ChangeColorWordsTo")) {
final byte color;
String changeColorWordsTo = params.get("ChangeColorWordsTo");
if (changeColorWordsTo.equals("ChosenColor")) {
if (hostCard.hasChosenColor()) {
color = MagicColor.fromName(Iterables.getFirst(hostCard.getChosenColors(), null));
@@ -625,7 +675,8 @@ public final class StaticAbilityContinuous {
if (color != 0) {
final String colorName = MagicColor.toLongString(color);
affectedCard.addChangedTextColorWord("Any", colorName, se.getTimestamp(), stAb.getId());
affectedCard.addChangedTextColorWord(stAb.getParamOrDefault("ChangeColorWordsFrom", "Any"), colorName, se.getTimestamp(), stAb.getId());
}
}
}
@@ -640,7 +691,7 @@ public final class StaticAbilityContinuous {
setToughness = AbilityUtils.calculateAmount(affectedCard, setT, stAb, true);
}
affectedCard.addNewPT(setPower, setToughness,
hostCard.getTimestamp(), stAb.hasParam("CharacteristicDefining"));
hostCard.getTimestamp(), stAb.getId(), stAb.hasParam("CharacteristicDefining"));
}
}
@@ -674,7 +725,7 @@ public final class StaticAbilityContinuous {
}
// replace one Keyword with list of keywords
if (input.startsWith("Protection") && input.contains("CardColors")) {
for (Byte color : affectedCard.determineColor()) {
for (Byte color : affectedCard.getColor()) {
extraKeywords.add(input.replace("CardColors", MagicColor.toLongString(color)));
}
return true;

View File

@@ -23,9 +23,12 @@ import forge.game.Game;
import forge.game.GameStage;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
/**
@@ -67,6 +70,16 @@ public class TriggerDrawn extends Trigger {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
return false;
}
if (hasParam("ValidPlayerControls")) {
final String sIsPresent = this.getParam("ValidPlayerControls");
final Player p = ((Player)runParams.get(AbilityKey.Player));
CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(),
this.getHostCard(), this);
if (list.size() == 0) {
return false;
}
}
if (hasParam("Number")) {
if (number != Integer.parseInt(getParam("Number"))) {

View File

@@ -21,7 +21,11 @@ import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
/**
@@ -56,6 +60,17 @@ public class TriggerLifeGained extends Trigger {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
return false;
}
if (hasParam("ValidPlayerControls")) {
final String sIsPresent = this.getParam("ValidPlayerControls");
final Player p = ((Player)runParams.get(AbilityKey.Player));
CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(),
this.getHostCard(), this);
if (list.size() == 0) {
return false;
}
}
if (!matchesValidParam("ValidSource", runParams.get(AbilityKey.Source))) {
return false;
}

View File

@@ -267,7 +267,7 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger {
if (!m.isSnow()) {
continue;
}
if (cast.determineColor().sharesColorWith(ColorSet.fromMask(m.getColor()))) {
if (cast.getColor().sharesColorWith(ColorSet.fromMask(m.getColor()))) {
found = true;
break;
}

View File

@@ -61,6 +61,7 @@ public class TriggerTokenCreated extends Trigger {
@Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player);
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card);
}
/** {@inheritDoc}
@@ -71,6 +72,10 @@ public class TriggerTokenCreated extends Trigger {
return false;
}
if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Card))) {
return false;
}
if (hasParam("OnlyFirst")) {
if ((int) runParams.get(AbilityKey.Num) != 1) {
return false;

View File

@@ -0,0 +1,75 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.trigger;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
import java.util.Map;
/**
* <p>
* Trigger_Trains class.
* </p>
*
* @author Forge
*/
public class TriggerTrains extends Trigger {
/**
* <p>
* Constructor for TriggerTrains.
* </p>
*
* @param params
* a {@link java.util.HashMap} object.
* @param host
* a {@link forge.game.card.Card} object.
* @param intrinsic
* the intrinsic
*/
public TriggerTrains(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/** {@inheritDoc}
* @param runParams*/
@Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
return false;
}
return true;
}
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card);
}
@Override
public String getImportantStackObjects(SpellAbility sa) {
StringBuilder sb = new StringBuilder();
sb.append(Localizer.getInstance().getMessage("lblTrains")).append(": ").append(sa.getTriggeringObject(AbilityKey.Card));
return sb.toString();
}
}

View File

@@ -107,6 +107,7 @@ public enum TriggerType {
Taps(TriggerTaps.class),
TapsForMana(TriggerTapsForMana.class),
TokenCreated(TriggerTokenCreated.class),
Trains(TriggerTrains.class),
Transformed(TriggerTransformed.class),
TurnBegin(TriggerTurnBegin.class),
TurnFaceUp(TriggerTurnFaceUp.class),

View File

@@ -93,11 +93,13 @@ public class FDeckImportDialog extends FDialog {
public void run() {
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
//if there are any unknown cards, let user know this and give them the option to cancel
//if there are any cards that cannot be imported, let user know this and give them the option to cancel
StringBuilder sb = new StringBuilder();
for (DeckRecognizer.Token token : tokens) {
if ((token.getType() == TokenType.UNKNOWN_CARD) ||
(token.getType() == TokenType.UNSUPPORTED_CARD)) {
if (TokenType.CARD_FROM_NOT_ALLOWED_SET.equals(token.getType())
|| TokenType.CARD_FROM_INVALID_SET.equals(token.getType())
|| TokenType.UNKNOWN_CARD.equals(token.getType())
|| TokenType.UNSUPPORTED_CARD.equals(token.getType())) {
if (sb.length() > 0) {
sb.append("\n");
}
@@ -116,6 +118,7 @@ public class FDeckImportDialog extends FDialog {
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
deck.optimizeMainCardArt();
hide();
callback.run(deck);
}

View File

@@ -2,8 +2,8 @@ Name:Braids, Conjurer Adept Avatar
ManaCost:no cost
Types:Vanguard
HandLifeModifier:+0/+3
A:AB$ ChangeZone | ActivationZone$ Command | Cost$ 2 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land | DefinedPlayer$ Player | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Each player may put a land card from their hand onto the battlefield tapped.
A:AB$ ChangeZone | ActivationZone$ Command | Cost$ 3 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Artifact.nonCreature | DefinedPlayer$ Player | ChangeNum$ 1 | SpellDescription$ Each player may put a noncreature artifact card from their hand onto the battlefield.
A:AB$ ChangeZone | ActivationZone$ Command | Cost$ 2 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land | DefinedPlayer$ Player | ChangeNum$ 1 | Tapped$ True | AILogic$ AtOppEOT | SpellDescription$ Each player may put a land card from their hand onto the battlefield tapped.
A:AB$ ChangeZone | ActivationZone$ Command | Cost$ 3 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Artifact.nonCreature | DefinedPlayer$ Player | ChangeNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Each player may put a noncreature artifact card from their hand onto the battlefield.
A:AB$ ChangeZone | ActivationZone$ Command | Cost$ 4 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | DefinedPlayer$ Player | ChangeNum$ 1 | SorcerySpeed$ True | SpellDescription$ Each player may put a creature card from their hand onto the battlefield. Activate only as a sorcery.
SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Braids, Conjurer Adept Avatar.full.jpg
Oracle:Hand +0, life +3\n{2}: Each player may put a land card from their hand onto the battlefield tapped.\n{3}: Each player may put a noncreature artifact card from their hand onto the battlefield.\n{4}: Each player may put a creature card from their hand onto the battlefield. Activate only as a sorcery.

View File

@@ -6,5 +6,5 @@ K:Flying
K:ETBReplacement:Other:TrigRoll
SVar:TrigRoll:DB$ RollDice | ResultSVar$ SetPwr | SubAbility$ RollTough | SpellDescription$ As CARDNAME enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.
SVar:RollTough:DB$ RollDice | ResultSVar$ SetTgn | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | Power$ SetPwr | Toughness$ SetTgn
SVar:DBAnimate:DB$ Animate | Defined$ Self | Power$ SetPwr | Toughness$ SetTgn | Duration$ Permanent
Oracle:As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.

View File

@@ -3,7 +3,7 @@ ManaCost:2 G G
Types:Creature Dragon
PT:1/3
K:Flying
A:AB$ GenericChoice | Cost$ 1 G G | AtRandom$ True | ShowChoice$ Description | Choices$ Berserk,Twiddle,BloodLust,Green,White,Red,Damage3,Flying,P3P3,Banding,Black,Blue,NoRegen,LilSneak,M2M0,ToHand,Damage1,Nerf,Exile,Orcish | StackDescription$ SpellDescription | SpellDescription$ Perform a random action.
A:AB$ GenericChoice | Cost$ 1 G G | AtRandom$ True | ShowChoice$ Description | Choices$ Berserk,Twiddle,BloodLust,Green,White,Red,Damage3,Flying,P3P3,Banding,Black,Blue,NoRegen,LilSneak,M2M0,ToHand,Damage1,Nerf,Exile,Orcish | AILogic$ AtOppEOT | StackDescription$ SpellDescription | SpellDescription$ Perform a random action.
SVar:Berserk:DB$ ChooseCard | Choices$ Creature | AtRandom$ True | RememberChosen$ True | SubAbility$ DBPump1 | SpellDescription$ A creature chosen at random gains trample and gets +X/+0 until end of turn, where X is its power. At the beginning of the next end step, destroy that creature if it attacked this turn.
SVar:DBPump1:DB$ Pump | Defined$ Remembered | KW$ Trample | NumAtt$ X1 | SubAbility$ DBDelayedTrigger1
SVar:DBDelayedTrigger1:DB$ DelayedTrigger | RememberObjects$ Remembered | Mode$ Phase | Phase$ End of Turn | Execute$ TrigDestroy1 | SubAbility$ DBCleanup | TriggerDescription$ At the beginning of the next end step, destroy that creature if it attacked this turn.

View File

@@ -3,9 +3,8 @@ ManaCost:1 U
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 1 U | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ AttackTrigger | AddSVar$ TrigTapUnTap | Description$ Enchanted creature has "Whenever this creature attacks, you may tap or untap target permanent.
SVar:AttackTrigger:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigTapUnTap | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, you may tap or untap target permanent.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ AttackTrigger | AddSVar$ TrigTapUnTap | Description$ Enchanted creature has "Whenever this creature attacks, you may tap or untap target permanent."
SVar:AttackTrigger:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigTapUnTap | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature attacks, you may tap or untap target permanent.
SVar:TrigTapUnTap:DB$ TapOrUntap | ValidTgts$ Permanent | TgtPrompt$ Select target permanent
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/ghostly_touch.jpg
Oracle:Enchant creature\nEnchanted creature has "Whenever this creature attacks, you may tap or untap target permanent."

View File

@@ -2,7 +2,7 @@ Name:Goblin Polka Band
ManaCost:R R
Types:Creature Goblin
PT:1/1
A:AB$ Tap | Announce$ TgtNum | AnnounceTitle$ any number of creatures to target | Cost$ X 2 T | XColor$ R | CostDesc$ {2}, {T}: | ValidTgts$ Creature.untapped | TargetMin$ TgtNum | TargetMax$ TgtNum | TargetsAtRandom$ True | RememberTargets$ True | SubAbility$ GoblinHangover | SpellDescription$ Tap any number of random target creatures. Goblins tapped in this way do not untap during their controllers' next untap phases. This ability costs {R} more to activate for each target.
A:AB$ Tap | Announce$ TgtNum | AnnounceTitle$ any number of creatures to target | Cost$ X 2 T | XColor$ R | CostDesc$ {2}, {T}: | ValidTgts$ Creature.untapped | TargetMin$ TgtNum | TargetMax$ TgtNum | TargetsAtRandom$ True | RememberTargets$ True | AILogic$ GoblinPolkaBand | SubAbility$ GoblinHangover | SpellDescription$ Tap any number of random target creatures. Goblins tapped in this way do not untap during their controllers' next untap phases. This ability costs {R} more to activate for each target.
SVar:GoblinHangover:DB$ PumpAll | ValidCards$ Goblin.IsRemembered | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent
SVar:TgtNum:Number$0
SVar:X:SVar$TgtNum

View File

@@ -2,6 +2,6 @@ Name:Power Struggle
ManaCost:2 U U U
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerController$ TriggeredPlayer | TriggerDescription$ At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.
SVar:TrigPump:DB$ Pump | TargetsWithDefinedController$ TriggeredPlayer | ValidTgts$ Artifact,Creature,Land | TargetsAtRandom$ True | SubAbility$ DBExchangeControl
SVar:DBExchangeControl:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Artifact,Creature,Land | TargetsWithDefinedController$ Player.OpponentOf TriggeredPlayer | TargetsWithSharedCardType$ ParentTarget | TargetsAtRandom$ True
SVar:TrigPump:DB$ Pump | TargetsWithDefinedController$ TriggeredPlayer | ValidTgts$ Artifact,Creature,Land | TargetsAtRandom$ True | AILogic$ PowerStruggle | SubAbility$ DBExchangeControl
SVar:DBExchangeControl:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Artifact,Creature,Land | TargetsWithDefinedController$ Player.OpponentOf TriggeredPlayer | TargetsWithSharedCardType$ ParentTarget | TargetsAtRandom$ True | AILogic$ PowerStruggle
Oracle:At the beginning of each player's upkeep, that player exchanges control of random target artifact, creature or land he or she controls, for control of random target permanent of the same type that a random opponent controls.

View File

@@ -3,7 +3,7 @@ ManaCost:2 B B
Types:Enchantment
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonBlack | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a nonblack creature dies, put a husk counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ HUSK
A:AB$ Token | Cost$ 5 SubCounter<1/HUSK> | TokenScript$ spawn_of_azar | TokenPower$ X | TokenToughness$ Y | SpellDescription$ Create an X/Y black Spawn creature token with swampwalk named Spawn of Azar, where X and Y are numbers chosen at random from 1 to 3.
A:AB$ Token | Cost$ 5 SubCounter<1/HUSK> | TokenScript$ spawn_of_azar | TokenPower$ X | TokenToughness$ Y | AILogic$ RandomPT | SpellDescription$ Create an X/Y black Spawn creature token with swampwalk named Spawn of Azar, where X and Y are numbers chosen at random from 1 to 3.
SVar:X:Count$Random.1.3
SVar:Y:Count$Random.1.3
DeckHas:Ability$Counters & Ability$Token

View File

@@ -1,7 +1,7 @@
Name:Pandora's Box
ManaCost:5
Types:Artifact
A:AB$ ChooseCard | Cost$ 3 T | Choices$ Creature | AtRandom$ True | AllCards$ True | SubAbility$ DBRepeatEach | StackDescription$ SpellDescription | SpellDescription$ Choose a creature card at random from all players' decklists. Each player flips a coin. Each player whose coin comes up heads creates a token that's a copy of that card.
A:AB$ ChooseCard | Cost$ 3 T | Choices$ Creature | AtRandom$ True | AllCards$ True | SubAbility$ DBRepeatEach | AILogic$ AtOppEOT | StackDescription$ SpellDescription | SpellDescription$ Choose a creature card at random from all players' decklists. Each player flips a coin. Each player whose coin comes up heads creates a token that's a copy of that card.
SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBFlip | SubAbility$ DBCleanup
SVar:DBFlip:DB$ FlipACoin | Flipper$ Remembered | NoCall$ True | HeadsSubAbility$ DBCopyPermanent
SVar:DBCopyPermanent:DB$ CopyPermanent | Defined$ ChosenCard | Controller$ Remembered

View File

@@ -2,6 +2,5 @@ Name:Spy Kit
ManaCost:2
Types:Artifact Equipment
K:Equip:2
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ AllNonLegendaryCreatureNames | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1 and has all names of nonlegendary creature cards in addition to its name.
SVar:Picture:http://www.wizards.com/global/images/magic/general/spy_kit.jpg
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddNames$ AllNonLegendaryCreatureNames | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1 and has all names of nonlegendary creature cards in addition to its name.
Oracle:Equipped creature gets +1/+1 and has all names of nonlegendary creature cards in addition to its name.\nEquip {2}

View File

@@ -0,0 +1,8 @@
Name:Apprentice Sharpshooter
ManaCost:1 G
Types:Creature Human Archer
PT:1/4
K:Reach
K:Training
DeckHas:Ability$Counters
Oracle:Reach\nTraining (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)

View File

@@ -0,0 +1,7 @@
Name:By Invitation Only
ManaCost:3 W W
Types:Sorcery
A:SP$ ChooseNumber | Defined$ You | Min$ 0 | Max$ 13 | SubAbility$ DBSac | AILogic$ SweepCreatures | StackDescription$ SpellDescription | SpellDescription$ Choose a number between 0 and 13. Each player sacrifices that many creatures.
SVar:DBSac:DB$ Sacrifice | Defined$ Player | SacValid$ Creature | Amount$ X | StackDescription$ None
SVar:X:Count$ChosenNumber
Oracle:Choose a number between 0 and 13. Each player sacrifices that many creatures.

View File

@@ -0,0 +1,31 @@
Name:Dorothea, Vengeful Victim
ManaCost:W U
Types:Legendary Creature Spirit
PT:4/4
K:Flying
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DelTrig | TriggerDescription$ When CARDNAME attacks or blocks, sacrifice it at end of combat.
T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ DelTrig | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or blocks, sacrifice it at end of combat.
SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | Execute$ TrigSacrifice | TriggerDescription$ Sacrifice CARDNAME at end of combat.
SVar:TrigSacrifice:DB$ SacrificeAll | Defined$ Self | Controller$ You
SVar:SacrificeEndCombat:True
K:Disturb:1 W U
AlternateMode:DoubleFaced
DeckHas:Ability$Sacrifice & Ability$Graveyard
Oracle:Flying\nWhen Dorothea, Vengeful Victim attacks or blocks, sacrifice it at end of combat.\nDisturb {1}{W}{U} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
Name:Dorothea's Retribution
ManaCost:no cost
Colors:white,blue
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ AttackTrigger | AddSVar$ AE | Description$ Enchanted creature has "Whenever this creature attacks, create a 4/4 white Spirit creature token with flying that's tapped and attacking. Sacrifice that token at end of combat."
SVar:AttackTrigger:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature attacks, create a 4/4 white Spirit creature token with flying that's tapped and attacking. Sacrifice that token at end of combat.
SVar:TrigToken:DB$ Token | TokenScript$ w_4_4_spirit_flying | TokenTapped$ True | TokenAttacking$ True | AtEOT$ SacrificeCombat
SVar:AE:SVar:HasAttackEffect:TRUE
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
DeckHas:Ability$Token & Ability$Sacrifice
Oracle:Enchant creature\nEnchanted creature has "Whenever this creature attacks, create a 4/4 white Spirit creature token with flying that's tapped and attacking. Sacrifice that token at end of combat."\nIf Dorothea's Retribution would be put into a graveyard from anywhere, exile it instead.

View File

@@ -0,0 +1,21 @@
Name:Drogskol Infantry
ManaCost:1 W
Types:Creature Spirit Soldier
PT:2/2
K:Disturb:3 W
AlternateMode:DoubleFaced
DeckHas:Ability$Graveyard
Oracle:Disturb {3}{W} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
Name:Drogskol Armaments
ManaCost:no cost
Colors:white
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2.
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nIf Drogskol Armaments would be put into a graveyard from anywhere, exile it instead.

View File

@@ -0,0 +1,10 @@
Name:Gluttonous Guest
ManaCost:2 B
Types:Creature Vampire
PT:1/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a Blood token. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")
SVar:TrigToken:DB$ Token | TokenScript$ c_a_blood_draw
T:Mode$ Sacrificed | ValidCard$ Blood.token+YouCtrl | Execute$ TrigGainLife | TriggerZones$ Battlefield | TriggerDescription$ Whenever you sacrifice a Blood token, you gain 1 life.
SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1
DeckHas:Ability$Token & Ability$Sacrifice & Ability$LifeGain & Type$Blood
Oracle:When Gluttonous Guest enters the battlefield, create a Blood token. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")\nWhenever you sacrifice a Blood token, you gain 1 life.

View File

@@ -0,0 +1,11 @@
Name:Grolnok, the Omnivore
ManaCost:2 G U
Types:Legendary Creature Frog
PT:3/3
T:Mode$ Attacks | ValidCard$ Frog.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMill | TriggerDescription$ Whenever a Frog you control attacks, mill three cards.
SVar:TrigMill:DB$ Mill | NumCards$ 3 | Defined$ You
T:Mode$ ChangesZone | ValidCard$ Permanent.YouOwn | Origin$ Library | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever a permanent card is put into your graveyard from your library, exile it with a croak counter on it.
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredCard | WithCountersType$ CROAK | WithCountersAmount$ 1
S:Mode$ Continuous | Affected$ Card.YouOwn+counters_GE1_CROAK | AffectedZone$ Exile | MayPlay$ True | Description$ You may play lands and cast noncreature spells from among cards you own in exile with croak counters on them.
DeckHas:Ability$Mill
Oracle:Whenever a Frog you control attacks, mill three cards.\nWhenever a permanent card is put into your graveyard from your library, exile it with a croak counter on it.\nYou may play lands and cast spells from among cards you own in exile with croak counters on them.

View File

@@ -0,0 +1,8 @@
Name:Gryff Rider
ManaCost:2 W
Types:Creature Human Knight
PT:2/1
K:Flying
K:Training
DeckHas:Ability$Counters
Oracle:Flying\nTraining (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)

View File

@@ -0,0 +1,5 @@
Name:Massive Might
ManaCost:G
Types:Instant
A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ 2 | NumDef$ 2 | KW$ Trample | SpellDescription$ Target creature gets +2/+2 and gains trample until end of turn.
Oracle:Target creature gets +2/+2 and gains trample until end of turn.

View File

@@ -0,0 +1,13 @@
Name:Millicent, Restless Revenant
ManaCost:5 W U
Types:Legendary Creature Spirit Soldier
PT:4/4
K:Flying
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each Spirit you control.
SVar:X:Count$TypeYouCtrl.Spirit
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self,Spirit.Other+nonToken+YouCtrl | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME or another nontoken Spirit you control dies or deals combat damage to a player, create a 1/1 white Spirit creature token with flying.
T:Mode$ DamageDone | ValidSource$ Card.Self,Spirit.Other+nonToken+YouCtrl | ValidTarget$ Player | Execute$ TrigToken | CombatDamage$ True | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another nontoken Spirit you control dies or deals combat damage to a player, create a 1/1 white Spirit creature token with flying.
SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_spirit_flying
DeckHints:Type$Spirit & Ability$Disturb
DeckHas:Ability$Token
Oracle:This spell costs {1} less to cast for each Spirit you control.\nFlying\nWhenever Millicent, Restless Revenant or another nontoken Spirit you control dies or deals combat damage to a player, create a 1/1 white Spirit creature token with flying.

View File

@@ -0,0 +1,15 @@
Name:Olivia, Crimson Bride
ManaCost:4 B R
Types:Legendary Creature Vampire Noble
PT:3/4
K:Flying
K:Haste
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature."
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | RememberChanged$ True | AnimateSubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia
SVar:TrigOlivia:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Vampire.YouCtrl+Legendary | PresentCompare$ EQ0 | Execute$ TrigExile | TriggerDescription$ When you don't control a legendary Vampire, exile this creature.
SVar:TrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Graveyard
DeckHints:Type$Vampire|Legendary
Oracle:Flying, haste\nWhenever Olivia, Crimson Bride attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature."

View File

@@ -0,0 +1,12 @@
Name:Overcharged Amalgam
ManaCost:2 U U
Types:Creature Zombie Horror
PT:2/2
K:Flash
K:Flying
K:Exploit
T:Mode$ Exploited | ValidCard$ Creature | ValidSource$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ When CARDNAME exploits a creature, counter target spell, activated ability, or triggered ability.
SVar:TrigCounter:DB$ Counter | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select target spell or ability | ValidTgts$ Card | SpellDescription$ Counter target spell, activated ability, or triggered ability.
AI:RemoveDeck:All
DeckHas:Ability$Sacrifice
Oracle:Flash\nFlying\nExploit (When this creature enters the battlefield, you may sacrifice a creature.)\nWhen Overcharged Amalgam exploits a creature, counter target spell, activated ability, or triggered ability.

View File

@@ -0,0 +1,9 @@
Name:Rot-Tide Gargantua
ManaCost:3 B B
Types:Creature Zombie Kraken
PT:5/4
K:Exploit
T:Mode$ Exploited | ValidCard$ Creature | ValidSource$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ When CARDNAME exploits a creature, each opponent sacrifices a creature.
SVar:TrigSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature
DeckHas:Ability$Sacrifice
Oracle:Exploit (When this creature enters the battlefield, you may sacrifice a creature.)\nWhen Rot-Tide Gargantua exploits a creature, each opponent sacrifices a creature.

View File

@@ -0,0 +1,11 @@
Name:Savior of Ollenbock
ManaCost:1 W W
Types:Creature Human Soldier
PT:1/2
K:Training
T:Mode$ Trains | ValidCard$ Card.Self | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME trains, exile up to one other target creature from the battlefield or creature card from a graveyard.
SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield,Graveyard | Destination$ Exile | ValidTgts$ Creature.Other | TgtPrompt$ Select up to one other target creature from the battlefield or creature card from a graveyard | TargetMin$ 0 | TargetMax$ 1 | TgtZone$ Battlefield,Graveyard | AILogic$ SaviorOfOllenbock
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME leaves the battlefield, put the exiled cards onto the battlefield under their owners' control.
SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Battlefield
DeckHas:Ability$Counters
Oracle:Training (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)\nWhenever Savior of Ollenbock trains, exile up to one other target creature from the battlefield or creature card from a graveyard.\nWhen Savior of Ollenbock leaves the battlefield, put the exiled cards onto the battlefield under their owners' control.

View File

@@ -0,0 +1,8 @@
Name:Sigarda's Summons
ManaCost:4 W W
Types:Enchantment
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Flying | AddType$ Angel | SetPower$ 4 | SetToughness$ 4 | Description$ Creatures you control with +1/+1 counters on them have base power and toughness 4/4, have flying, and are Angels in addition to their other types.
SVar:PlayMain1:TRUE
DeckNeeds:Ability$Counters
DeckHints:Type$Angel
Oracle:Creatures you control with +1/+1 counters on them have base power and toughness 4/4, have flying, and are Angels in addition to their other types.

View File

@@ -0,0 +1,14 @@
Name:Sorin the Mirthless
ManaCost:2 B B
Types:Legendary Planeswalker Sorin
Loyalty:4
A:AB$ PeekAndReveal | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | PeekAmount$ 1 | RevealOptional$ True | RememberRevealed$ True | SubAbility$ DBChangeZone | SpellDescription$ Look at the top card of your library. You may reveal that card and put it into your hand. If you do, you lose life equal to its mana value.
SVar:DBChangeZone:DB$ ChangeZone | Defined$ TopOfLibrary | Origin$ Library | Destination$ Hand | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 | SubAbility$ DBLoseLife | StackDescription$ None
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$CardManaCost
A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenScript$ b_2_3_vampire_flying_lifelink | SpellDescription$ Create a 2/3 black Vampire creature token with flying and lifelink.
A:AB$ DealDamage | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 13 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 13 damage to any target. You gain 13 life.
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 13
DeckHas:Ability$Token & Ability$LifeGain & Type$Vampire
Oracle:[+1]: Look at the top card of your library. You may reveal that card and put it into your hand. If you do, you lose life equal to its mana value.\n[2]: Create a 2/3 black Vampire creature token with flying and lifelink.\n[7]: Sorin the Mirthless deals 13 damage to any target. You gain 13 life.

View File

@@ -0,0 +1,16 @@
Name:Strefan, Maurer Progenitor
ManaCost:2 B R
Types:Legendary Creature Vampire Noble
PT:3/2
K:Flying
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, create a Blood token for each player who lost life this turn.
SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ c_a_blood_draw
SVar:X:PlayerCountPlayers$HasPropertyLostLifeThisTurn
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ Whenever NICKNAME attacks, you may sacrifice two Blood tokens. If you do, you may put a Vampire card from your hand onto the battlefield tapped and attacking. It gains indestructible until end of turn.
SVar:TrigChangeZone:AB$ ChangeZone | Cost$ Sac<2/Blood.token/Blood token> | Origin$ Hand | Destination$ Battlefield | SelectPrompt$ You may select a Vampire card from your hand | ChangeType$ Vampire | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBPump
SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Indestructible | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Token & Ability$Sacrifice
DeckHints:Type$Vampire
Oracle:Flying\nAt the beginning of your end step, create a Blood token for each player who lost life this turn.\nWhenever Strefan attacks, you may sacrifice two Blood tokens. If you do, you may put a Vampire card from your hand onto the battlefield tapped and attacking. It gains indestructible until end of turn.

View File

@@ -0,0 +1,9 @@
Name:Torens, Fist of the Angels
ManaCost:1 G W
Types:Legendary Creature Human Cleric
PT:2/2
K:Training
T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a creature spell, create a 1/1 green and white Human Soldier creature token with training.
SVar:TrigToken:DB$ Token | TokenScript$ gw_1_1_human_soldier_training
DeckHas:Ability$Counters & Ability$Token
Oracle:Training (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)\nWhenever you cast a creature spell, create a 1/1 green and white Human Soldier creature token with training.

View File

@@ -0,0 +1,25 @@
Name:Voldaren Bloodcaster
ManaCost:1 B
Types:Creature Vampire Wizard
PT:2/1
K:Flying
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self,Creature.Other+nonToken+YouCtrl | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME or another nontoken creature you control dies, create a Blood token. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")
SVar:TrigToken:DB$ Token | TokenScript$ c_a_blood_draw
T:Mode$ TokenCreated | ValidPlayer$ You | ValidToken$ Blood | IsPresent$ Blood.token+YouCtrl | PresentCompare$ GE5 | Execute$ TrigTransform | TriggerZone$ Battlefield | TriggerDescription$ Whenever you create a Blood token, if you control five or more Blood tokens, transform CARDNAME.
SVar:TrigTransform:DB$ SetState | Defined$ Self | Mode$ Transform
AlternateMode:DoubleFaced
DeckHas:Ability$Token & Ability$Sacrifice & Type$Blood
Oracle:Flying\nWhenever Voldaren Bloodcaster or another nontoken creature you control dies, create a Blood token. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")\nWhenever you create a Blood token, if you control five or more Blood tokens, transform Voldaren Bloodcaster.
ALTERNATE
Name:Bloodbat Summoner
ManaCost:no cost
Colors:black
Types:Creature Vampire Wizard
PT:3/3
K:Flying
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigAnimate | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, up to one target Blood token you control becomes a 2/2 black Bat creature with flying and haste in addition to its other types.
SVar:TrigAnimate:DB$ Animate | ValidTgts$ Blood.token+YouCtrl | TgtPrompt$ Select up to one target Blood token you control | TargetMin$ 0 | TargetMax$ 1 | Power$ 2 | Toughness$ 2 | Colors$ Black | OverwriteColors$ True | Types$ Creature,Bat | Keywords$ Flying & Haste | Duration$ Permanent
DeckHas:Type$Bat
Oracle:Flying\nAt the beginning of combat on your turn, up to one target Blood token you control becomes a 2/2 black Bat creature with flying and haste in addition to its other types.

View File

@@ -0,0 +1,10 @@
Name:Voldaren Estate
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ Mana | Cost$ T PayLife<1> | Produced$ Any | Amount$ 1 | RestrictValid$ Vampire | SpellDescription$ Add one mana of any color. Spend this mana only to cast a Vampire spell.
A:AB$ Token | Cost$ 5 T | TokenScript$ c_a_blood_draw | ReduceCost$ X | SpellDescription$ Create a Blood token. This ability costs {1} less to activate for each Vampire you control. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")
SVar:X:Count$TypeYouCtrl.Vampire
DeckNeeds:Type$Vampire
DeckHas:Ability$Token & Ability$Sacrifice & Type$Blood
Oracle:{T}: Add {C}.\n{T}, Pay 1 life: Add one mana of any color. Spend this mana only to cast a Vampire spell.\n{5}, {T}: Create a Blood token. This ability costs {1} less to activate for each Vampire you control. (It's an artifact with "{1}, {T}, Discard a card, Sacrifice this artifact: Draw a card.")

View File

@@ -0,0 +1,19 @@
Name:Weary Prisoner
ManaCost:3 R
Types:Creature Human Werewolf
PT:2/6
K:Defender
K:Daybound
AlternateMode:DoubleFaced
Oracle:Defender\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.)
ALTERNATE
Name:Wrathful Jailbreaker
ManaCost:no cost
Colors:red
Types:Creature Werewolf
PT:6/6
K:CARDNAME attacks each combat if able.
K:Nightbound
Oracle:Wrathful Jailbreaker attacks each combat if able.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.)

View File

@@ -0,0 +1,22 @@
Name:Wedding Announcement
ManaCost:2 W
Types:Enchantment
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your end step, put an invitation counter on CARDNAME. If you attacked with two or more creatures this turn, draw card. Otherwise, create a 1/1 white Human creature token. Then if CARDNAME has three or more invitation counters on it, transform it.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ INVITATION | CounterNum$ 1 | SubAbility$ DBBranch
SVar:DBBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE2 | TrueSubAbility$ DBDraw | FalseSubAbility$ DBToken
SVar:X:Count$AttackersDeclared
SVar:DBDraw:DB$ Draw | NumCards$ 1 | SubAbility$ DBTransform
SVar:DBToken:DB$ Token | TokenScript$ w_1_1_human | SubAbility$ DBTransform
SVar:DBTransform:DB$ SetState | Defined$ Self | ConditionDefined$ Self | ConditionPresent$ Card.counters_GE3_INVITATION | Mode$ Transform
AlternateMode:DoubleFaced
DeckHas:Ability$Counters & Ability$Token
Oracle:At the beginning of your end step, put an invitation counter on Wedding Announcement. If you attacked with two or more creatures this turn, draw card. Otherwise, create a 1/1 white Human creature token. Then if Wedding Announcement has three or more invitation counters on it, transform it.
ALTERNATE
Name:Wedding Festivity
ManaCost:no cost
Colors:white
Types:Enchantment
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1.
Oracle:Creatures you control get +1/+1.

View File

@@ -0,0 +1,10 @@
Name:Wedding Invitation
ManaCost:2
Types:Artifact
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
A:AB$ Pump | Cost$ 1 Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN Unblockable | AITgts$ Vampire | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't be blocked this turn. | SpellDescription$ Target creature can't be blocked this turn. If it's a Vampire, it also gains lifelink until end of turn.
SVar:DBPump:DB$ Pump | Defined$ Targeted.Vampire | KW$ Lifelink | StackDescription$ If it's a Vampire, it also gains lifelink until end of turn.
DeckHints:Type$Vampire
DeckHas:Ability$LifeGain
Oracle:When Wedding Invitation enters the battlefield, draw a card.\n{T}, Sacrifice Wedding Invitation: Target creature can't be blocked this turn. If it's a Vampire, it also gains lifelink until end of turn.

View File

@@ -0,0 +1,12 @@
Name:Wedding Ring
ManaCost:2 W W
Types:Artifact
T:Mode$ ChangesZone | ValidCard$ Card.Self+wasCast | Origin$ Any | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enters the battlefield, if it was cast, target opponent creates a token that's a copy of it.
SVar:TrigCopy:DB$ CopyPermanent | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Defined$ Self | Controller$ TargetedPlayer
T:Mode$ Drawn | ValidCard$ Card.OppOwn | ValidPlayer$ Player.Opponent+Active | ValidPlayerControls$ Card.namedWedding Ring | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent who controls an artifact named Wedding Ring draws a card during their turn, you draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
T:Mode$ LifeGained | ValidPlayer$ Player.Opponent+Active | ValidPlayerControls$ Card.namedWedding Ring | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever an opponent who controls an artifact named Wedding Ring gains life during their turn, you gain that much life.
SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:TriggerCount$LifeAmount
DeckHas:Ability$LifeGain
Oracle:When Wedding Ring enters the battlefield, if it was cast, target opponent creates a token that's a copy of it.\n\nWhenever an opponent who controls an artifact named Wedding Ring draws a card during their turn, you draw a card.\nWhenever an opponent who controls an artifact named Wedding Ring gains life during their turn, you gain that much life.

View File

@@ -3,11 +3,10 @@ ManaCost:1 U U
Types:Creature Phyrexian Shapeshifter
PT:0/1
A:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card.
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ Creature.TopGraveyard+YouCtrl | GainTextAbilities$ VolrathDiscard | Description$ As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.)
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ TopOfGraveyard.Creature | GainTextAbilities$ VolrathDiscard | Description$ As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.)
SVar:VolrathDiscard:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card.
SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/volraths_shapeshifter.jpg
Oracle:As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." (Volrath's Shapeshifter has that card's name, mana cost, color, types, abilities, power, and toughness.)\n{2}: Discard a card.

View File

@@ -1,7 +1,7 @@
Name:Whimsy
ManaCost:X U U
Types:Sorcery
A:SP$ Repeat | MaxRepeat$ X | RepeatSubAbility$ DBGenericChoice | StackDescription$ SpellDescription | SpellDescription$ Perform X random actions.
A:SP$ Repeat | MaxRepeat$ X | RepeatSubAbility$ DBGenericChoice | AILogic$ MaxX | StackDescription$ SpellDescription | SpellDescription$ Perform X random actions.
SVar:DBGenericChoice:DB$ GenericChoice | ShowChoice$ Description | AtRandom$ True | Choices$ ToHand,Untap,Tap,Damage,Draw3,DestroyGain,DestroyAE,Gain3,Anoint,DestroyCL,Mill2,Wasp,Nevinyrral,Suleiman,Pandora,Discard,Fog,Sindbad
SVar:ToHand:DB$ ChooseCard | Choices$ Permanent.unenchanted | AtRandom$ True | SubAbility$ DBChangeZone3 | SpellDescription$ Return a permanent that isn't enchanted chosen at random to its owner's hand.
SVar:DBChangeZone3:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBCleanup

View File

@@ -0,0 +1,38 @@
[metadata]
Name=Braids, Conjurer Adept
[Avatar]
1 Braids, Conjurer Adept Avatar|VAN|1
[Main]
1 Angel of Finality|AFC|1
1 Argentum Armor|AFC|1
1 Blink Dog|AFR|1
1 Cave of the Frost Dragon|AFR|1
1 Clay Golem|AFC|1
1 Desert|AFC|1
1 Devoted Paladin|AFR|1
1 Dungeon Descent|AFR|1
1 Fey Steed|AFC|1
1 Flumph|AFR|2
1 Grand Master of Flowers|AFR|1
1 Holy Avenger|AFC|1
1 Icingdeath, Frost Tyrant|AFR|1
1 Mishra's Factory|AFC|1
1 Monk of the Open Hand|AFR|1
1 Moonsilver Spear|AFC|1
1 Nadaar, Selfless Paladin|AFR|1
1 Paladin Class|AFR|1
4 Plains|AFR|2
3 Plains|AFR|3
4 Plains|AFR|4
1 Planar Ally|AFR|1
1 Radiant Solar|AFC|1
1 Rally Maneuver|AFR|1
1 Sol Ring|AFC|1
1 Sram, Senior Edificer|AFC|1
1 Steadfast Paladin|AFR|1
1 The Deck of Many Things|AFR|1
1 Thorough Investigation|AFC|1
1 Treasure Chest|AFR|1
1 Wall of Omens|AFC|1
1 White Dragon|AFR|1
[Sideboard]

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