This commit is contained in:
Alessandro Coli
2019-05-21 20:38:45 +02:00
63 changed files with 477 additions and 107 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -191,6 +191,27 @@ public class AiAttackController {
return false;
}
// the attacker will die to a triggered ability (e.g. Sarkhan the Masterless)
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
for (Trigger t : c.getTriggers()) {
if (t.getMode() == TriggerType.Attacks) {
SpellAbility sa = t.getOverridingAbility();
if (sa == null && t.hasParam("Execute")) {
sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
}
if (sa != null && sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
List<Card> valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa);
// TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine
// how much damage is dealt by each of the creatures in the valid list.
if (attacker.getNetToughness() <= valid.size()) {
return false;
}
}
}
}
}
if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) {
return true;
}

View File

@@ -129,7 +129,10 @@ public enum AiProps { /** */
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"); /** */
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->

View File

@@ -26,6 +26,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -676,7 +677,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// only use blink or bounce effects
if (!(destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone))
if (!(destination.equals(ZoneType.Exile)
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
&& !destination.equals(ZoneType.Hand)) {
return false;
}
@@ -936,7 +938,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if it's blink or bounce, try to save my about to die stuff
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|| (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
// save my about to die stuff
Card tobounce = canBouncePermanent(ai, sa, list);
@@ -1230,6 +1232,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// filter out untargetables
CardCollectionView aiPermanents = CardLists
.filterControlledBy(list, ai);
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
// Felidar Guardian + Saheeli Rai combo support
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
@@ -1273,6 +1276,33 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
}
// Reload planeswalkers
else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) {
int maxLoyaltyToConsider = 2;
int loyaltyDiff = 2;
int chance = 30;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
}
if (MyRandom.percentTrue(chance)) {
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
}
});
for (Card pw : aiPlaneswalkers) {
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
int freshLoyalty = pw.getCurrentState().getBaseLoyalty();
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
return pw;
}
}
}
}
return null;
}

View File

@@ -75,7 +75,7 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
sa.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) {
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
}
@@ -115,7 +115,7 @@ public class DamageDealAi extends DamageAiBase {
}
// Set PayX here to maximum value. It will be adjusted later depending on the target.
source.setSVar("PayX", Integer.toString(dmg));
sa.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).contains("InYourHand") && source.getZone().is(ZoneType.Hand)) {
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
@@ -283,7 +283,7 @@ public class DamageDealAi extends DamageAiBase {
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
actualPay = actualPay > 2 ? actualPay - 2 : 0;
}
source.setSVar("PayX", Integer.toString(actualPay));
sa.setSVar("PayX", Integer.toString(actualPay));
}
}
@@ -291,7 +291,7 @@ public class DamageDealAi extends DamageAiBase {
// Check to ensure that we have enough counters to remove per the defined PayX
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(sa.getSVar("PayX"))) {
return false;
}
break;
@@ -941,7 +941,7 @@ public class DamageDealAi extends DamageAiBase {
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
sa.setSVar("PayX", Integer.toString(dmg));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -971,7 +971,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
source.setSVar("PayX", Integer.toString(actualPay));
sa.setSVar("PayX", Integer.toString(actualPay));
}
}
@@ -1031,7 +1031,7 @@ public class DamageDealAi extends DamageAiBase {
saTgt.resetTargets();
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
source.setSVar("PayX", Integer.toString(dmg));
sa.setSVar("PayX", Integer.toString(dmg));
return true;
}

View File

@@ -1,9 +1,13 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -126,6 +130,25 @@ public class DigAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
if (bestChoice == null) {
// no creatures, but maybe there's a morphable card that can be played as a creature?
CardCollection morphs = CardLists.filter(valid, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.hasKeyword(Keyword.MORPH);
}
});
if (!morphs.isEmpty()) {
bestChoice = ComputerUtilCard.getBestAI(morphs);
}
}
// still nothing, so return the worst card since it'll be unplayable from exile (e.g. Vivien, Champion of the Wilds)
return bestChoice != null ? bestChoice : ComputerUtilCard.getWorstAI(valid);
}
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
} else {

View File

@@ -211,9 +211,11 @@ public class DrawAi extends SpellAbilityAi {
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
final boolean drawback = sa.getParent() != null;
final Game game = ai.getGame();
final String logic = sa.getParamOrDefault("AILogic", "");
final boolean considerPrimary = logic.equals("ConsiderPrimary");
final boolean drawback = (sa.getParent() != null) && !considerPrimary;
boolean assumeSafeX = false; // if true, the AI will assume that the X value has been set to a value that is safe to draw
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
@@ -241,7 +243,12 @@ public class DrawAi extends SpellAbilityAi {
numCards = Integer.parseInt(source.getSVar("PayX"));
} else {
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
// try not to overdraw
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
numCards = Math.min(numCards, safeDraw);
source.setSVar("PayX", Integer.toString(numCards));
assumeSafeX = true;
}
xPaid = true;
}
@@ -492,7 +499,8 @@ public class DrawAi extends SpellAbilityAi {
if ((computerHandSize + numCards > computerMaxHandSize)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& !sa.isTrigger()) {
&& !sa.isTrigger()
&& !assumeSafeX) {
// Don't draw too many cards and then risk discarding cards at
// EOT
if (!drawback) {

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -185,8 +185,7 @@ public class GameAction {
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
// up on the wrong card state etc.).
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
c.setState(CardStateName.Original, true);
c.runFaceupCommands();
c.turnFaceUp();
}
if (!c.isToken()) {
@@ -306,8 +305,8 @@ public class GameAction {
}
if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard) && repres == ReplacementResult.Prevented) {
copied.getOwner().removeInboundToken(copied);
return moveToGraveyard(c, cause, params);
copied.getOwner().removeInboundToken(copied);
return moveToGraveyard(c, cause, params);
}
copied.getOwner().removeInboundToken(copied);
return c;
@@ -445,13 +444,9 @@ public class GameAction {
// rule 504.6: reveal a face-down card leaving the stack
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) {
// FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
boolean trackerFrozen = game.getTracker().isFrozen();
game.getTracker().unfreeze();
c.setState(CardStateName.Original, true);
reveal(new CardCollection(c), c.getOwner(), true, "Face-down card moves from the stack: ");
c.setState(CardStateName.FaceDown, true);
if (trackerFrozen) { game.getTracker().freeze(); }
Card revealLKI = CardUtil.getLKICopy(c);
revealLKI.turnFaceUp(true, false);
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card moves from the stack: ");
}
if (fromBattlefield) {
@@ -480,14 +475,12 @@ public class GameAction {
}
// Reveal if face-down
if (wasFacedown) {
// FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
boolean trackerFrozen = game.getTracker().isFrozen();
game.getTracker().unfreeze();
c.setState(CardStateName.Original, true);
reveal(new CardCollection(c), c.getOwner(), true, "Face-down card leaves the battlefield: ");
c.setState(CardStateName.FaceDown, true);
if (trackerFrozen) { game.getTracker().freeze(); }
copied.setState(CardStateName.Original, true);
Card revealLKI = CardUtil.getLKICopy(c);
revealLKI.turnFaceUp(true, false);
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card leaves the battlefield: ");
copied.setState(CardStateName.Original, true);
}
unattachCardLeavingBattlefield(copied);
// Remove all changed keywords
@@ -506,9 +499,9 @@ public class GameAction {
game.getStack().fizzleTriggersOnStackTargeting(copied, TriggerType.DamageDoneOnce);
}
} else if (zoneTo.is(ZoneType.Graveyard)
|| zoneTo.is(ZoneType.Hand)
|| zoneTo.is(ZoneType.Library)
|| zoneTo.is(ZoneType.Exile)) {
|| zoneTo.is(ZoneType.Hand)
|| zoneTo.is(ZoneType.Library)
|| zoneTo.is(ZoneType.Exile)) {
copied.setTimestamp(game.getNextTimestamp());
copied.clearOptionalCostsPaid();
if (copied.isFaceDown()) {
@@ -1238,14 +1231,14 @@ public class GameAction {
}
if (reason == null) {
List<Player> notLost = Lists.newArrayList();
Set<Integer> teams = Sets.newHashSet();
for (Player p : allPlayers) {
List<Player> notLost = Lists.newArrayList();
Set<Integer> teams = Sets.newHashSet();
for (Player p : allPlayers) {
if (p.getOutcome() == null || p.getOutcome().hasWon()) {
notLost.add(p);
teams.add(p.getTeam());
notLost.add(p);
teams.add(p.getTeam());
}
}
}
int cntNotLost = notLost.size();
if (cntNotLost == 1) {
reason = GameEndReason.AllOpponentsLost;
@@ -1733,7 +1726,7 @@ public class GameAction {
} while (!allKept);
//Vancouver Mulligan as a scry with the decisions inside
List<Player> scryers = Lists.newArrayList();
List<Player> scryers = Lists.newArrayList();
for(Player p : whoCanMulligan) {
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
scryers.add(p);

View File

@@ -72,6 +72,17 @@ public final class GameActionUtil {
if (sa.isSpell()) {
boolean lkicheck = false;
// need to be done before so it works with Vivien and Zoetic Cavern
if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) {
if (!source.isLKI()) {
source = CardUtil.getLKICopy(source);
}
source.turnFaceUp(false, false);
lkicheck = true;
}
if (sa.hasParam("Bestow") && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) {
if (!source.isLKI()) {
source = CardUtil.getLKICopy(source);
@@ -106,23 +117,16 @@ public final class GameActionUtil {
}
final Card host = o.getHost();
final SpellAbility newSA = sa.copy(activator);
final SpellAbilityRestriction sar = newSA.getRestrictions();
if (o.isWithFlash()) {
sar.setInstantSpeed(true);
}
sar.setZone(null);
newSA.setMayPlay(o.getAbility());
newSA.setMayPlayOriginal(sa);
SpellAbility newSA = null;
boolean changedManaCost = false;
if (o.getPayManaCost() == PayManaCost.NO) {
newSA = sa.copyWithNoManaCost(activator);
newSA.setBasicSpell(false);
newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
changedManaCost = true;
} else if (o.getAltManaCost() != null) {
newSA = sa.copyWithManaCostReplaced(activator, o.getAltManaCost());
newSA.setBasicSpell(false);
newSA.setPayCosts(newSA.getPayCosts().copyWithDefinedMana(o.getAltManaCost()));
changedManaCost = true;
if (host.hasSVar("AsForetoldSplitCMCHack")) {
// TODO: This is a temporary workaround for As Foretold interaction with split cards, better solution needed.
@@ -138,7 +142,17 @@ public final class GameActionUtil {
}
}
}
} else {
newSA = sa.copy(activator);
}
final SpellAbilityRestriction sar = newSA.getRestrictions();
if (o.isWithFlash()) {
sar.setInstantSpeed(true);
}
sar.setZone(null);
newSA.setMayPlay(o.getAbility());
newSA.setMayPlayOriginal(sa);
if (changedManaCost) {
if ("0".equals(sa.getParam("ActivationLimit")) && sa.getHostCard().getManaCost().isNoCost()) {
sar.setLimitToCheck(null);

View File

@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import com.google.common.collect.Sets;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
@@ -43,7 +42,7 @@ public class ManifestEffect extends SpellAbilityEffect {
//check if lki would be a land entering the battlefield
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) {
Card lki = CardUtil.getLKICopy(c);
lki.setState(CardStateName.FaceDown, false);
lki.turnFaceDownNoUpdate();
lki.setManifested(true);
lki.setLastKnownZone(p.getZone(ZoneType.Battlefield));
CardCollection preList = new CardCollection(lki);

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
@@ -63,8 +62,8 @@ public class MillEffect extends SpellAbilityEffect {
}
for (final Card c : milled) {
c.setExiledWith(host);
if (facedown) {
c.setState(CardStateName.FaceDown, true);
if (facedown) {
c.turnFaceDown(true);
}
}
}

View File

@@ -14,7 +14,6 @@ import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.StaticData;
import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
@@ -153,7 +152,7 @@ public class PlayEffect extends SpellAbilityEffect {
final boolean wasFaceDown;
if (tgtCard.isFaceDown()) {
tgtCard.setState(CardStateName.Original, false);
tgtCard.turnFaceUp(false, false);
wasFaceDown = true;
} else {
wasFaceDown = false;
@@ -165,7 +164,7 @@ public class PlayEffect extends SpellAbilityEffect {
if (optional && !controller.getController().confirmAction(sa, null, TextUtil.concatWithSpace("Do you want to play", TextUtil.addSuffix(tgtCard.toString(),"?")))) {
if (wasFaceDown) {
tgtCard.setState(CardStateName.FaceDown, false);
tgtCard.turnFaceDownNoUpdate();
}
saidNoTo.add(tgtCard);
continue;

View File

@@ -7,6 +7,7 @@ import forge.game.GameLogEntryType;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player;
@@ -73,13 +74,9 @@ public class SetStateEffect extends SpellAbilityEffect {
// facedown cards that are not Permanent, can't turn faceup there
if ("TurnFace".equals(mode) && tgt.isFaceDown() && tgt.isInZone(ZoneType.Battlefield)
&& !tgt.getState(CardStateName.Original).getType().isPermanent()) {
// need to cache manifest status
boolean manifested = tgt.isManifested();
// FIXME setState has to many other Consequences, use LKI?
tgt.setState(CardStateName.Original, true);
game.getAction().reveal(new CardCollection(tgt), tgt.getOwner(), true, "Face-down card can't turn face up");
tgt.setState(CardStateName.FaceDown, true);
tgt.setManifested(manifested);
Card lki = CardUtil.getLKICopy(tgt);
lki.turnFaceUp(true, false);
game.getAction().reveal(new CardCollection(lki), lki.getOwner(), true, "Face-down card can't turn face up");
continue;
}

View File

@@ -2743,7 +2743,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
return result;
}
public final void setMayPlay(final Player player, final boolean withoutManaCost, final String altManaCost, final boolean withFlash, final boolean grantZonePermissions, final StaticAbility sta) {
public final void setMayPlay(final Player player, final boolean withoutManaCost, final Cost altManaCost, final boolean withFlash, final boolean grantZonePermissions, final StaticAbility sta) {
this.mayPlay.put(sta, new CardPlayOption(player, sta, withoutManaCost, altManaCost, withFlash, grantZonePermissions));
}
public final void removeMayPlay(final StaticAbility sta) {
@@ -5803,7 +5803,7 @@ public class Card extends GameEntity implements Comparable<Card> {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
if (isFaceDown() && isInZone(ZoneType.Exile) && !mayPlay(player).isEmpty()) {
if (isFaceDown() && isInZone(ZoneType.Exile)) {
for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}

View File

@@ -1,9 +1,8 @@
package forge.game.card;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import org.apache.commons.lang3.StringUtils;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
@@ -20,12 +19,12 @@ public final class CardPlayOption {
private final PayManaCost payManaCost;
private final boolean withFlash;
private final boolean grantsZonePermissions;
private final String altManaCost;
private final Cost altManaCost;
public CardPlayOption(final Player player, final StaticAbility sta, final boolean withoutManaCost, final String altManaCost, final boolean withFlash, final boolean grantZonePermissions) {
public CardPlayOption(final Player player, final StaticAbility sta, final boolean withoutManaCost, final Cost altManaCost, final boolean withFlash, final boolean grantZonePermissions) {
this(player, sta, withoutManaCost ? PayManaCost.NO : PayManaCost.YES, altManaCost, withFlash, grantZonePermissions);
}
private CardPlayOption(final Player player, final StaticAbility sta, final PayManaCost payManaCost, final String altManaCost, final boolean withFlash,
private CardPlayOption(final Player player, final StaticAbility sta, final PayManaCost payManaCost, final Cost altManaCost, final boolean withFlash,
final boolean grantZonePermissions) {
this.player = player;
this.sta = sta;
@@ -66,10 +65,10 @@ public final class CardPlayOption {
public boolean grantsZonePermissions() { return grantsZonePermissions; }
public String getAltManaCost() { return altManaCost; }
public Cost getAltManaCost() { return altManaCost; }
private String getFormattedAltManaCost() {
return new ManaCost(new ManaCostParser(altManaCost)).getSimpleString();
return altManaCost.toSimpleString();
}
@Override

View File

@@ -371,13 +371,18 @@ public class CardView extends GameEntityView {
switch (zone) {
case Ante:
case Command:
case Exile:
case Battlefield:
case Graveyard:
case Flashback:
case Stack:
//cards in these zones are visible to all
return true;
case Exile:
//in exile, only face up cards and face down cards you can look at should be shown (since "exile face down" is a thing)
if (!isFaceDown()) {
return true;
}
break;
case Hand:
if (controller.hasKeyword("Play with your hand revealed.")) {
return true;
@@ -628,6 +633,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Cloned, c.isCloned());
set(TrackableProperty.SplitCard, isSplitCard);
set(TrackableProperty.FlipCard, c.isFlipCard());
set(TrackableProperty.Facedown, c.isFaceDown());
final Card cloner = c.getCloner();

View File

@@ -933,6 +933,18 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return copyWithDefinedCost(new Cost(abCost, isAbility()));
}
public SpellAbility copyWithManaCostReplaced(Player active, Cost abCost) {
final SpellAbility newSA = copy(active);
if (newSA == null) {
return null; // the ability was not copyable, e.g. a Suspend SA may get here
}
final Cost newCost = newSA.getPayCosts().copyWithNoMana();
newCost.add(abCost);
newSA.setPayCosts(newCost);
return newSA;
}
public boolean isTrigger() {
return trigger;
}

View File

@@ -737,8 +737,17 @@ public final class StaticAbilityContinuous {
}
if (controllerMayPlay && (mayPlayLimit == null || stAb.getMayPlayTurn() < mayPlayLimit)) {
String mayPlayAltCost = mayPlayAltManaCost;
if (mayPlayAltCost != null && mayPlayAltCost.contains("ConvertedManaCost")) {
final String costcmc = Integer.toString(affectedCard.getCMC());
mayPlayAltCost = mayPlayAltCost.replace("ConvertedManaCost", costcmc);
}
Player mayPlayController = params.containsKey("MayPlayCardOwner") ? affectedCard.getOwner() : controller;
affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost, mayPlayAltManaCost, mayPlayWithFlash, mayPlayGrantZonePermissions, stAb);
affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost,
mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null,
mayPlayWithFlash, mayPlayGrantZonePermissions, stAb);
}
}

View File

@@ -6,7 +6,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.24.001</alpha-version>
<alpha-version>1.6.25.001</alpha-version>
<sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass>
@@ -19,7 +19,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>

View File

@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.24.001</alpha-version>
<alpha-version>1.6.25.001</alpha-version>
</properties>
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -34,7 +34,7 @@ import java.util.List;
import java.util.Stack;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.24.001";
public static final String CURRENT_VERSION = "1.6.25.001";
private static final ApplicationListener app = new Forge();
private static Clipboard clipboard;

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -1,7 +1,5 @@
#Add one announcement per line
War for the Spark is here! We're still missing a few cards, but hopefully we'll get those added soon.
A very drastic change has happened for tokens and token images. Check out the information here: https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=23317
War for the Spark is all added. I'm sure we didn't get all of the bugs fully worked out, so please let us know if you see anything weird.
A significant improvement was made to how we cache images which should improve memory usage
[b]Forge now requires Java 8 (or newer). You will not be able to start the game if you are not yet running Java 8.[/b]
We have a Discord server for hanging out with Forge devs and other Forge fans. Feel free to [url=https://discord.gg/3v9JCVr]jump on in and say hi[/url]!
Online Multiplayer is once again functional!

View File

@@ -3,6 +3,7 @@ Austinio7116
Churrufli
DrDev
excessum
Flair
Gos
Hanmac
Indigo Dragon

View File

@@ -7,7 +7,7 @@ Some people use the Windows application 7zip. This utility can be found at http:
Once the Forge archive has been decompressed you should then be able to launch Forge by using the included launcher. Launching Forge by double clicking on the forge jar file in the past caused a java heap space error. Forge's memory requirements have increased over time and the launchers increase the java heap space available to Forge. Currently you can launch Forge by double clicking on the forge jar file without a java heap space error but this is likely to change as we add in more sounds, icons, etc.
- The Mac OS application version -
We haven't been able to distribute the OS X Application version of Forge in sometime. We've recently automated our release tools, and will continue to look in the viability of creating this file now that things are autoamted.
We provide separate macOS builds of desktop and mobile (backported) Forge packaged as native Mac applications. Please check the relevant thread for details and download links.
- Online Multiplayer -

View File

@@ -252,6 +252,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
# A chance (per check) to activate this ability
BLINK_RELOAD_PLANESWALKER_CHANCE=15
# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the

View File

@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
# A chance (per check) to activate this ability
BLINK_RELOAD_PLANESWALKER_CHANCE=30
# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the

View File

@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=400
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=5
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=5
# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
# A chance (per check) to activate this ability
BLINK_RELOAD_PLANESWALKER_CHANCE=70
# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the

View File

@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
# A chance (per check) to activate this ability
BLINK_RELOAD_PLANESWALKER_CHANCE=60
# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the

View File

@@ -4,7 +4,7 @@ Types:Creature Human Soldier
PT:2/4
K:Cycling:2 W
T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | Secondary$ True | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | TriggerController$ TriggeredCardController | Secondary$ True | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_soldier | TokenOwner$ You | LegacyImage$ w 1 1 soldier arb
SVar:Picture:http://www.wizards.com/global/images/magic/general/bant_sojourners.jpg
Oracle:When you cycle Bant Sojourners or it dies, you may create a 1/1 white Soldier creature token.\nCycling {2}{W} ({2}{W}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,7 @@
Name:Bolas's Citadel
ManaCost:3 B B B
Types:Legendary Artifact
S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time.
S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | MayPlayAltManaCost$ PayLife<ConvertedManaCost> | Description$ You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
A:AB$ LoseLife | Cost$ T Sac<10/Permanent.nonLand/nonland permanent> | Defined$ Player.Opponent | LifeAmount$ 10 | SpellDescription$ Each opponent loses 10 life.
Oracle:You may look at the top card of your library any time.\nYou may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.\n{T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.

View File

@@ -2,7 +2,7 @@ Name:Finale of Revelation
ManaCost:X U U
Types:Sorcery
A:SP$ ChangeZoneAll | Cost$ X U U | ChangeType$ Card | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Random$ True | SubAbility$ DBDraw | UseAllOriginZones$ True | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 | SpellDescription$ Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.
SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBUntap
SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBUntap | AILogic$ ConsiderPrimary
SVar:DBUntap:DB$ Untap | UntapUpTo$ True | UntapType$ Land | Amount$ 5 | SubAbility$ DBEffect | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
SVar:DBEffect:DB$ Effect | Name$ Finale of Revelation Effect | StaticAbilities$ STHandSize | Duration$ Permanent | SubAbility$ DBChange | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
SVar:STHandSize:Mode$ Continuous | EffectZone$ Command | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.

View File

@@ -12,4 +12,5 @@ T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$
SVar:TrigFromGraveyard:DB$ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 2
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Exile | ValidCard$ Card.Self | Execute$ TrigFromExile | OptionalDecider$ You | TriggerController$ TriggeredCardController | Secondary$ True | TriggerDescription$ When CARDNAME dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.
SVar:TrigFromExile:DB$ChangeZone | Defined$ TriggeredCard | Origin$ Exile | Destination$ Library | LibraryPosition$ 2
SVar:PlayMain1:TRUE
Oracle:Deathtouch\nWhen God-Eternal Rhonas enters the battlefield, double the power of each other creature you control until end of turn. Those creatures gain vigilance until end of turn.\nWhen God-Eternal Rhonas dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.

View File

@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Legendary Planeswalker Kasmina
Loyalty:5
S:Mode$ RaiseCost | ValidTarget$ Creature.YouOwn+inZoneBattlefield | Activator$ Player.Opponent | Type$ Spell | Amount$ 2 | Description$ Spells your opponents cast that target a creature or planeswalker you control cost {2} more to cast.
A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ u_2_2_Wizard | TokenOwner$ You | LegacyImage$ u 2 2 Wizard war | SubAbility$ DBDraw | SpellDescription$ Create a 2/2 blue Wizard creature token. Draw a card, then discard a card.
A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ u_2_2_wizard | TokenOwner$ You | LegacyImage$ u 2 2 Wizard war | SubAbility$ DBDraw | SpellDescription$ Create a 2/2 blue Wizard creature token. Draw a card, then discard a card.
SVar:DBDraw:DB$Draw | NumCards$ 1 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1
AI:RemoveDeck:Random

View File

@@ -6,7 +6,7 @@ S:Mode$ Continuous | Affected$ Creature.YouCtrl,Planeswalker.YouCtrl | AddKeywor
SVar:NonStackingEffect:True
A:AB$ DealDamage | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker.
A:AB$ ChangeZone | Cost$ SubCounter<X/LOYALTY> | Planeswalker$ True | Origin$ Graveyard | Destination$ Battlefield | Announce$ X | References$ X | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature with converted mana cost X from your graveyard | SubAbility$ Animate | SpellDescription$ Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types.
SVar:Animate:DB$Animate | Defined$ Targeted | Types$ Vampire | Keywords$ Animate | Permanent$ True
SVar:Animate:DB$ Animate | Defined$ Targeted | Types$ Vampire | Permanent$ True
SVar:X:Targeted$CardManaCost
AI:RemoveDeck:All
Oracle:As long as it's your turn, creatures and planeswalkers you control have lifelink.\n[+2]: Sorin, Vengeful Bloodlord deals 1 damage to target player or planeswalker.\n-X: Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types.

View File

@@ -4,7 +4,7 @@ Types:Creature Elemental
PT:3/4
K:Flying
A:AB$ Tap | Cost$ U ExileFromTop<1/Card> | ValidTgts$ Creature.withFlying | TgtPrompt$ Select target creature with flying | SpellDescription$ Tap target creature with flying.
A:AB$ Pump | Cost$ U ExileFromTop<1/Card> | Defined$ Self | NumAtt$ +X | NumDef$ +X | SpellDescription$ If the exiled card is a snow land, CARDNAME gets +1/+1 until end of turn.
A:AB$ Pump | Cost$ U ExileFromTop<1/Card> | Defined$ Self | NumAtt$ +X | NumDef$ +X | References$ X | SpellDescription$ If the exiled card is a snow land, CARDNAME gets +1/+1 until end of turn.
SVar:X:Exiled$Valid Land.Snow
SVar:Picture:http://www.wizards.com/global/images/magic/general/storm_elemental.jpg
Oracle:Flying\n{U}, Exile the top card of your library: Tap target creature with flying.\n{U}, Exile the top card of your library: If the exiled card is a snow land, Storm Elemental gets +1/+1 until end of turn.

View File

@@ -0,0 +1,10 @@
Name:Teferi's Time Twist
ManaCost:1 U
Types:Instant
A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | AILogic$ DelayedBlink | SubAbility$ DBAnimate | SpellDescription$ Exile target permanent you control. Return that card to the battlefield under its owners control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Keywords$ etbCounter:P1P1:1:ValidCard$ Creature.IsRemembered | RememberObjects$ Remembered | SubAbility$ DelTrig | AILogic$ Always
SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigBounce | RememberObjects$ Remembered | AILogic$ Always | TriggerDescription$ Return that card to the battlefield under its owners control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.
SVar:TrigBounce:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ DelayTriggerRemembered
SVar:Picture:https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=460999&type=card
DeckHas:Ability$Counters
Oracle:Exile target permanent you control. Return that card to the battlefield under its owner's control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.

View File

@@ -0,0 +1,16 @@
Name:Ugin, the Ineffable
ManaCost:6
Types:Legendary Planeswalker Ugin
Loyalty:4
S:Mode$ ReduceCost | ValidCard$ Card.Colorless | Type$ Spell | Activator$ You | Amount$ 2 | Description$ Colorless spells you cast cost {2} less to cast.
A:AB$ PeekAndReveal | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | PeekAmount$ 1 | NoReveal$ True | SubAbility$ DBMill | StackDescription$ SpellDescription | SpellDescription$ Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand.
SVar:DBMill:DB$ Mill | Defined$ You | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | ExileFaceDown$ True | NoReveal$ True | SubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenScript$ c_2_2_spirit | ImprintTokens$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | Triggers$ TrigLeavesBattlefield | SVars$ DBReturn,DBExile | RememberObjects$ Remembered | ImprintCards$ Imprinted | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:TrigLeavesBattlefield:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ DBReturn | TriggerDescription$ When that token leaves the battlefield, put the exiled card into your hand.
SVar:DBReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Hand | ChangeType$ Card.IsRemembered | SubAbility$ DBExile
SVar:DBExile:DB$ ChangeZoneAll | Origin$ Command | Destination$ Exile | ChangeType$ Card.Self
A:AB$ Destroy | Cost$ SubCounter<3/LOYALTY> | ValidTgts$ Permanent.nonColorless | TgtPrompt$ Select target permanent that's one or more colors | Planeswalker$ True | SpellDescription$ Destroy target permanent thats one or more colors.
DeckHas:Ability$Token
Oracle:Colorless spells you cast cost {2} less to cast.\n+1: Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand.\n3: Destroy target permanent thats one or more colors.

View File

@@ -0,0 +1,11 @@
Name:Cabal Therapist
ManaCost:B
Types:Creature Horror
PT:1/1
K:Menace
T:Mode$ Phase | Phase$ Main1 | PreCombatMain$ True | ValidPlayer$ You | Execute$ DBImmediateTrigger | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your precombat main phase, you may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name.
SVar:DBImmediateTrigger:DB$ ImmediateTrigger | Execute$ NameCard | TriggerDescription$ You may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name. | UnlessCost$ Sac<1/Creature> | UnlessPayer$ You | UnlessSwitched$ True
SVar:NameCard:DB$ NameCard | Defined$ You | ValidCards$ Card.nonLand | ValidDesc$ nonland | SubAbility$ DBDiscard | SpellDescription$ Choose a nonland card name. Target player reveals their hand and discards all cards with that name.
SVar:DBDiscard:DB$ Discard | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ RevealDiscardAll | DiscardValid$ Card.NamedCard
AI:RemoveDeck:All
Oracle:Menace\nAt the beginning of your precombat main phase, you may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name.

View File

@@ -0,0 +1,11 @@
Name:Deep Forest Hermit
ManaCost:3 G G
Types:Creature Elf Druid
K:Vanishing:3
PT:1/1
S:Mode$ Continuous | Affected$ Creature.Squirrel+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Squirrel creatures you control get +1/+1.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create four 1/1 green Squirrel creature tokens.
SVar:TrigToken:DB$Token | TokenAmount$ 4 | TokenScript$ g_1_1_squirrel | TokenOwner$ You | LegacyImage$ g 1 1 squirrel mh1
SVar:PlayMain1:TRUE
SVar:Picture:http://mythicspoiler.com/mh1/cards/deepforesthermit.jpg
Oracle:Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)\nWhen Deep Forest Hermit enters the battlefield, create four 1/1 green Squirrel creature tokens.\nSquirrels you control get +1/+1.

View File

@@ -0,0 +1,7 @@
Name:Force of Negation
ManaCost:1 U U
Types:Instant
A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target nonCreature Spell | ValidTgts$ Card.nonCreature | Destination$ Exile | SpellDescription$ Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyayrd.
SVar:AltCost:Cost$ ExileFromHand<1/Card.Blue> | OpponentTurn$ True | Description$ If it's not your turn, you may exile a blue card from your hand rather than pay this spell's mana cost.
Svar:Picture:http://mythicspoiler.com/mh1/cards/forceofnegation.jpg
Oracle:If it's not your turn, you may exile a blue card from your hand rather than pay this spell's mana cost.\nCounter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyayrd.

View File

@@ -0,0 +1,6 @@
Name:Force of Vigor
ManaCost:2 G G
Types:Instant
A:SP$ Destroy | Cost$ 2 G G | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy up to two target artifacts and/or enchantments.
SVar:AltCost:Cost$ ExileFromHand<1/Card.Green> | OpponentTurn$ True | Description$ If it's not your turn, you may exile a green card from your hand rather than pay this spell's mana cost.
Oracle:If it's not your turn, you may exile a green card from your hand rather than pay this spell's mana cost.\nDestroy up to two target artifacts and/or enchantments.

View File

@@ -0,0 +1,8 @@
Name:Goblin War Party
ManaCost:3 R
Types:Sorcery
K:Entwine:2 R
A:SP$ Charm | Cost$ 3 R | Choices$ DBTokens,DBBuff | Defined$ You | CharmNum$ 1
SVar:DBTokens:DB$ Token | TokenAmount$ 3 | TokenScript$ r_1_1_goblin | TokenOwner$ You | LegacyImage$ r 1 1 goblin mh1 | SpellDescription$ Create three 1/1 red Goblin creature tokens.
SVar:DBBuff:DB$ PumpAll | NumAtt$ 1 | NumDef$ 1 | ValidCards$ Creature.YouCtrl | KW$ Haste | SpellDescription$ Creatures you control get +1/+1 and gain haste until end of turn.
Oracle:Choose one —\n• Create three 1/1 red Goblin creature tokens.\n• Creatures you control get +1/+1 and gain haste until end of turn. \nEntwine {2}{R}(Choose both if you pay the entwine cost.)

View File

@@ -0,0 +1,8 @@
Name:Headless Specter
ManaCost:1 B B
Types:Creature Specter
PT:2/2
K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Hellbent$ True | Execute$ TrigDiscard | TriggerZones$ Battlefield | TriggerDescription$ Hellbent Whenever CARDNAME deals combat damage to a player, if you have no cards in hand, that player discards a card at random.
SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredTarget | NumCards$ 1 | Mode$ Random
Oracle:Flying\nHellbent — Whenever Hellbent Specter deals combat damage to a player, if you have no cards in hand, that player discards a card at random.

View File

@@ -0,0 +1,12 @@
Name:Ice-Fang Coatl
ManaCost:G U
Types:Snow Creature Snake
PT:1/1
K:Flash
K:Flying
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
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Deathtouch | CheckSVar$ X | SVarCompare$ GE3 | Description$ CARDNAME has deathtouch as long as you control at least three other snow permanents.
SVar:X:Count$Valid Permanent.Snow+Other
SVar:BuffedBy:Permanent.Snow
Oracle:Flash\nFlying\nWhen Ice-Fang Coatl enters the battlefield, draw a card.\nIce-Fang Coatl has deathtouch as long as you control at least three other snow permanents.

View File

@@ -0,0 +1,6 @@
Name:Impostor of the Sixth Pride
ManaCost:1 W
Types:Creature Shapeshifter
PT:3/1
K:Changeling
Oracle:Changeling (This card is every creature type.)

View File

@@ -0,0 +1,16 @@
Name:Morophon, the Boundless
ManaCost:7
Types:Legendary Creature Shapeshifter
PT:6/6
K:Changeling
K:ETBReplacement:Other:ChooseCT
SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. | StackDescription$ SpellDescription
AI:RemoveDeck:Random
S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ W | Description$ Spells you cast of the chosen type cost {W}{U}{B}{R}{G} less to cast. This effect reduces only the amount of colored mana you pay.
S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ B
S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ U
S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ R
S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ G
S:Mode$ Continuous | Affected$ Creature.ChosenType+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control of the chosen type get +1/+1.
SVar:PlayMain1:TRUE
Oracle:Changeling (This card is every creature type.)\nAs Morophon, the Boundless enters the battlefield, choose a creature type.\nSpells you cast of the chosen type cost {W}{U}{B}{R}{G} less to cast. This effect reduces only the amount of colored mana you pay.\nOther creatures you control of the chosen type get +1/+1.

View File

@@ -0,0 +1,5 @@
Name:Prismatic Vista
ManaCost:
Types:Land
A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle your library.
Oracle:{T}, Pay 1 life, Sacrifice Prismatic View: Search your library for a basic land card, put it onto the battlefield, then shuffle your library.

View File

@@ -0,0 +1,6 @@
Name:Savage Swipe
ManaCost:G
Types:Sorcery
A:SP$ Pump | Cost$ G | ValidTgts$ Creature.YouCtrl | AILogic$ Fight | AITgts$ Creature.YouCtrl+powerEQ2 | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | NumAtt$ +2 | NumDef$ +2 | ConditionDefined$ Targeted | ConditionPresent$ Creature.powerEQ2 | SpellDescription$ Target creature you control gets +2/+2 until end of turn. It fights target creature you don't control.
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control
Oracle:Target creature you control gets +2/+2 until end of turn if its power is 2. Then it fights target creature you don't control. (Each deals damage equal to its power to the other.)

View File

@@ -0,0 +1,11 @@
Name:Serra the Benevolent
ManaCost:2 W W
Types:Legendary Planeswalker Serra
Loyalty:4
A:AB$ PumpAll | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | ValidCards$ Creature.withFlying+YouCtrl | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ Creatures you control with flying get +1/+1 until end of turn.
A:AB$ Token | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | TokenScript$ w_4_4_angel_flying_vigilance | TokenOwner$ You | LegacyImage$ w 4 4 angel flying vigilance mh1 | SpellDescription$ Create a 4/4 white Angel creature token with flying and vigilance.
A:AB$ Effect | Cost$ SubCounter<6/LOYALTY> | Name$ Emblem - Serra the Benevolent | Image$ emblem_serra_the_benevolent | StaticAbilities$ STWorship | Planeswalker$ True | Ultimate$ True | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead."
SVar:STWorship:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ DamageLifeThreshold:1 | IsPresent$ Creature.YouCtrl | Description$ If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead.
DeckHas:Ability$Token
SVar:Picture:http://www.wizards.com/global/images/magic/general/serra_the_benevolent.jpg
Oracle:[+2]: Creatures you control with flying get +1/+1 until end of turn.\n[-3]: Create a 4/4 white Angel creature token with flying and vigilance.\n[-6]: You get an emblem with "If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead."

View File

@@ -0,0 +1,7 @@
Name:Stream of Thought
ManaCost:U
Types:Sorcery
A:SP$ Mill | Cost$ U | NumCards$ 4 | ValidTgts$ Player | TgtPrompt$ Choose a player | RememberMilled$ True | SubAbility$ DBChangeZone | SpellDescription$ Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | DefinedPlayer$ You | Hidden$ True | ChangeNum$ 4 | ChangeType$ Card.YouOwn | Shuffle$ True
K:Replicate:2 U U
Oracle:Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.\nReplicate {2}{U}{U} (When you cast this spell, copy it for each time you paid the replicate cost. You may choose new targets for the copies.)

View File

@@ -0,0 +1,9 @@
Name:Undead Augur
ManaCost:B B
Types:Creature Zombie Wizard
PT:2/2
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | TriggerController$ TriggeredCardController | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME or another Zombie you control dies, you draw a card and you lose 1 life.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Zombie.Other+YouCtrl | TriggerZones$ Battlefield | TriggerController$ TriggeredCardController | Secondary$ True | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME or another Zombie you control dies, you draw a card and you lose 1 life.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
Oracle:Whenever Undead Augur or another Zombie you control dies, you draw a card and you lose 1 life.

View File

@@ -0,0 +1,12 @@
Name:Vivien, Champion of the Wilds
ManaCost:2 G
Types:Legendary Planeswalker Vivien
Loyalty:4
S:Mode$ Continuous | Affected$ Creature | WithFlash$ You | AffectedZone$ Exile,Graveyard,Hand,Library,Command | Description$ You may cast creature spells as though they had flash.
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature | KW$ Vigilance & Reach | UntilYourNextTurn$ True | SpellDescription$ Until your next turn, up to one target creature gains vigilance and reach.
A:AB$ Dig | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | Defined$ You | DigNum$ 3 | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DBEffect | AILogic$ DigForCreature | SpellDescription$ Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card.
SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
SVar:STPlay1:Mode$ Continuous | MayLookAt$ You | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at the card and you may cast it if it's a creature.
SVar:STPlay2:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Creature.IsRemembered | AffectedZone$ Exile | Secondary$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:You may cast creature spells as though they had flash.\n[+1]: Until your next turn, up to one target creature gains vigilance and reach.\n[2]: Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card.

View File

@@ -0,0 +1,46 @@
[metadata]
Code=MH1
Date=2019-6-14
Name=Modern Horizons
Code2=MH1
MciCode=mh1
Type=Other
[cards]
1 M Morophon, the Boundless
14 C Impostor of the Sixth Pride
19 C Martyr's Soul
26 M Serra the Benevolent
36 C Wall of One Thousand Cuts
38 U Wing Shards
44 C Choking Tethers
50 U Fact or Fiction
52 R Force of Negation
64 C Prohibit
71 C Stream of Thought
80 R Cabal Therapist
95 C Headless Specter
114 C Venomous Changling
122 U Firebolt
129 U Goblin Matron
131 C Goblin War Party
134 C Lava Dart
145 M Seasoned Pyromancer
161 R Deep Forest Hermit
162 C Elvish Fury
164 R Force of Vigor
178 C Savage Swipe
203 R Ice-Fang Coatl
244 R Prismatic Vista
250 L Snow-Covered Plains
251 L Snow-Covered Island
252 L Snow-Covered Swamp
253 L Snow-Covered Mountain
254 L Snow-Covered Forest
255 R Flusterstorm
[tokens]
w_4_4_angel_flying_vigilance
g_1_1_squirrel
r_1_1_goblin

View File

@@ -316,8 +316,20 @@ public abstract class InputPayMana extends InputSyncronizedBase {
} else {
chosen = chosenAbility;
}
ColorSet colors = ColorSet.fromMask(0 == colorNeeded ? colorCanUse : colorNeeded);
chosen.getManaPartRecursive().setExpressChoice(colors);
// Filter the colors for the express choice so that only actually producible colors can be chosen
int producedColorMask = 0;
for (final byte color : ManaAtom.MANATYPES) {
if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color))
&& colors.hasAnyColor(color)) {
producedColorMask |= color;
}
}
ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask);
chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors);
// System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana");

View File

@@ -5,7 +5,7 @@
<artifactId>forge</artifactId>
<packaging>pom</packaging>
<name>Forge Parent</name>
<version>1.6.25-SNAPSHOT</version>
<version>1.6.26-SNAPSHOT</version>
<description>
Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules.