mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Merge remote-tracking branch 'upstream/master' into deck-importer-decks-file-format
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) :
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,12 @@ public class TokenAi extends SpellAbilityAi {
|
||||
sa.getRootAbility().setXManaCostPaid(x);
|
||||
}
|
||||
if (x <= 0) {
|
||||
return false; // 0 tokens or 0 toughness token(s)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,13 +275,10 @@ public class GameCopier {
|
||||
// TODO: Controllers' list with timestamps should be copied.
|
||||
zoneOwner = playerMap.get(c.getController());
|
||||
newCard.setController(zoneOwner, 0);
|
||||
|
||||
newCard.setPTTable(c.getSetPTTable());
|
||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
||||
|
||||
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.setPTBoost(c.getPTBoostTable());
|
||||
newCard.setDamage(c.getDamage());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 Volrath’s 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?
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,14 +11,14 @@ import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
|
||||
public class CardTraitChanges implements Cloneable {
|
||||
|
||||
|
||||
private List<Trigger> triggers = Lists.newArrayList();
|
||||
private List<ReplacementEffect> replacements = Lists.newArrayList();
|
||||
private List<SpellAbility> abilities = Lists.newArrayList();
|
||||
private List<StaticAbility> staticAbilities = Lists.newArrayList();
|
||||
|
||||
private List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||
|
||||
|
||||
private boolean removeAll = false;
|
||||
private boolean removeNonMana = false;
|
||||
|
||||
@@ -64,13 +64,13 @@ public class CardTraitChanges implements Cloneable {
|
||||
public Collection<SpellAbility> getAbilities() {
|
||||
return abilities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getRemovedAbilities() {
|
||||
return removedAbilities;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the staticAbilities
|
||||
@@ -78,11 +78,11 @@ public class CardTraitChanges implements Cloneable {
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return staticAbilities;
|
||||
}
|
||||
|
||||
|
||||
public boolean isRemoveAll() {
|
||||
return removeAll;
|
||||
}
|
||||
|
||||
|
||||
public boolean isRemoveNonMana() {
|
||||
return removeNonMana;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public class CardTraitChanges implements Cloneable {
|
||||
public CardTraitChanges copy(Card host, boolean lki) {
|
||||
try {
|
||||
CardTraitChanges result = (CardTraitChanges) super.clone();
|
||||
|
||||
|
||||
result.abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
result.abilities.add(sa.copy(host, lki));
|
||||
@@ -99,25 +99,43 @@ public class CardTraitChanges implements Cloneable {
|
||||
for (SpellAbility sa : this.removedAbilities) {
|
||||
result.removedAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : this.triggers) {
|
||||
result.triggers.add(tr.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
result.replacements.add(re.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.staticAbilities = Lists.newArrayList();
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
result.staticAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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());
|
||||
@@ -316,4 +316,4 @@ public class TokenInfo {
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ public class Equip extends KeywordWithCost {
|
||||
|
||||
public Equip() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
String[] k = details.split(":");
|
||||
@@ -15,7 +15,7 @@ public class Equip extends KeywordWithCost {
|
||||
type = k[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String formatReminderText(String reminderText) {
|
||||
return String.format(reminderText, cost.toSimpleString(), type);
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -239,27 +239,27 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
public KeywordInterface copy(final Card host, final boolean lki) {
|
||||
try {
|
||||
KeywordInstance<?> result = (KeywordInstance<?>) super.clone();
|
||||
|
||||
|
||||
result.abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
result.abilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : this.triggers) {
|
||||
result.triggers.add(tr.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
result.replacements.add(re.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
result.staticAbilities = Lists.newArrayList();
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
result.staticAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("KeywordInstance : clone() error", ex);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,18 +21,19 @@ public interface KeywordInterface extends Cloneable {
|
||||
|
||||
void createTraits(final Card host, final boolean intrinsic);
|
||||
void createTraits(final Card host, final boolean intrinsic, final boolean clear);
|
||||
|
||||
|
||||
void createTraits(final Player player);
|
||||
void createTraits(final Player player, final boolean clear);
|
||||
|
||||
void addTrigger(final Trigger trg);
|
||||
|
||||
|
||||
void addReplacement(final ReplacementEffect trg);
|
||||
|
||||
void addSpellAbility(final SpellAbility s);
|
||||
void addStaticAbility(final StaticAbility st);
|
||||
|
||||
|
||||
void setHostCard(final Card host);
|
||||
void setIntrinsic(final boolean value);
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
@@ -50,7 +51,7 @@ public interface KeywordInterface extends Cloneable {
|
||||
* @return the staticAbilities
|
||||
*/
|
||||
Collection<StaticAbility> getStaticAbilities();
|
||||
|
||||
|
||||
KeywordInterface copy(final Card host, final boolean lki);
|
||||
|
||||
boolean redundant(final Collection<KeywordInterface> list);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class KeywordWithAmountAndType extends KeywordInstance<KeywordWithAmountA
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
String[] d = details.split(":");
|
||||
String[] d = details.split(":");
|
||||
amount = Integer.parseInt(d[0]);
|
||||
type = TextUtil.fastReplace(d[1], ",", " and/or ");
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* 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/>.
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ import forge.game.trigger.Trigger;
|
||||
* <p>
|
||||
* Card_Keywords class.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
*/
|
||||
public class KeywordsChange implements Cloneable {
|
||||
@@ -43,9 +43,9 @@ public class KeywordsChange implements Cloneable {
|
||||
private boolean removeAllKeywords;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Construct a new {@link KeywordsChange}.
|
||||
*
|
||||
*
|
||||
* @param keywordList the list of keywords to add.
|
||||
* @param removeKeywordList the list of keywords to remove.
|
||||
* @param removeAll whether to remove all keywords.
|
||||
@@ -81,9 +81,9 @@ public class KeywordsChange implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* getKeywords.
|
||||
*
|
||||
*
|
||||
* @return ArrayList<String>
|
||||
*/
|
||||
public final Collection<KeywordInterface> getKeywords() {
|
||||
@@ -94,9 +94,9 @@ public class KeywordsChange implements Cloneable {
|
||||
return this.removeKeywordInterfaces;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* getRemoveKeywords.
|
||||
*
|
||||
*
|
||||
* @return ArrayList<String>
|
||||
*/
|
||||
public final List<String> getRemoveKeywords() {
|
||||
@@ -104,9 +104,9 @@ public class KeywordsChange implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* isRemoveAllKeywords.
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isRemoveAllKeywords() {
|
||||
@@ -137,10 +137,6 @@ public class KeywordsChange implements Cloneable {
|
||||
public final boolean removeKeywordfromAdd(final String keyword) {
|
||||
return keywords.remove(keyword);
|
||||
}
|
||||
|
||||
public final void addKeyword(final String keyword) {
|
||||
keywords.add(keyword);
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
keywords.setHostCard(host);
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Kicker extends KeywordWithCost {
|
||||
|
||||
public Kicker() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
List<String> l = Lists.newArrayList(TextUtil.split(details, ':'));
|
||||
@@ -20,7 +20,7 @@ public class Kicker extends KeywordWithCost {
|
||||
if (l.size() > 1)
|
||||
cost2 = new Cost(l.get(1), false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String formatReminderText(String reminderText) {
|
||||
if (cost2 == null) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package forge.game.keyword;
|
||||
public class Suspend extends KeywordWithCostAndAmount {
|
||||
|
||||
boolean withoutCostAndAmount = false;
|
||||
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
if ("".equals(details)) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 &¶ms.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,29 +585,98 @@ 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();
|
||||
|
||||
// Change color words
|
||||
if (changeColorWordsTo != null) {
|
||||
final byte color;
|
||||
if (changeColorWordsTo.equals("ChosenColor")) {
|
||||
if (hostCard.hasChosenColor()) {
|
||||
color = MagicColor.fromName(Iterables.getFirst(hostCard.getChosenColors(), null));
|
||||
} else {
|
||||
color = 0;
|
||||
// for Volrath’s 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);
|
||||
}
|
||||
|
||||
// Volrath’s Shapeshifter has that card’s 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());
|
||||
}
|
||||
} else {
|
||||
color = MagicColor.fromName(changeColorWordsTo);
|
||||
}
|
||||
|
||||
if (color != 0) {
|
||||
final String colorName = MagicColor.toLongString(color);
|
||||
affectedCard.addChangedTextColorWord("Any", colorName, se.getTimestamp(), stAb.getId());
|
||||
if (stAb.hasParam("AddNames")) { // currently only for AllNonLegendaryCreatureNames
|
||||
affectedCard.addChangedName(null, true, se.getTimestamp(), stAb.getId());
|
||||
}
|
||||
|
||||
// Change color words
|
||||
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));
|
||||
} else {
|
||||
color = 0;
|
||||
}
|
||||
} else {
|
||||
color = MagicColor.fromName(changeColorWordsTo);
|
||||
}
|
||||
|
||||
if (color != 0) {
|
||||
final String colorName = MagicColor.toLongString(color);
|
||||
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;
|
||||
|
||||
@@ -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"))) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
10
forge-gui/res/cardsfolder/upcoming/gluttonous_guest.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/gluttonous_guest.txt
Normal 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.
|
||||
11
forge-gui/res/cardsfolder/upcoming/grolnok_the_omnivore.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/grolnok_the_omnivore.txt
Normal 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.
|
||||
8
forge-gui/res/cardsfolder/upcoming/gryff_rider.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/gryff_rider.txt
Normal 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.)
|
||||
5
forge-gui/res/cardsfolder/upcoming/massive_might.txt
Normal file
5
forge-gui/res/cardsfolder/upcoming/massive_might.txt
Normal 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.
|
||||
@@ -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.
|
||||
15
forge-gui/res/cardsfolder/upcoming/olivia_crimson_bride.txt
Normal file
15
forge-gui/res/cardsfolder/upcoming/olivia_crimson_bride.txt
Normal 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."
|
||||
12
forge-gui/res/cardsfolder/upcoming/overcharged_amalgam.txt
Normal file
12
forge-gui/res/cardsfolder/upcoming/overcharged_amalgam.txt
Normal 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.
|
||||
@@ -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.
|
||||
11
forge-gui/res/cardsfolder/upcoming/savior_of_ollenbock.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/savior_of_ollenbock.txt
Normal 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.
|
||||
8
forge-gui/res/cardsfolder/upcoming/sigardas_summons.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/sigardas_summons.txt
Normal 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.
|
||||
14
forge-gui/res/cardsfolder/upcoming/sorin_the_mirthless.txt
Normal file
14
forge-gui/res/cardsfolder/upcoming/sorin_the_mirthless.txt
Normal 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
10
forge-gui/res/cardsfolder/upcoming/voldaren_estate.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/voldaren_estate.txt
Normal 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.")
|
||||
@@ -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.)
|
||||
@@ -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.
|
||||
10
forge-gui/res/cardsfolder/upcoming/wedding_invitation.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/wedding_invitation.txt
Normal 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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user