This commit is contained in:
Grimm
2021-11-18 02:27:16 +01:00
387 changed files with 3631 additions and 1106 deletions

View File

@@ -4,7 +4,7 @@
Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated) Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated)
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968) Discord channel [here](https://discord.gg/fWfNgCUNRq)
## Requirements / Tools ## Requirements / Tools

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.46-SNAPSHOT</version> <version>1.6.47-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -49,7 +49,7 @@
</goals> </goals>
<configuration> <configuration>
<headerType>gui</headerType> <headerType>gui</headerType>
<outfile>${project.build.directory}/forge-adventure.exe</outfile> <outfile>${project.build.directory}/forge-adventure-java8.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar> <jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar> <dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle> <errTitle>forge</errTitle>
@@ -66,6 +66,60 @@
<opt>-Dfile.encoding=UTF-8</opt> <opt>-Dfile.encoding=UTF-8</opt>
</opts> </opts>
</jre> </jre>
<versionInfo>
<fileVersion>
1.0.0.0
</fileVersion>
<txtFileVersion>
1.0.0.0
</txtFileVersion>
<fileDescription>Forge</fileDescription>
<copyright>Forge</copyright>
<productVersion>
1.0.0.0
</productVersion>
<txtProductVersion>
1.0.0.0
</txtProductVersion>
<productName>forge-adventure</productName>
<internalName>forge-adventure</internalName>
<originalFilename>forge-adventure-java8.exe</originalFilename>
</versionInfo>
</configuration>
</execution>
<!--extra-->
<execution>
<id>l4j-adv2</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
<configuration>
<headerType>gui</headerType>
<outfile>${project.build.directory}/forge-adventure.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle>
<downloadUrl>https://www.oracle.com/java/technologies/downloads/</downloadUrl>
<icon>src/main/config/forge-adventure.ico</icon>
<classPath>
<mainClass>forge.adventure.Main</mainClass>
<addDependencies>false</addDependencies>
<preCp>anything</preCp>
</classPath>
<jre>
<minVersion>11.0.1</minVersion>
<jdkPreference>jdkOnly</jdkPreference>
<maxHeapSize>4096</maxHeapSize>
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
<opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.text=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt>
</opts>
</jre>
<versionInfo> <versionInfo>
<fileVersion> <fileVersion>
1.0.0.0 1.0.0.0
@@ -87,6 +141,7 @@
</versionInfo> </versionInfo>
</configuration> </configuration>
</execution> </execution>
<!--extra-->
</executions> </executions>
</plugin> </plugin>
@@ -220,7 +275,7 @@
<dependency> <dependency>
<groupId>forge</groupId> <groupId>forge</groupId>
<artifactId>forge-gui-mobile</artifactId> <artifactId>forge-gui-mobile</artifactId>
<version>1.6.46-SNAPSHOT</version> <version>1.6.47-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

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

View File

@@ -29,6 +29,8 @@ import com.google.common.collect.Iterables;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
@@ -39,6 +41,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -334,6 +337,35 @@ public class AiBlockController {
}); });
} }
private Predicate<Card> changesPTWhenBlocked(final boolean onlyForDefVsTrample) {
return new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (final Trigger tr : card.getTriggers()) {
if (tr.getMode() == TriggerType.AttackerBlocked) {
SpellAbility ab = tr.getOverridingAbility();
if (ab != null) {
if (ab.getApi() == ApiType.Pump && "Self".equals(ab.getParam("Defined"))) {
String rawP = ab.getParam("NumAtt");
String rawT = ab.getParam("NumDef");
if ("+X".equals(rawP) && "+X".equals(rawT) && "TriggerCount$NumBlockers".equals(card.getSVar("X"))) {
return true;
}
// TODO: maybe also predict calculated bonus above certain threshold?
} else if (ab.getApi() == ApiType.PumpAll && ab.hasParam("ValidCards")
&& ab.getParam("ValidCards").startsWith("Creature.blockingSource")) {
int pBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumAtt"), ab);
int tBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumDef"), ab);
return (!onlyForDefVsTrample && pBonus < 0) || tBonus < 0;
}
}
}
}
return false;
}
};
}
// Good Gang Blocks means a good trade or no trade // Good Gang Blocks means a good trade or no trade
/** /**
* <p> * <p>
@@ -725,8 +757,9 @@ public class AiBlockController {
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE); List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat))); tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced the keyword: // TODO - Instead of filtering out rampage-like and similar triggers, make the AI properly count P/T and
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." // reinforce when actually possible without losing material.
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true)));
for (final Card attacker : tramplingAttackers) { for (final Card attacker : tramplingAttackers) {
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size() if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
@@ -755,8 +788,9 @@ public class AiBlockController {
List<Card> blockers; List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat))); List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced // TODO - Instead of filtering out rampage-like and similar triggers, make the AI properly count P/T and
// the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." // reinforce when actually possible without losing material.
targetAttackers = CardLists.filter(targetAttackers, Predicates.not(changesPTWhenBlocked(false)));
for (final Card attacker : targetAttackers) { for (final Card attacker : targetAttackers) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);

View File

@@ -1929,9 +1929,13 @@ public class AiController {
int cardsInPlay = player.getCardsIn(ZoneType.Battlefield).size(); int cardsInPlay = player.getCardsIn(ZoneType.Battlefield).size();
return Math.min(chosenMax, cardsInPlay); return Math.min(chosenMax, cardsInPlay);
} else if ("OptionalDraw".equals(logic)) { } else if ("OptionalDraw".equals(logic)) {
int cardsInLib = player.getCardsIn(ZoneType.Library).size();
if (cardsInLib >= max && player.isCardInPlay("Laboratory Maniac")) {
return max;
}
int cardsInHand = player.getCardsIn(ZoneType.Hand).size(); int cardsInHand = player.getCardsIn(ZoneType.Hand).size();
int maxDraw = Math.min(player.getMaxHandSize() + 2 - cardsInHand, max); int maxDraw = Math.min(player.getMaxHandSize() + 2 - cardsInHand, max);
int maxCheckLib = Math.min(maxDraw, player.getCardsIn(ZoneType.Library).size()); int maxCheckLib = Math.min(maxDraw, cardsInLib);
return Math.max(min, maxCheckLib); return Math.max(min, maxCheckLib);
} else if ("RepeatDraw".equals(logic)) { } else if ("RepeatDraw".equals(logic)) {
int remaining = player.getMaxHandSize() - player.getCardsIn(ZoneType.Hand).size() int remaining = player.getMaxHandSize() - player.getCardsIn(ZoneType.Hand).size()

View File

@@ -145,13 +145,20 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override @Override
public PaymentDecision visit(CostDraw cost) { public PaymentDecision visit(CostDraw cost) {
if (!cost.canPay(ability, player)) {
return null;
}
Integer c = cost.convertAmount(); Integer c = cost.convertAmount();
if (c == null) { if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
} }
return PaymentDecision.number(c); List<Player> res = cost.getPotentialPlayers(player, ability);
PaymentDecision decision = PaymentDecision.players(res);
decision.c = c;
return decision;
} }
@Override @Override

View File

@@ -1044,8 +1044,7 @@ public class ComputerUtil {
if (ai.getManaPool().willManaBeLostAtEndOfPhase()) { if (ai.getManaPool().willManaBeLostAtEndOfPhase()) {
boolean canUseToPayCost = false; boolean canUseToPayCost = false;
for (byte color : MagicColor.WUBRGC) { for (byte color : MagicColor.WUBRGC) {
if (ai.getManaPool().getAmountOfColor(color) > 0 if (ai.getManaPool().getAmountOfColor(color) > 0 && card.getManaCost().canBePaidWithAvailable(color)) {
&& ((card.getManaCost().getColorProfile() & color) == color)) {
canUseToPayCost = true; canUseToPayCost = true;
break; break;
} }

View File

@@ -1326,7 +1326,7 @@ public class ComputerUtilCard {
// will the creature attack (only relevant for sorcery speed)? // will the creature attack (only relevant for sorcery speed)?
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& phase.isPlayerTurn(ai) && phase.isPlayerTurn(ai)
&& SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred && (SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred)
&& power > 0 && power > 0
&& doesCreatureAttackAI(ai, c)) { && doesCreatureAttackAI(ai, c)) {
return true; return true;

View File

@@ -20,6 +20,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -590,7 +591,7 @@ public class ComputerUtilCost {
} }
// Ward - will be accounted for when rechecking a targeted ability // Ward - will be accounted for when rechecking a targeted ability
if (sa.usesTargeting()) { if (!(sa instanceof WrappedAbility) && sa.usesTargeting()) {
for (Card tgt : sa.getTargets().getTargetCards()) { for (Card tgt : sa.getTargets().getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
@@ -662,7 +663,7 @@ public class ComputerUtilCost {
} }
} else if ("RiskFactor".equals(aiLogic)) { } else if ("RiskFactor".equals(aiLogic)) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
if (!activator.canDraw() || activator.hasKeyword("You can't draw more than one card each turn.")) { if (!activator.canDraw()) {
return false; return false;
} }
} else if ("MorePowerful".equals(aiLogic)) { } else if ("MorePowerful".equals(aiLogic)) {

View File

@@ -545,6 +545,32 @@ public class SpecialCardAi {
} }
} }
// Fell the Mighty
public static class FellTheMighty {
public static boolean consider(final Player ai, final SpellAbility sa) {
CardCollection aiList = ai.getCreaturesInPlay();
if (aiList.isEmpty()) {
return false;
}
CardLists.sortByPowerAsc(aiList);
Card lowest = aiList.get(0);
if (!sa.canTarget(lowest)) {
return false;
}
CardCollection oppList = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents()));
oppList = CardLists.filterPower(oppList, lowest.getNetPower() + 1);
if (ComputerUtilCard.evaluateCreatureList(oppList) > 200) {
sa.resetTargets();
sa.getTargets().add(lowest);
return true;
}
return false;
}
}
// Force of Will // Force of Will
public static class ForceOfWill { public static class ForceOfWill {
public static boolean consider(final Player ai, final SpellAbility sa) { public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -1,32 +1,12 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.*;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.ai.*;
import forge.ai.AiAttackController; import forge.card.CardType;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi;
import forge.ai.SpecialAiLogic;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -35,12 +15,14 @@ import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostDiscard; import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPutCounter; import forge.game.cost.CostPutCounter;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -50,8 +32,12 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget; import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
public class ChangeZoneAi extends SpellAbilityAi { public class ChangeZoneAi extends SpellAbilityAi {
/* /*
@@ -1211,7 +1197,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (choice == null) { // Could not find a creature. if (choice == null) { // Could not find a creature.
if (ai.getLife() <= 5) { // Desperate? if (ai.getLife() <= 5) { // Desperate?
// Get something AI can cast soon. // Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardLists.sortByCmcDesc(nonLands); CardLists.sortByCmcDesc(nonLands);
for (Card potentialCard : nonLands) { for (Card potentialCard : nonLands) {
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) { if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) {
@@ -1221,7 +1206,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
} else { } else {
// Get the best card in there. // Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
choice = ComputerUtilCard.getBestAI(nonLands); choice = ComputerUtilCard.getBestAI(nonLands);
} }
} }
@@ -1487,7 +1471,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (choice == null) { // Could not find a creature. if (choice == null) { // Could not find a creature.
if (ai.getLife() <= 5) { // Desperate? if (ai.getLife() <= 5) { // Desperate?
// Get something AI can cast soon. // Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardLists.sortByCmcDesc(nonLands); CardLists.sortByCmcDesc(nonLands);
for (Card potentialCard : nonLands) { for (Card potentialCard : nonLands) {
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) { if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) {
@@ -1497,7 +1480,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
} else { } else {
// Get the best card in there. // Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
choice = ComputerUtilCard.getBestAI(nonLands); choice = ComputerUtilCard.getBestAI(nonLands);
} }
} }
@@ -1544,11 +1526,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
if ("DeathgorgeScavenger".equals(logic)) { if ("DeathgorgeScavenger".equals(logic)) {
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa); return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
} } else if ("ExtraplanarLens".equals(logic)) {
if ("ExtraplanarLens".equals(logic)) {
return SpecialCardAi.ExtraplanarLens.consider(ai, sa); return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
} } else if ("ExileCombatThreat".equals(logic)) {
if ("ExileCombatThreat".equals(logic)) {
return doExileCombatThreatLogic(ai, sa); return doExileCombatThreatLogic(ai, sa);
} }
@@ -1574,7 +1554,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return null; return null;
} }
if (sa.hasParam("AILogic")) { if (sa.hasParam("AILogic")) {
String logic = sa.getParam("AILogic"); String logic = sa.getParamOrDefault("AILogic", "");
if ("NeverBounceItself".equals(logic)) { if ("NeverBounceItself".equals(logic)) {
Card source = sa.getHostCard(); Card source = sa.getHostCard();
if (fetchList.contains(source) && (fetchList.size() > 1 || !sa.getRootAbility().isMandatory())) { if (fetchList.contains(source) && (fetchList.size() > 1 || !sa.getRootAbility().isMandatory())) {
@@ -1597,6 +1577,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
multipleCardsToChoose.remove(0); multipleCardsToChoose.remove(0);
return choice; return choice;
} }
} else if (logic.startsWith("ExilePreference")) {
return doExilePreferenceLogic(decider, sa, fetchList);
} }
} }
if (fetchList.isEmpty()) { if (fetchList.isEmpty()) {
@@ -1680,12 +1662,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
canCastSomething = canCastSomething || ComputerUtilMana.hasEnoughManaSourcesToCast(cardInHand.getFirstSpellAbility(), decider); canCastSomething = canCastSomething || ComputerUtilMana.hasEnoughManaSourcesToCast(cardInHand.getFirstSpellAbility(), decider);
} }
if (!canCastSomething) { if (!canCastSomething) {
System.out.println("Pulling a land as there are none in hand, less than 4 on the board, and nothing in hand is castable.");
c = basicManaFixing(decider, fetchList); c = basicManaFixing(decider, fetchList);
} }
} }
if (c == null) { if (c == null) {
System.out.println("Don't need a land or none available; trying for a creature.");
fetchList = CardLists.getNotType(fetchList, "Land"); fetchList = CardLists.getNotType(fetchList, "Land");
// Prefer to pull a creature, generally more useful for AI. // Prefer to pull a creature, generally more useful for AI.
c = chooseCreature(decider, CardLists.filter(fetchList, CardPredicates.Presets.CREATURES)); c = chooseCreature(decider, CardLists.filter(fetchList, CardPredicates.Presets.CREATURES));
@@ -1693,7 +1673,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (c == null) { // Could not find a creature. if (c == null) { // Could not find a creature.
if (decider.getLife() <= 5) { // Desperate? if (decider.getLife() <= 5) { // Desperate?
// Get something AI can cast soon. // Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardLists.sortByCmcDesc(fetchList); CardLists.sortByCmcDesc(fetchList);
for (Card potentialCard : fetchList) { for (Card potentialCard : fetchList) {
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), decider)) { if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), decider)) {
@@ -1703,7 +1682,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
} else { } else {
// Get the best card in there. // Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
c = ComputerUtilCard.getBestAI(fetchList); c = ComputerUtilCard.getBestAI(fetchList);
} }
} }
@@ -1786,7 +1764,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override @Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
// Called when attaching Aura to player or adding creature to combat // Called when attaching Aura to player or adding creature to combat
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options); return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
@@ -1794,7 +1772,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override @Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
// should not be reached // should not be reached
@@ -2037,6 +2015,143 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false; return false;
} }
public static Card doExilePreferenceLogic(final Player aiPlayer, final SpellAbility sa, CardCollection fetchList) {
// Filter by preference. If nothing is preferred, choose the best/worst/random target for the opponent
// or for the AI depending on the settings. This logic must choose at least something if at all possible,
// since it's called from chooseSingleCard.
if (fetchList.isEmpty()) {
return null; // there was nothing to choose at all
}
final Card host = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
final String valid = logic.split(":")[1];
final boolean isCurse = logic.contains("Curse");
final boolean isOwnOnly = logic.contains("OwnOnly");
final boolean isWorstChoice = logic.contains("Worst");
final boolean isRandomChoice = logic.contains("Random");
if (logic.endsWith("HighestCMC")) {
return ComputerUtilCard.getMostExpensivePermanentAI(fetchList);
} else if (logic.contains("MostProminent")) {
CardCollection scanList = new CardCollection();
if (logic.endsWith("OwnType")) {
scanList.addAll(aiPlayer.getCardsIn(ZoneType.Library));
scanList.addAll(aiPlayer.getCardsIn(ZoneType.Hand));
} else if (logic.endsWith("OppType")) {
// this assumes that the deck list is known to the AI before the match starts,
// so it's possible to figure out what remains in library/hand if you know what's
// in graveyard, exile, etc.
scanList.addAll(aiPlayer.getOpponents().getCardsIn(ZoneType.Library));
scanList.addAll(aiPlayer.getOpponents().getCardsIn(ZoneType.Hand));
}
if (logic.contains("NonLand")) {
scanList = CardLists.filter(scanList, Predicates.not(Presets.LANDS));
}
if (logic.contains("NonExiled")) {
CardCollection exiledBy = new CardCollection();
for (Card exiled : aiPlayer.getGame().getCardsIn(ZoneType.Exile)) {
if (exiled.getExiledWith() != null && exiled.getExiledWith().getName().equals(host.getName())) {
exiledBy.add(exiled);
}
}
scanList = CardLists.filter(scanList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (exiledBy.isEmpty()) {
return true;
}
for (Card c : exiledBy) {
return !c.getType().sharesCardTypeWith(card.getType());
}
return true;
}
});
}
CardCollectionView graveyardList = aiPlayer.getGame().getCardsIn(ZoneType.Graveyard);
Set<CardType.CoreType> presentTypes = new HashSet<>();
for (Card inGrave : graveyardList) {
for(CardType.CoreType type : inGrave.getType().getCoreTypes()) {
presentTypes.add(type);
}
}
final Map<CardType.CoreType, Integer> typesInDeck = Maps.newHashMap();
for (final Card c : scanList) {
for (CardType.CoreType ct : c.getType().getCoreTypes()) {
if (presentTypes.contains(ct)) {
Integer count = typesInDeck.get(ct);
if (count == null) {
count = 0;
}
typesInDeck.put(ct, count + 1);
}
}
}
int max = 0;
CardType.CoreType maxType = CardType.CoreType.Land;
for (final Map.Entry<CardType.CoreType, Integer> entry : typesInDeck.entrySet()) {
final CardType.CoreType type = entry.getKey();
if (max < entry.getValue()) {
max = entry.getValue();
maxType = type;
}
}
final CardType.CoreType determinedMaxType = maxType;
CardCollection preferredList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.getType().hasType(determinedMaxType);
}
});
CardCollection preferredOppList = CardLists.filter(preferredList, CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()));
if (!preferredOppList.isEmpty()) {
return Aggregates.random(preferredOppList);
} else if (!preferredList.isEmpty()) {
return Aggregates.random(preferredList);
}
return Aggregates.random(fetchList);
}
CardCollection preferredList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean playerPref = true;
if (isCurse) {
playerPref = card.getController().isOpponentOf(aiPlayer);
} else if (isOwnOnly) {
playerPref = card.getController().equals(aiPlayer) || !card.getController().isOpponentOf(aiPlayer);
}
if (!playerPref) {
return false;
}
return card.isValid(valid, aiPlayer, host, sa); // for things like ExilePreference:Land.Basic
}
});
if (!preferredList.isEmpty()) {
if (isRandomChoice) {
return Aggregates.random(preferredList);
}
return isWorstChoice ? ComputerUtilCard.getWorstAI(preferredList) : ComputerUtilCard.getBestAI(preferredList);
} else {
if (isRandomChoice) {
return Aggregates.random(preferredList);
}
return isWorstChoice ? ComputerUtilCard.getWorstAI(fetchList) : ComputerUtilCard.getBestAI(fetchList);
}
}
private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable<Card> potentialTgts) { private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable<Card> potentialTgts) {
// Determines if the controller of each potential target can negate the ChangeZone effect // Determines if the controller of each potential target can negate the ChangeZone effect
// by paying the Unless cost. Returns the list of targets that can be saved that way. // by paying the Unless cost. Returns the list of targets that can be saved that way.

View File

@@ -247,7 +247,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay(); final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
@@ -257,7 +257,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
@Override @Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
// should not be reached // should not be reached

View File

@@ -488,6 +488,9 @@ public class CountersPutAi extends CountersAi {
} }
}); });
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
if (abCost.hasSpecificCostType(CostSacrifice.class)) { if (abCost.hasSpecificCostType(CostSacrifice.class)) {
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list); Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
// this card is planned to be sacrificed during cost payment, so don't target it // this card is planned to be sacrificed during cost payment, so don't target it
@@ -805,6 +808,9 @@ public class CountersPutAi extends CountersAi {
} }
list = CardLists.getTargetableCards(list, sa); list = CardLists.getTargetableCards(list, sa);
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
int totalTargets = list.size(); int totalTargets = list.size();
sa.resetTargets(); sa.resetTargets();

View File

@@ -2,13 +2,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.*;
import forge.ai.AiBlockController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -66,7 +60,13 @@ public class DestroyAllAi extends SpellAbilityAi {
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; return false;
} }
final String aiLogic = sa.getParamOrDefault("AILogic", "");
if ("FellTheMighty".equals(aiLogic)) {
return SpecialCardAi.FellTheMighty.consider(ai, sa);
}
return doMassRemovalLogic(ai, sa); return doMassRemovalLogic(ai, sa);
} }

View File

@@ -121,6 +121,12 @@ public class DigAi extends SpellAbilityAi {
return !ComputerUtil.preventRunAwayActivations(sa); return !ComputerUtil.preventRunAwayActivations(sa);
} }
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// TODO: improve this check in ways that may be specific to a subability
return canPlayAI(aiPlayer, sa);
}
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
@@ -183,7 +189,7 @@ public class DigAi extends SpellAbilityAi {
*/ */
@Override @Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
// an opponent choose a card from // an opponent choose a card from
@@ -192,7 +198,7 @@ public class DigAi extends SpellAbilityAi {
@Override @Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
// should not be reached // should not be reached

View File

@@ -224,7 +224,7 @@ public class DrawAi extends SpellAbilityAi {
final Game game = ai.getGame(); final Game game = ai.getGame();
final String logic = sa.getParamOrDefault("AILogic", ""); final String logic = sa.getParamOrDefault("AILogic", "");
final boolean considerPrimary = logic.equals("ConsiderPrimary"); final boolean considerPrimary = logic.equals("ConsiderPrimary");
final boolean drawback = (sa.getParent() != null) && !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 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(); int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
@@ -266,7 +266,7 @@ public class DrawAi extends SpellAbilityAi {
// [Necrologia, Pay X Life : Draw X Cards] // [Necrologia, Pay X Life : Draw X Cards]
// Don't draw more than what's "safe" and don't risk a near death experience // Don't draw more than what's "safe" and don't risk a near death experience
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO); boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
while ((ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && (numCards > 0))) { while (ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && numCards > 0) {
numCards--; numCards--;
} }
} }
@@ -310,8 +310,20 @@ public class DrawAi extends SpellAbilityAi {
PlayerCollection opps = players.filter(PlayerPredicates.isOpponentOf(ai)); PlayerCollection opps = players.filter(PlayerPredicates.isOpponentOf(ai));
for (Player oppA : opps) { for (Player oppA : opps) {
if (sa.isCurse() && ai.canDraw() && oppA.canLoseLife()) { // Risk Factor
if (numCards >= computerLibrarySize - 3) {
if (ai.isCardInPlay("Laboratory Maniac")) {
sa.getTargets().add(oppA);
return true;
}
} else if (computerHandSize + numCards <= computerMaxHandSize) {
sa.getTargets().add(oppA);
return true;
}
}
// try to kill opponent // try to kill opponent
if (oppA.cantLose()) { if (oppA.cantLose() || !oppA.canDraw()) {
continue; continue;
} }
@@ -376,7 +388,7 @@ public class DrawAi extends SpellAbilityAi {
boolean aiTarget = sa.canTarget(ai); boolean aiTarget = sa.canTarget(ai);
// checks what the ai prevent from casting it on itself // checks what the ai prevent from casting it on itself
// if spell is not mandatory // if spell is not mandatory
if (aiTarget && !ai.cantLose()) { if (aiTarget && !ai.cantLose() && ai.canDraw()) {
if (numCards >= computerLibrarySize - 3) { if (numCards >= computerLibrarySize - 3) {
if (xPaid) { if (xPaid) {
numCards = computerLibrarySize - 1; numCards = computerLibrarySize - 1;
@@ -436,7 +448,7 @@ public class DrawAi extends SpellAbilityAi {
// try to benefit ally // try to benefit ally
for (Player ally : ai.getAllies()) { for (Player ally : ai.getAllies()) {
// try to select ally to help // try to select ally to help
if (!sa.canTarget(ally)) { if (!sa.canTarget(ally) || !ally.canDraw()) {
continue; continue;
} }
@@ -489,9 +501,14 @@ public class DrawAi extends SpellAbilityAi {
return true; return true;
} }
} else if (!mandatory) { } else if (!mandatory) {
// ability is not targeted
// TODO: consider if human is the defined player // TODO: consider if human is the defined player
// ability is not targeted if ((numCards == 0 || !ai.canDraw()) && !drawback) {
return false;
}
if (numCards >= computerLibrarySize - 3) { if (numCards >= computerLibrarySize - 3) {
if (ai.isCardInPlay("Laboratory Maniac")) { if (ai.isCardInPlay("Laboratory Maniac")) {
return true; return true;
@@ -500,10 +517,6 @@ public class DrawAi extends SpellAbilityAi {
return false; return false;
} }
if (numCards == 0 && !drawback) {
return false;
}
if ((computerHandSize + numCards > computerMaxHandSize) if ((computerHandSize + numCards > computerMaxHandSize)
&& game.getPhaseHandler().isPlayerTurn(ai) && game.getPhaseHandler().isPlayerTurn(ai)
&& !sa.isTrigger() && !sa.isTrigger()
@@ -526,7 +539,6 @@ public class DrawAi extends SpellAbilityAi {
return targetAI(ai, sa, mandatory); return targetAI(ai, sa, mandatory);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */

View File

@@ -36,16 +36,7 @@ public class PumpAi extends PumpAiBase {
@Override @Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("FellTheMighty".equals(aiLogic)) { if ("MoveCounter".equals(aiLogic)) {
CardCollection aiList = ai.getCreaturesInPlay();
if (aiList.isEmpty()) {
return false;
}
CardLists.sortByPowerAsc(aiList);
if (!sa.canTarget(aiList.get(0))) {
return false;
}
} else if ("MoveCounter".equals(aiLogic)) {
final Game game = ai.getGame(); final Game game = ai.getGame();
List<Card> tgtCards = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), List<Card> tgtCards = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.isTargetableBy(sa)); CardPredicates.isTargetableBy(sa));
@@ -70,10 +61,6 @@ public class PumpAi extends PumpAiBase {
return SpecialAiLogic.doAristocratLogic(ai, sa); return SpecialAiLogic.doAristocratLogic(ai, sa);
} else if (aiLogic.startsWith("AristocratCounters")) { } else if (aiLogic.startsWith("AristocratCounters")) {
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa); return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
} else if ("RiskFactor".equals(aiLogic)) {
if (ai.getCardsIn(ZoneType.Hand).size() + 3 >= ai.getMaxHandSize()) {
return false;
}
} else if (aiLogic.equals("SwitchPT")) { } else if (aiLogic.equals("SwitchPT")) {
// Some more AI would be even better, but this is a good start to prevent spamming // Some more AI would be even better, but this is a good start to prevent spamming
if (sa.isAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) { if (sa.isAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
@@ -262,20 +249,6 @@ public class PumpAi extends PumpAiBase {
} }
} }
} else if ("FellTheMighty".equals(aiLogic)) {
CardCollection aiList = ai.getCreaturesInPlay();
CardLists.sortByPowerAsc(aiList);
Card lowest = aiList.get(0);
CardCollection oppList = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents()));
oppList = CardLists.filterPower(oppList, lowest.getNetPower() + 1);
if (ComputerUtilCard.evaluateCreatureList(oppList) > 200) {
sa.resetTargets();
sa.getTargets().add(lowest);
return true;
}
} else if (aiLogic.startsWith("Donate")) { } else if (aiLogic.startsWith("Donate")) {
// Donate step 1 - try to target an opponent, preferably one who does not have a donate target yet // Donate step 1 - try to target an opponent, preferably one who does not have a donate target yet
return SpecialCardAi.Donate.considerTargetingOpponent(ai, sa); return SpecialCardAi.Donate.considerTargetingOpponent(ai, sa);
@@ -391,9 +364,10 @@ public class PumpAi extends PumpAiBase {
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) { if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
return true; return true;
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) { } else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords); if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa) ||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) { game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped); return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped);
} }

View File

@@ -1,16 +1,13 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class RestartGameAi extends SpellAbilityAi { public class RestartGameAi extends SpellAbilityAi {
@@ -29,8 +26,7 @@ public class RestartGameAi extends SpellAbilityAi {
} }
// check if enough good permanents will be available to be returned, so AI can "autowin" // check if enough good permanents will be available to be returned, so AI can "autowin"
CardCollection exiled = new CardCollection(Iterables.filter(sa.getHostCard().getRemembered(), Card.class)); CardCollection exiled = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Exile), "Permanent.nonAura+IsRemembered", ai, sa.getHostCard(), sa);
exiled = CardLists.filter(exiled, Presets.PERMANENTS);
if (ComputerUtilCard.evaluatePermanentList(exiled) > 20) { if (ComputerUtilCard.evaluatePermanentList(exiled) > 20) {
return true; return true;
} }

View File

@@ -314,7 +314,7 @@ public class TokenAi extends SpellAbilityAi {
*/ */
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
@@ -325,7 +325,7 @@ public class TokenAi extends SpellAbilityAi {
*/ */
@Override @Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) { if (params != null && params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options)); return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
} }
// should not be reached // should not be reached

View File

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

View File

@@ -332,6 +332,10 @@ public class CardPool extends ItemPool<PaperCard> {
sumWeights += editionsCount; sumWeights += editionsCount;
weightedMean += weightedFrequency; weightedMean += weightedFrequency;
} }
if (frequencyValues.isEmpty())
return null;
int totalNoCards = (int)weightedMean; int totalNoCards = (int)weightedMean;
weightedMean /= sumWeights; weightedMean /= sumWeights;

View File

@@ -388,6 +388,8 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
// == Get the most representative (Pivot) Edition in the Pool // == Get the most representative (Pivot) Edition in the Pool
// Note: Card Art Updates (if any) will be determined based on the Pivot Edition. // Note: Card Art Updates (if any) will be determined based on the Pivot Edition.
CardEdition pivotEdition = pool.getPivotCardEdition(isCardArtPreferenceLatestArt); CardEdition pivotEdition = pool.getPivotCardEdition(isCardArtPreferenceLatestArt);
if (pivotEdition == null)
continue;
// == Inspect and Update the Pool // == Inspect and Update the Pool
Date releaseDatePivotEdition = pivotEdition.getDate(); Date releaseDatePivotEdition = pivotEdition.getDate();

View File

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

View File

@@ -175,6 +175,10 @@ public final class GameActionUtil {
final String keyword = inst.getOriginal(); final String keyword = inst.getOriginal();
if (keyword.startsWith("Disturb")) { if (keyword.startsWith("Disturb")) {
if (!source.isInZone(ZoneType.Graveyard)) {
continue;
}
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final Cost disturbCost = new Cost(k[1], true); final Cost disturbCost = new Cost(k[1], true);
@@ -203,6 +207,10 @@ public final class GameActionUtil {
alternatives.add(newSA); alternatives.add(newSA);
} else if (keyword.startsWith("Escape")) { } else if (keyword.startsWith("Escape")) {
if (!source.isInZone(ZoneType.Graveyard)) {
continue;
}
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final Cost escapeCost = new Cost(k[1], true); final Cost escapeCost = new Cost(k[1], true);
@@ -224,6 +232,10 @@ public final class GameActionUtil {
alternatives.add(newSA); alternatives.add(newSA);
} else if (keyword.startsWith("Flashback")) { } else if (keyword.startsWith("Flashback")) {
if (!source.isInZone(ZoneType.Graveyard)) {
continue;
}
// if source has No Mana cost, and flashback doesn't have own one, // if source has No Mana cost, and flashback doesn't have own one,
// flashback can't work // flashback can't work
if (keyword.equals("Flashback") && source.getManaCost().isNoCost()) { if (keyword.equals("Flashback") && source.getManaCost().isNoCost()) {

View File

@@ -2365,6 +2365,20 @@ public class AbilityUtils {
return doXMath(getCardTypesFromList(oppCards), expr, c, ctb); return doXMath(getCardTypesFromList(oppCards), expr, c, ctb);
} }
//Count$TypesSharedWith [defined]
if (sq[0].startsWith("TypesSharedWith")) {
Set<CardType.CoreType> thisTypes = Sets.newHashSet(c.getType().getCoreTypes());
Set<CardType.CoreType> matches = new HashSet<>();
for (Card c1 : AbilityUtils.getDefinedCards(ctb.getHostCard(), l[0].split(" ")[1], ctb)) {
for (CardType.CoreType type : Sets.newHashSet(c1.getType().getCoreTypes())) {
if (thisTypes.contains(type)) {
matches.add(type);
}
}
}
return matches.size();
}
// Count$TopOfLibraryCMC // Count$TopOfLibraryCMC
if (sq[0].equals("TopOfLibraryCMC")) { if (sq[0].equals("TopOfLibraryCMC")) {
int cmc = player.getCardsIn(ZoneType.Library).isEmpty() ? 0 : int cmc = player.getCardsIn(ZoneType.Library).isEmpty() ? 0 :
@@ -2431,6 +2445,19 @@ public class AbilityUtils {
return doXMath(list.size(), expr, c, ctb); return doXMath(list.size(), expr, c, ctb);
} }
if (sq[0].contains("AbilityYouCtrl")) {
CardCollection all = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), "Creature", player,
c, ctb);
int count = 0;
for (String ab : sq[0].substring(15).split(",")) {
CardCollection found = CardLists.getValidCards(all, "Creature.with" + ab, player, c, ctb);
if (!found.isEmpty()) {
count++;
}
}
return doXMath(count, expr, c, ctb);
}
if (sq[0].contains("Party")) { if (sq[0].contains("Party")) {
CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield),
"Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb); "Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb);
@@ -2600,7 +2627,7 @@ public class AbilityUtils {
} }
if (sq[0].contains("CardTypes")) { if (sq[0].contains("CardTypes")) {
return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), expr, c, ctb); return doXMath(getCardTypesFromList(getDefinedCards(c, sq[1], ctb)), expr, c, ctb);
} }
if (sq[0].equals("TotalTurns")) { if (sq[0].equals("TotalTurns")) {
@@ -3563,6 +3590,16 @@ public class AbilityUtils {
} }
} }
if (string.startsWith("GreatestPower")) {
int highest = 0;
for (final Card crd : paidList) {
if (crd.getNetPower() > highest) {
highest = crd.getNetPower();
}
}
return highest;
}
if (string.startsWith("DifferentCMC")) { if (string.startsWith("DifferentCMC")) {
final Set<Integer> diffCMC = new HashSet<>(); final Set<Integer> diffCMC = new HashSet<>();
for (final Card card : paidList) { for (final Card card : paidList) {

View File

@@ -521,7 +521,7 @@ public abstract class SpellAbilityEffect {
String repeffstr = "Event$ Moved | ValidCard$ " + valid + String repeffstr = "Event$ Moved | ValidCard$ " + valid +
"| Origin$ Battlefield | Destination$ Graveyard " + "| Origin$ Battlefield | Destination$ Graveyard " +
"| Description$ If the creature would die this turn, exile it instead."; "| Description$ If that permanent would die this turn, exile it instead.";
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone; String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);

View File

@@ -37,7 +37,7 @@ public class AmassEffect extends TokenEffectBase {
sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter")); sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter"));
sb.append("on an Army you control. If you dont control one, create a 0/0 black Zombie Army creature token first.)"); sb.append("on an Army you control. If you don't control one, create a 0/0 black Zombie Army creature token first.)");
return sb.toString(); return sb.toString();
} }

View File

@@ -31,7 +31,7 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if ((tgt == null) || p.canBeTargetedBy(sa)) { if ((tgt == null) || p.canBeTargetedBy(sa)) {
if (!p.hasKeyword("You cant become the monarch this turn.")) { if (!p.hasKeyword("You can't become the monarch this turn.")) {
p.getGame().getAction().becomeMonarch(p, set); p.getGame().getAction().becomeMonarch(p, set);
} }
} }

View File

@@ -137,9 +137,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0; final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0;
if (!random) { if (!random && !((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) && sa.hasParam("Shuffle"))) {
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) && cards.size() >= 2) {
&& !sa.hasParam("Shuffle") && cards.size() >= 2) {
Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0); Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0);
cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa); cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa);
//the last card in this list will be the closest to the top, but we want the first card to be closest. //the last card in this list will be the closest to the top, but we want the first card to be closest.

View File

@@ -60,11 +60,10 @@ public class CopyPermanentEffect extends TokenEffectBase {
final Game game = host.getGame(); final Game game = host.getGame();
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) { if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
return; return;
} }
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, sa.getParam("NumCopies"), sa) : 1;
sa.getParam("NumCopies"), sa) : 1;
Player controller = null; Player controller = null;
if (sa.hasParam("Controller")) { if (sa.hasParam("Controller")) {

View File

@@ -121,6 +121,17 @@ public class DamageDealEffect extends DamageBaseEffect {
} }
stringBuilder.append("."); stringBuilder.append(".");
if (spellAbility.hasParam("ReplaceDyingDefined")) {
String statement = "If that creature would die this turn, exile it instead.";
String[] sentences = spellAbility.getParamOrDefault("SpellDescription", "").split("\\.");
for (String s : sentences) {
if (s.contains("would die")) {
statement = s;
break;
}
}
stringBuilder.append(statement);
}
return stringBuilder.toString(); return stringBuilder.toString();
} }
@@ -255,6 +266,16 @@ public class DamageDealEffect extends DamageBaseEffect {
protected void internalDamageDeal(SpellAbility sa, Card sourceLKI, Card c, int dmg, CardDamageMap damageMap) { protected void internalDamageDeal(SpellAbility sa, Card sourceLKI, Card c, int dmg, CardDamageMap damageMap) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Player activationPlayer = sa.getActivatingPlayer(); final Player activationPlayer = sa.getActivatingPlayer();
int excess = 0;
int dmgToTarget = 0;
if (sa.hasParam("ExcessDamage") || (sa.hasParam("ExcessSVar"))) {
int lethal = c.getLethalDamage();
if (sourceLKI.hasKeyword(Keyword.DEATHTOUCH)) {
lethal = Math.min(lethal, 1);
}
dmgToTarget = Math.min(lethal, dmg);
excess = dmg - dmgToTarget;
}
if (sa.hasParam("Remove")) { if (sa.hasParam("Remove")) {
c.setDamage(0); c.setDamage(0);
@@ -263,11 +284,6 @@ public class DamageDealEffect extends DamageBaseEffect {
} else { } else {
if (sa.hasParam("ExcessDamage") && (!sa.hasParam("ExcessDamageCondition") || if (sa.hasParam("ExcessDamage") && (!sa.hasParam("ExcessDamageCondition") ||
sourceLKI.isValid(sa.getParam("ExcessDamageCondition").split(","), activationPlayer, hostCard, sa))) { sourceLKI.isValid(sa.getParam("ExcessDamageCondition").split(","), activationPlayer, hostCard, sa))) {
int lethal = c.getLethalDamage();
if (sourceLKI.hasKeyword(Keyword.DEATHTOUCH)) {
lethal = Math.min(lethal, 1);
}
int dmgToTarget = Math.min(lethal, dmg);
damageMap.put(sourceLKI, c, dmgToTarget); damageMap.put(sourceLKI, c, dmgToTarget);
@@ -276,10 +292,13 @@ public class DamageDealEffect extends DamageBaseEffect {
list.addAll(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("ExcessDamage"), sa)); list.addAll(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("ExcessDamage"), sa));
if (!list.isEmpty()) { if (!list.isEmpty()) {
damageMap.put(sourceLKI, list.get(0), dmg - dmgToTarget); damageMap.put(sourceLKI, list.get(0), excess);
} }
} else { } else {
damageMap.put(sourceLKI, c, dmg); damageMap.put(sourceLKI, c, dmg);
if (sa.hasParam("ExcessSVar")) {
hostCard.setSVar(sa.getParam("ExcessSVar"), Integer.toString(excess));
}
} }
} }
} }

View File

@@ -8,7 +8,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbilityCantDraw;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
@@ -21,7 +21,7 @@ public class DrawEffect extends SpellAbilityEffect {
if (!tgtPlayers.isEmpty()) { if (!tgtPlayers.isEmpty()) {
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1; int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
sb.append(Lang.joinHomogenous(tgtPlayers)); sb.append(Lang.joinHomogenous(tgtPlayers));
if (tgtPlayers.size() > 1) { if (tgtPlayers.size() > 1) {
@@ -40,19 +40,34 @@ public class DrawEffect extends SpellAbilityEffect {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1; final int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean optional = sa.hasParam("OptionalDecider");
final boolean upto = sa.hasParam("Upto"); final boolean upto = sa.hasParam("Upto");
final boolean optional = sa.hasParam("OptionalDecider") || upto;
for (final Player p : getDefinedPlayersOrTargeted(sa)) { for (final Player p : getDefinedPlayersOrTargeted(sa)) {
if ((tgt == null) || p.canBeTargetedBy(sa)) // TODO can this be removed?
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")))) if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
continue; continue;
}
int actualNum = numCards; // it is optional, not upto and player can't choose to draw that many cards
if (optional && !upto && !p.canDrawAmount(numCards)) {
continue;
}
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")))) {
continue;
}
int actualNum = numCards;
if (upto) { // if it is upto, player can only choose how many cards they can draw
actualNum = StaticAbilityCantDraw.canDrawAmount(p, actualNum);
}
if (actualNum <= 0) {
continue;
}
if (upto) { if (upto) {
actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, numCards); actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, actualNum);
} }
final CardCollectionView drawn = p.drawCards(actualNum, sa); final CardCollectionView drawn = p.drawCards(actualNum, sa);
@@ -68,6 +83,7 @@ public class DrawEffect extends SpellAbilityEffect {
source.addRemembered(c); source.addRemembered(c);
} }
} }
sa.setSVar("AFNotDrawnNum_" + p.getId(), "Number$" + drawn.size());
} }
} }
} }

View File

@@ -36,15 +36,25 @@ public class LifeGainEffect extends SpellAbilityEffect {
*/ */
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final int lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);
List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa); List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
if (tgtPlayers.isEmpty()) { if (tgtPlayers.isEmpty()) {
tgtPlayers.add(sa.getActivatingPlayer()); tgtPlayers.add(sa.getActivatingPlayer());
} }
String amount = sa.getParam("LifeAmount");
boolean variableAmount = amount.equals("AFNotDrawnNum");
int lifeAmount = 0;
if (variableAmount) {
amount = "X";
} else {
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amount, sa);
}
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (variableAmount) {
sa.setSVar("AFNotDrawnNum", sa.getSVar("AFNotDrawnNum_" + p.getId()));
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amount, sa);
}
p.gainLife(lifeAmount, sa.getHostCard(), sa); p.gainLife(lifeAmount, sa.getHostCard(), sa);
} }
} }

View File

@@ -100,15 +100,19 @@ public class SacrificeEffect extends SpellAbilityEffect {
final boolean destroy = sa.hasParam("Destroy"); final boolean destroy = sa.hasParam("Destroy");
final boolean remSacrificed = sa.hasParam("RememberSacrificed"); final boolean remSacrificed = sa.hasParam("RememberSacrificed");
final boolean optional = sa.hasParam("Optional");
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield()); params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
if (valid.equals("Self") && game.getZoneOf(card) != null) { if (valid.equals("Self") && game.getZoneOf(card) != null) {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) { if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
if (game.getAction().sacrifice(card, sa, table, params) != null) { if (!optional || activator.getController().confirmAction(sa, null,
if (remSacrificed) { Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()))) {
card.addRemembered(card); if (game.getAction().sacrifice(card, sa, table, params) != null) {
if (remSacrificed) {
card.addRemembered(card);
}
} }
} }
} }
@@ -147,12 +151,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
if (sa.hasParam("Random")) { if (sa.hasParam("Random")) {
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection()); choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection());
} else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) { } else if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) {
choosenToSacrifice = CardCollection.EMPTY; choosenToSacrifice = CardCollection.EMPTY;
} else { } else {
boolean isOptional = sa.hasParam("Optional");
boolean isStrict = sa.hasParam("StrictAmount"); boolean isStrict = sa.hasParam("StrictAmount");
int minTargets = isOptional ? 0 : amount; int minTargets = optional ? 0 : amount;
boolean notEnoughTargets = isStrict && validTargets.size() < minTargets; boolean notEnoughTargets = isStrict && validTargets.size() < minTargets;
if (!notEnoughTargets) { if (!notEnoughTargets) {

View File

@@ -39,7 +39,6 @@ import forge.game.zone.ZoneType;
public abstract class TokenEffectBase extends SpellAbilityEffect { public abstract class TokenEffectBase extends SpellAbilityEffect {
protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) { protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
TokenCreateTable tokenTable = new TokenCreateTable(); TokenCreateTable tokenTable = new TokenCreateTable();
for (final Player owner : players) { for (final Player owner : players) {
for (String script : tokenScripts) { for (String script : tokenScripts) {
@@ -83,7 +82,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
// support PlayerCollection for affected // support PlayerCollection for affected
Set<Player> toRemove = Sets.newHashSet(); Set<Player> toRemove = Sets.newHashSet();
for (Player p : tokenTable.rowKeySet()) { for (Player p : tokenTable.rowKeySet()) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
repParams.put(AbilityKey.Token, tokenTable); repParams.put(AbilityKey.Token, tokenTable);
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens? repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?

View File

@@ -56,6 +56,7 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.*; import forge.game.spellability.*;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.staticability.StaticAbilityCantPutCounter;
import forge.game.staticability.StaticAbilityCantTransform; import forge.game.staticability.StaticAbilityCantTransform;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -1364,13 +1365,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
@Override @Override
public final boolean canReceiveCounters(final CounterType type) { public final boolean canReceiveCounters(final CounterType type) {
// CantPutCounter static abilities if (StaticAbilityCantPutCounter.anyCantPutCounter(this, type)) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { return false;
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPutCounter", this, type)) {
return false;
}
}
} }
return true; return true;
} }
@@ -1915,7 +1911,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbLong.append(p[2]).append("\r\n"); sbLong.append(p[2]).append("\r\n");
} else if (keyword.equals("Unblockable")) { } else if (keyword.equals("Unblockable")) {
sbLong.append(getName()).append(" can't be blocked.\r\n"); sbLong.append(getName()).append(" can't be blocked.\r\n");
sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n");
} else if (keyword.startsWith("IfReach")) { } else if (keyword.startsWith("IfReach")) {
String[] k = keyword.split(":"); String[] k = keyword.split(":");
sbLong.append(getName()).append(" can block ") sbLong.append(getName()).append(" can block ")
@@ -1986,17 +1981,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbLong.append(k).append("\r\n"); sbLong.append(k).append("\r\n");
} else if (keyword.startsWith("Ripple")) { } else if (keyword.startsWith("Ripple")) {
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n"); sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
} else if (keyword.startsWith("Madness")) {
String[] parts = keyword.split(":");
// If no colon exists in Madness keyword, it must have been granted and assumed the cost from host
if (parts.length < 2) {
sbLong.append(parts[0]).append(" ").append(this.getManaCost()).append("\r\n");
} else {
sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n");
}
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") } else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:") || keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|| keyword.startsWith("Disturb")) { || keyword.startsWith("Disturb") || keyword.startsWith("Madness:")){
String[] k = keyword.split(":"); String[] k = keyword.split(":");
sbLong.append(k[0]); sbLong.append(k[0]);
if (k.length > 1) { if (k.length > 1) {
@@ -2014,6 +2001,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbLong.append(" (").append(inst.getReminderText()).append(")"); sbLong.append(" (").append(inst.getReminderText()).append(")");
sbLong.append("\r\n"); sbLong.append("\r\n");
} }
} else if (keyword.startsWith("Madness")) {
// If no colon exists in Madness keyword, it must have been granted and assumed the cost from host
sbLong.append("Madness ").append(this.getManaCost()).append(" (").append(inst.getReminderText());
sbLong.append(")").append("\r\n");
} else if (keyword.startsWith("Emerge") || keyword.startsWith("Reflect")) { } else if (keyword.startsWith("Emerge") || keyword.startsWith("Reflect")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1])); sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
@@ -2079,7 +2070,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbLong.append(((Companion)inst).getDescription()); sbLong.append(((Companion)inst).getDescription());
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) { } else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
// Pseudo keywords, only print Reminder // Pseudo keywords, only print Reminder
sbLong.append(inst.getReminderText()); sbLong.append(inst.getReminderText()).append("\r\n");
} else if (keyword.contains("At the beginning of your upkeep, ") } else if (keyword.contains("At the beginning of your upkeep, ")
&& keyword.contains(" unless you pay")) { && keyword.contains(" unless you pay")) {
sbLong.append(keyword).append("\r\n"); sbLong.append(keyword).append("\r\n");

View File

@@ -134,9 +134,21 @@ public class CardFactory {
c.removeType(CardType.Supertype.Legendary); c.removeType(CardType.Supertype.Legendary);
} }
if (sourceSA.hasParam("CopySetPower")) {
c.setBasePower(Integer.parseInt(sourceSA.getParam("CopySetPower")));
}
if (sourceSA.hasParam("CopySetToughness")) {
c.setBaseToughness(Integer.parseInt(sourceSA.getParam("CopySetToughness")));
}
if (sourceSA.hasParam("CopyAddTypes")) {
c.addType(Arrays.asList(sourceSA.getParam("CopyAddTypes").split(" & ")));
}
// change the color of the copy (eg: Fork) // change the color of the copy (eg: Fork)
if (sourceSA.hasParam("CopyIsColor")) { if (sourceSA.hasParam("CopyIsColor")) {
ColorSet finalColors = ColorSet.getNullColor(); ColorSet finalColors;
final String newColor = sourceSA.getParam("CopyIsColor"); final String newColor = sourceSA.getParam("CopyIsColor");
if (newColor.equals("ChosenColor")) { if (newColor.equals("ChosenColor")) {
finalColors = ColorSet.fromNames(source.getChosenColors()); finalColors = ColorSet.fromNames(source.getChosenColors());

View File

@@ -982,7 +982,7 @@ public class CardFactoryUtil {
inst.addTrigger(trigger); inst.addTrigger(trigger);
} else if (keyword.equals("Daybound")) { } else if (keyword.equals("Daybound")) {
// Set Day when it's Neither // Set Day when it's Neither
final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with daybound, if its neither day nor night, it becomes day."; final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with daybound, if it's neither day nor night, it becomes day.";
String setDayEff = "DB$ DayTime | Value$ Day"; String setDayEff = "DB$ DayTime | Value$ Day";
Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic); Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic);
@@ -1343,7 +1343,7 @@ public class CardFactoryUtil {
triggers.add(gainControlTrigger); triggers.add(gainControlTrigger);
// when the card with hideaway leaves the battlefield, forget all exiled cards // when the card with hideaway leaves the battlefield, forget all exiled cards
final Trigger changeZoneTrigger = TriggerHandler.parseTrigger("Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | TriggerZone$ Battlefield | Static$ True", card, intrinsic); final Trigger changeZoneTrigger = TriggerHandler.parseTrigger("Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | TriggerZones$ Battlefield | Static$ True", card, intrinsic);
String cleanupStr = "DB$ Cleanup | ClearRemembered$ True"; String cleanupStr = "DB$ Cleanup | ClearRemembered$ True";
changeZoneTrigger.setOverridingAbility(AbilityFactory.getAbility(cleanupStr, card)); changeZoneTrigger.setOverridingAbility(AbilityFactory.getAbility(cleanupStr, card));
triggers.add(changeZoneTrigger); triggers.add(changeZoneTrigger);
@@ -1528,7 +1528,7 @@ public class CardFactoryUtil {
inst.addTrigger(parsedTrigger); inst.addTrigger(parsedTrigger);
} else if (keyword.equals("Nightbound")) { } else if (keyword.equals("Nightbound")) {
// Set Night when it's Neither // Set Night when it's Neither
final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | IsPresent$ Card.Daybound | PresentCompare$ EQ0 | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with nightbound, if its neither day nor night and there are no permanents with daybound on the battlefield, it becomes night."; final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | IsPresent$ Card.Daybound | PresentCompare$ EQ0 | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with nightbound, if it's neither day nor night and there are no permanents with daybound on the battlefield, it becomes night.";
String setDayEff = "DB$ DayTime | Value$ Night"; String setDayEff = "DB$ DayTime | Value$ Night";
Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic); Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic);
@@ -2875,7 +2875,7 @@ public class CardFactoryUtil {
Player activator = this.getActivatingPlayer(); Player activator = this.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
if (!activator.hasKeyword("Foretell on any players turn") && !game.getPhaseHandler().isPlayerTurn(activator)) { if (!activator.hasKeyword("Foretell on any player's turn") && !game.getPhaseHandler().isPlayerTurn(activator)) {
return false; return false;
} }
@@ -3480,7 +3480,7 @@ public class CardFactoryUtil {
} else if (keyword.startsWith("Dash")) { } else if (keyword.startsWith("Dash")) {
effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste"; effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
} else if (keyword.equals("Daybound")) { } else if (keyword.equals("Daybound")) {
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent cant be transformed except by its daybound ability."; effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
} else if (keyword.equals("Decayed")) { } else if (keyword.equals("Decayed")) {
effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " + effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
"Secondary$ True"; "Secondary$ True";
@@ -3534,7 +3534,7 @@ public class CardFactoryUtil {
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " + effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " +
" | Description$ Intimidate ( " + inst.getReminderText() + ")"; " | Description$ Intimidate ( " + inst.getReminderText() + ")";
} else if (keyword.equals("Nightbound")) { } else if (keyword.equals("Nightbound")) {
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent cant be transformed except by its nightbound ability."; effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can't be transformed except by its nightbound ability.";
} else if (keyword.startsWith("Protection")) { } else if (keyword.startsWith("Protection")) {
String valid = getProtectionValid(keyword, false); String valid = getProtectionValid(keyword, false);
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self "; effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self ";

View File

@@ -20,6 +20,8 @@ import forge.game.mana.Mana;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.trigger.Trigger;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
@@ -380,6 +382,15 @@ public class CardProperty {
if (!card.equals(source.getEffectSource())) { if (!card.equals(source.getEffectSource())) {
return false; return false;
} }
} else if (property.equals("withoutManaAbility")) {
if (Iterables.any(card.getSpellAbilities(), SpellAbilityPredicates.isManaAbility())) {
return false;
}
for (final Trigger trig : card.getTriggers()) {
if (trig.getOverridingAbility() != null && !trig.getOverridingAbility().isManaAbility()) {
return false;
}
}
} else if (property.equals("CanBeSacrificedBy") && spellAbility instanceof SpellAbility) { } else if (property.equals("CanBeSacrificedBy") && spellAbility instanceof SpellAbility) {
if (!card.canBeSacrificedBy((SpellAbility) spellAbility)) { if (!card.canBeSacrificedBy((SpellAbility) spellAbility)) {
return false; return false;

View File

@@ -1428,42 +1428,86 @@ public class CardView extends GameEntityView {
if (sa.getManaPart().isAnyMana()) { if (sa.getManaPart().isAnyMana()) {
anyMana = true; anyMana = true;
} }
switch (sa.getManaPart().getOrigProduced()) { if (sa.getManaPart().isComboMana()) {
case "R": String[] colorsProduced = sa.getManaPart().getComboColors().split(" ");
if (!rMana) { //todo improve this
count += 1; for (final String s : colorsProduced) {
rMana = true; switch (s.toUpperCase()) {
case "R":
if (!rMana) {
count += 1;
rMana = true;
}
break;
case "G":
if (!gMana) {
count += 1;
gMana = true;
}
break;
case "B":
if (!bMana) {
count += 1;
bMana = true;
}
break;
case "U":
if (!uMana) {
count += 1;
uMana = true;
}
break;
case "W":
if (!wMana) {
count += 1;
wMana = true;
}
break;
case "C":
if (!cMana) {
cMana = true;
}
break;
} }
break; }
case "G": } else {
if (!gMana) { switch (sa.getManaPart().getOrigProduced()) {
count += 1; case "R":
gMana = true; if (!rMana) {
} count += 1;
break; rMana = true;
case "B": }
if (!bMana) { break;
count += 1; case "G":
bMana = true; if (!gMana) {
} count += 1;
break; gMana = true;
case "U": }
if (!uMana) { break;
count += 1; case "B":
uMana = true; if (!bMana) {
} count += 1;
break; bMana = true;
case "W": }
if (!wMana) { break;
count += 1; case "U":
wMana = true; if (!uMana) {
} count += 1;
break; uMana = true;
case "C": }
if (!cMana) { break;
cMana = true; case "W":
} if (!wMana) {
break; count += 1;
wMana = true;
}
break;
case "C":
if (!cMana) {
cMana = true;
}
break;
}
} }
} }
} }

View File

@@ -51,6 +51,8 @@ public enum CounterEnumType {
BLOOD("BLOOD", 255, 108, 111), BLOOD("BLOOD", 255, 108, 111),
BLOODLINE("BLDLN", 224, 44, 44),
BOUNTY("BOUNT", 255, 158, 0), BOUNTY("BOUNT", 255, 158, 0),
BRIBERY("BRIBE", 172, 201, 235), BRIBERY("BRIBE", 172, 201, 235),

View File

@@ -17,11 +17,10 @@
*/ */
package forge.game.cost; package forge.game.cost;
import java.util.ArrayList; import forge.game.ability.AbilityUtils;
import java.util.List;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
@@ -63,11 +62,17 @@ public class CostDraw extends CostPart {
* @param payer * @param payer
* @param source * @param source
*/ */
private List<Player> getPotentialPlayers(final Player payer, final Card source) { public PlayerCollection getPotentialPlayers(final Player payer, final SpellAbility ability) {
List<Player> res = new ArrayList<>(); PlayerCollection res = new PlayerCollection();
String type = this.getType(); String type = this.getType();
final Card source = ability.getHostCard();
Integer c = convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
for (Player p : payer.getGame().getPlayers()) { for (Player p : payer.getGame().getPlayers()) {
if (p.isValid(type, payer, source, null) && p.canDraw()) { if (p.isValid(type, payer, source, ability) && p.canDrawAmount(c)) {
res.add(p); res.add(p);
} }
} }
@@ -83,8 +88,7 @@ public class CostDraw extends CostPart {
*/ */
@Override @Override
public final boolean canPay(final SpellAbility ability, final Player payer) { public final boolean canPay(final SpellAbility ability, final Player payer) {
List<Player> potentials = getPotentialPlayers(payer, ability.getHostCard()); return !getPotentialPlayers(payer, ability).isEmpty();
return !potentials.isEmpty();
} }
/* /*
@@ -95,7 +99,7 @@ public class CostDraw extends CostPart {
*/ */
@Override @Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) { public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
for (final Player p : getPotentialPlayers(ai, ability.getHostCard())) { for (final Player p : decision.players) {
p.drawCards(decision.c, ability); p.drawCards(decision.c, ability);
} }
return true; return true;

View File

@@ -117,6 +117,11 @@ public class CostExile extends CostPartWithList {
return String.format ("Exile any number of %s from your %s", desc, origin); return String.format ("Exile any number of %s from your %s", desc, origin);
} }
//for very specific situations like Timothar
if (desc.startsWith("the")) {
return String.format("Exile %s from your %s", desc, origin);
}
return String.format("Exile %s from your %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), origin); return String.format("Exile %s from your %s", Cost.convertAmountTypeToWords(i, this.getAmount(), desc), origin);
} }
@@ -159,7 +164,7 @@ public class CostExile extends CostPartWithList {
amount++; amount++;
} }
if ((amount != null) && (list.size() < amount)) { if (amount != null && list.size() < amount) {
return false; return false;
} }

View File

@@ -29,7 +29,6 @@ public class CostPayLife extends CostPart {
* Serializables need a version ID. * Serializables need a version ID.
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
int paidAmount = 0;
/** /**
* Instantiates a new cost pay life. * Instantiates a new cost pay life.
@@ -92,7 +91,7 @@ public class CostPayLife extends CostPart {
return false; return false;
} }
if (payer.hasKeyword("You can't pay life to cast spells or activate abilities.")) { if (!ability.isTrigger() && payer.hasKeyword("You can't pay life to cast spells or activate abilities.")) {
return false; return false;
} }
@@ -101,9 +100,7 @@ public class CostPayLife extends CostPart {
@Override @Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
// TODO Auto-generated method stub return ai.payLife(decision.c, null);
paidAmount = decision.c;
return ai.payLife(paidAmount, null);
} }
public <T> T accept(ICostVisitor<T> visitor) { public <T> T accept(ICostVisitor<T> visitor) {

View File

@@ -141,7 +141,7 @@ public class CostPayment extends ManaConversionMatrix {
PaymentDecision pd = part.accept(decisionMaker); PaymentDecision pd = part.accept(decisionMaker);
// RIght before we start paying as decided, we need to transfer the CostPayments matrix over? // Right before we start paying as decided, we need to transfer the CostPayments matrix over?
if (part instanceof CostPartMana) { if (part instanceof CostPartMana) {
((CostPartMana)part).setCardMatrix(this); ((CostPartMana)part).setCardMatrix(this);
} }
@@ -187,9 +187,10 @@ public class CostPayment extends ManaConversionMatrix {
for (final CostPart part : parts) { for (final CostPart part : parts) {
PaymentDecision decision = part.accept(decisionMaker); PaymentDecision decision = part.accept(decisionMaker);
if (null == decision) return false;
// the AI will try to exile the same card repeatedly unless it does it immediately // the AI will try to exile the same card repeatedly unless it does it immediately
final boolean payImmediately = part instanceof CostExile && ((CostExile) part).from == ZoneType.Library; final boolean payImmediately = part instanceof CostExile && ((CostExile) part).from == ZoneType.Library;
if (null == decision) return false;
// wrap the payment and push onto the cost stack // wrap the payment and push onto the cost stack
game.costPaymentStack.push(part, this); game.costPaymentStack.push(part, this);

View File

@@ -72,7 +72,7 @@ public class CostReveal extends CostPartWithList {
modifiedHand.remove(source); // can't pay for itself modifiedHand.remove(source); // can't pay for itself
handList = modifiedHand; handList = modifiedHand;
} }
handList = CardLists.getValidCards(handList, getType().split(";"), payer, source, ability); handList = CardLists.getValidCards(handList, getType().split(","), payer, source, ability);
return handList.size(); return handList.size();
} }

View File

@@ -89,7 +89,6 @@ public class PaymentDecision {
} }
public static PaymentDecision players(List<Player> players) { public static PaymentDecision players(List<Player> players) {
// TODO Auto-generated method stub
return new PaymentDecision(null, null, players, null, null); return new PaymentDecision(null, null, players, null, null);
} }

View File

@@ -95,7 +95,7 @@ public enum Keyword {
HIDEAWAY("Hideaway", SimpleKeyword.class, false, "This permanent enters the battlefield tapped. When it does, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library."), HIDEAWAY("Hideaway", SimpleKeyword.class, false, "This permanent enters the battlefield tapped. When it does, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library."),
HORSEMANSHIP("Horsemanship", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with horsemanship."), HORSEMANSHIP("Horsemanship", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with horsemanship."),
IMPROVISE("Improvise", SimpleKeyword.class, true, "Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}."), IMPROVISE("Improvise", SimpleKeyword.class, true, "Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}."),
INDESTRUCTIBLE("Indestructible", SimpleKeyword.class, true, "Effects that say \"destroy\" dont destroy this."), INDESTRUCTIBLE("Indestructible", SimpleKeyword.class, true, "Effects that say \"destroy\" don't destroy this."),
INFECT("Infect", SimpleKeyword.class, true, "This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters."), INFECT("Infect", SimpleKeyword.class, true, "This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters."),
INGEST("Ingest", SimpleKeyword.class, false, "Whenever this creature deals combat damage to a player, that player exiles the top card of their library."), INGEST("Ingest", SimpleKeyword.class, false, "Whenever this creature deals combat damage to a player, that player exiles the top card of their library."),
INTIMIDATE("Intimidate", SimpleKeyword.class, true, "This creature can't be blocked except by artifact creatures and/or creatures that share a color with it."), INTIMIDATE("Intimidate", SimpleKeyword.class, true, "This creature can't be blocked except by artifact creatures and/or creatures that share a color with it."),
@@ -105,7 +105,7 @@ public enum Keyword {
LEVEL_UP("Level up", KeywordWithCost.class, false, "%s: Put a level counter on this. Level up only as a sorcery."), LEVEL_UP("Level up", KeywordWithCost.class, false, "%s: Put a level counter on this. Level up only as a sorcery."),
LIFELINK("Lifelink", SimpleKeyword.class, true, "Damage dealt by this creature also causes its controller to gain that much life."), LIFELINK("Lifelink", SimpleKeyword.class, true, "Damage dealt by this creature also causes its controller to gain that much life."),
LIVING_WEAPON("Living Weapon", SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Phyrexian Germ creature token, then attach this to it."), LIVING_WEAPON("Living Weapon", SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Phyrexian Germ creature token, then attach this to it."),
MADNESS("Madness", KeywordWithCost.class, false, "If you discard this card, discard it into exile. When you do, cast it for %s or put it into your graveyard."), MADNESS("Madness", KeywordWithCost.class, false, "If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard."),
MELEE("Melee", SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked this combat."), MELEE("Melee", SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked this combat."),
MENTOR("Mentor", SimpleKeyword.class, false, "Whenever this creature attacks, put a +1/+1 counter on target attacking creature with lesser power."), MENTOR("Mentor", SimpleKeyword.class, false, "Whenever this creature attacks, put a +1/+1 counter on target attacking creature with lesser power."),
MENACE("Menace", SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."), MENACE("Menace", SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."),
@@ -130,7 +130,7 @@ public enum Keyword {
PROTECTION("Protection", Protection.class, false, "This creature can't be blocked, targeted, dealt damage, or equipped/enchanted by %s."), PROTECTION("Protection", Protection.class, false, "This creature can't be blocked, targeted, dealt damage, or equipped/enchanted by %s."),
PROVOKE("Provoke", SimpleKeyword.class, false, "Whenever this creature attacks, you may have target creature defending player controls untap and block it if able."), PROVOKE("Provoke", SimpleKeyword.class, false, "Whenever this creature attacks, you may have target creature defending player controls untap and block it if able."),
PROWESS("Prowess", SimpleKeyword.class, false, "Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn."), PROWESS("Prowess", SimpleKeyword.class, false, "Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn."),
PROWL("Prowl", KeywordWithCost.class, false, "You may pay %s rather than pay this spells mana cost if a player was dealt combat damage this turn by a source that, at the time it dealt that damage, was under your control and had any of this spells creature types."), PROWL("Prowl", KeywordWithCost.class, false, "You may pay %s rather than pay this spell's mana cost if a player was dealt combat damage this turn by a source that, at the time it dealt that damage, was under your control and had any of this spell's creature types."),
RAMPAGE("Rampage", KeywordWithAmount.class, false, "Whenever this creature becomes blocked, it gets +%1$d/+%1$d until end of turn for each creature blocking it beyond the first."), RAMPAGE("Rampage", KeywordWithAmount.class, false, "Whenever this creature becomes blocked, it gets +%1$d/+%1$d until end of turn for each creature blocking it beyond the first."),
REACH("Reach", SimpleKeyword.class, true, "This creature can block creatures with flying."), REACH("Reach", SimpleKeyword.class, true, "This creature can block creatures with flying."),
REBOUND("Rebound", SimpleKeyword.class, true, "If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost."), REBOUND("Rebound", SimpleKeyword.class, true, "If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost."),

View File

@@ -73,7 +73,6 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
protected abstract void parse(String details); protected abstract void parse(String details);
protected abstract String formatReminderText(String reminderText); protected abstract String formatReminderText(String reminderText);
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean) * @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean)

View File

@@ -15,7 +15,7 @@ public class Trample extends KeywordInstance<Trample> {
@Override @Override
protected String formatReminderText(String reminderText) { protected String formatReminderText(String reminderText) {
if (!type.isEmpty()) { if (!type.isEmpty()) {
return "This creature can deal excess combat damage to the controller of the planeswalker its attacking."; return "This creature can deal excess combat damage to the controller of the planeswalker it's attacking.";
} }
return reminderText; return reminderText;
} }

View File

@@ -104,6 +104,8 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantBeCast; import forge.game.staticability.StaticAbilityCantBeCast;
import forge.game.staticability.StaticAbilityCantDraw;
import forge.game.staticability.StaticAbilityCantPutCounter;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -862,13 +864,8 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final boolean canReceiveCounters(final CounterType type) { public final boolean canReceiveCounters(final CounterType type) {
// CantPutCounter static abilities if (StaticAbilityCantPutCounter.anyCantPutCounter(this, type)) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { return false;
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPutCounter", this, type)) {
return false;
}
}
} }
return true; return true;
} }
@@ -1284,13 +1281,11 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final boolean canDraw() { public final boolean canDraw() {
if (hasKeyword("You can't draw cards.")) { return canDrawAmount(1);
return false; }
}
if (hasKeyword("You can't draw more than one card each turn.")) { public final boolean canDrawAmount(int amount) {
return numDrawnThisTurn < 1; return StaticAbilityCantDraw.canDrawThisAmount(this, amount);
}
return true;
} }
public final CardCollectionView drawCard() { public final CardCollectionView drawCard() {
@@ -1302,6 +1297,9 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final CardCollectionView drawCards(final int n, SpellAbility cause) { public final CardCollectionView drawCards(final int n, SpellAbility cause) {
final CardCollection drawn = new CardCollection(); final CardCollection drawn = new CardCollection();
if (n <= 0) {
return drawn;
}
// Replacement effects // Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
@@ -3129,7 +3127,7 @@ public class Player extends GameEntity implements Comparable<Player> {
moved += " | Destination$ Graveyard,Exile,Hand,Library | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead."; moved += " | Destination$ Graveyard,Exile,Hand,Library | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead.";
} else { } else {
// rule 903.9b // rule 903.9b
moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owners hand or library from anywhere, its owner may put it into the command zone instead."; moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead.";
} }
eff.addReplacementEffect(ReplacementHandler.parseReplacement(moved, eff, true)); eff.addReplacementEffect(ReplacementHandler.parseReplacement(moved, eff, true));
} }

View File

@@ -564,17 +564,19 @@ public class AbilityManaPart implements java.io.Serializable {
return TextUtil.fastReplace(origProduced, "Combo ", ""); return TextUtil.fastReplace(origProduced, "Combo ", "");
} }
// ColorIdentity // ColorIdentity
List<Card> commanders = getSourceCard().getController().getCommanders();
if (commanders.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
ColorSet identity = getSourceCard().getController().getCommanderColorID(); if (getSourceCard().getController() != null) {
if (identity.hasWhite()) { sb.append("W "); } List<Card> commanders = getSourceCard().getController().getCommanders();
if (identity.hasBlue()) { sb.append("U "); } if (commanders.isEmpty()) {
if (identity.hasBlack()) { sb.append("B "); } return "";
if (identity.hasRed()) { sb.append("R "); } }
if (identity.hasGreen()) { sb.append("G "); } ColorSet identity = getSourceCard().getController().getCommanderColorID();
if (identity.hasWhite()) { sb.append("W "); }
if (identity.hasBlue()) { sb.append("U "); }
if (identity.hasBlack()) { sb.append("B "); }
if (identity.hasRed()) { sb.append("R "); }
if (identity.hasGreen()) { sb.append("G "); }
}
// TODO: Add support for {C}. // TODO: Add support for {C}.
return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1); return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
} }

View File

@@ -37,7 +37,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardState; import forge.game.card.CardState;
import forge.game.card.CounterType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -330,42 +329,6 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
return false; return false;
} }
public final boolean applyAbility(String mode, Card card, CounterType type) {
// don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) {
return false;
}
if (this.isSuppressed() || !this.checkConditions()) {
return false;
}
if (mode.equals("CantPutCounter")) {
return StaticAbilityCantPutCounter.applyCantPutCounter(this, card, type);
}
return false;
}
public final boolean applyAbility(String mode, Player player, CounterType type) {
// don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) {
return false;
}
if (this.isSuppressed() || !this.checkConditions()) {
return false;
}
if (mode.equals("CantPutCounter")) {
return StaticAbilityCantPutCounter.applyCantPutCounter(this, player, type);
}
return false;
}
public final boolean applyAbility(final String mode, final Card card, final boolean isCombat) { public final boolean applyAbility(final String mode, final Card card, final boolean isCombat) {
// don't apply the ability if it hasn't got the right mode // don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) { if (!getParam("Mode").equals(mode)) {

View File

@@ -0,0 +1,42 @@
package forge.game.staticability;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
public class StaticAbilityCantDraw {
static String MODE = "CantDraw";
public static boolean canDrawThisAmount(final Player player, int startAmount) {
if (startAmount <= 0) {
return true;
}
return startAmount <= canDrawAmount(player, startAmount);
}
public static int canDrawAmount(final Player player, int startAmount) {
int amount = startAmount;
if (startAmount <= 0)
return 0;
final Game game = player.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
amount = applyCantDrawAmountAbility(stAb, player, amount);
}
}
return amount;
}
public static int applyCantDrawAmountAbility(final StaticAbility stAb, final Player player, int amount) {
if (!stAb.matchesValidParam("ValidPlayer", player)) {
return amount;
}
int limit = Integer.valueOf(stAb.getParamOrDefault("DrawLimit", "0"));
int drawn = player.getNumDrawnThisTurn();
return Math.min(Math.max(limit - drawn, 0), amount);
}
}

View File

@@ -1,11 +1,45 @@
package forge.game.staticability; package forge.game.staticability;
import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType;
public class StaticAbilityCantPutCounter { public class StaticAbilityCantPutCounter {
static String MODE = "CantPutCounter";
public static boolean anyCantPutCounter(final Card card, final CounterType type) {
final Game game = card.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
if (applyCantPutCounter(stAb, card, type)) {
return true;
}
}
}
return false;
}
public static boolean anyCantPutCounter(final Player player, final CounterType type) {
final Game game = player.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
if (applyCantPutCounter(stAb, player, type)) {
return true;
}
}
}
return false;
}
public static boolean applyCantPutCounter(final StaticAbility stAb, final Card card, final CounterType type) { public static boolean applyCantPutCounter(final StaticAbility stAb, final Card card, final CounterType type) {
if (stAb.hasParam("CounterType")) { if (stAb.hasParam("CounterType")) {
CounterType t = CounterType.getType(stAb.getParam("CounterType")); CounterType t = CounterType.getType(stAb.getParam("CounterType"));

View File

@@ -61,7 +61,8 @@ public class PlayerZone extends Zone {
} }
boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) || boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) ||
c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE); c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE) ||
c.hasKeyword(Keyword.DISTURB);
boolean exileCastable = (c.isAdventureCard() || c.isForetold()) && c.isInZone(ZoneType.Exile); boolean exileCastable = (c.isAdventureCard() || c.isForetold()) && c.isInZone(ZoneType.Exile);
for (final SpellAbility sa : c.getSpellAbilities()) { for (final SpellAbility sa : c.getSpellAbilities()) {
final ZoneType restrictZone = sa.getRestrictions().getZone(); final ZoneType restrictZone = sa.getRestrictions().getZone();

View File

@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="forge.app" package="forge.app"
android:versionCode="1" android:versionCode="1"
android:versionName="1.6.45" > <!-- versionName should be updated and it's used for Sentry releases tag --> android:versionName="1.6.46" > <!-- versionName should be updated and it's used for Sentry releases tag -->
<uses-sdk <uses-sdk
android:minSdkVersion="19" android:minSdkVersion="19"

View File

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

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.46-SNAPSHOT</version> <version>1.6.47-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-desktop</artifactId> <artifactId>forge-gui-desktop</artifactId>
@@ -250,7 +250,7 @@
</goals> </goals>
<configuration> <configuration>
<headerType>gui</headerType> <headerType>gui</headerType>
<outfile>${project.build.directory}/forge.exe</outfile> <outfile>${project.build.directory}/forge-java8.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar> <jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar> <dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle> <errTitle>forge</errTitle>
@@ -284,7 +284,7 @@
</txtProductVersion> </txtProductVersion>
<productName>Forge</productName> <productName>Forge</productName>
<internalName>forge</internalName> <internalName>forge</internalName>
<originalFilename>forge.exe</originalFilename> <originalFilename>forge-java8.exe</originalFilename>
</versionInfo> </versionInfo>
</configuration> </configuration>
</execution> </execution>
@@ -322,6 +322,7 @@
<include name="res/**" /> <include name="res/**" />
<exclude name="res/cardsfolder/**" /> <exclude name="res/cardsfolder/**" />
</fileset> </fileset>
<fileset dir="${project.build.directory}" includes="forge-java8.exe" />
<fileset dir="${project.build.directory}" includes="forge.exe" /> <fileset dir="${project.build.directory}" includes="forge.exe" />
<fileset dir="${project.build.directory}" includes="${project.build.finalName}-jar-with-dependencies.jar" /> <fileset dir="${project.build.directory}" includes="${project.build.finalName}-jar-with-dependencies.jar" />
</copy> </copy>
@@ -329,18 +330,21 @@
<zip destfile="${project.build.directory}/${project.build.finalName}/res/cardsfolder/cardsfolder.zip" basedir="${basedir}/../forge-gui/res/cardsfolder" level="1" /> <zip destfile="${project.build.directory}/${project.build.finalName}/res/cardsfolder/cardsfolder.zip" basedir="${basedir}/../forge-gui/res/cardsfolder" level="1" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge.sh" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge.sh" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge.command" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge.command" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-java8.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge.exe" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge.exe" perm="a+rx" />
<tar destfile="${project.build.directory}/${project.build.finalName}.tar.bz2" compression="bzip2"> <tar destfile="${project.build.directory}/${project.build.finalName}.tar.bz2" compression="bzip2">
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}"> <tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
<include name="forge.sh" /> <include name="forge.sh" />
<include name="forge.command" /> <include name="forge.command" />
<include name="forge.exe" /> <include name="forge.exe" />
<include name="forge-java8.exe" />
</tarfileset> </tarfileset>
<tarfileset dir="${project.build.directory}/${project.build.finalName}"> <tarfileset dir="${project.build.directory}/${project.build.finalName}">
<include name="**" /> <include name="**" />
<exclude name="forge.sh" /> <exclude name="forge.sh" />
<exclude name="forge.command" /> <exclude name="forge.command" />
<exclude name="forge.exe" /> <exclude name="forge.exe" />
<exclude name="forge-java8.exe" />
</tarfileset> </tarfileset>
</tar> </tar>
</target> </target>
@@ -407,7 +411,7 @@
</goals> </goals>
<configuration> <configuration>
<headerType>gui</headerType> <headerType>gui</headerType>
<outfile>${project.build.directory}/forge.exe</outfile> <outfile>${project.build.directory}/forge-java8.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar> <jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar> <dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle> <errTitle>forge</errTitle>
@@ -424,6 +428,60 @@
<opt>-Dfile.encoding=UTF-8</opt> <opt>-Dfile.encoding=UTF-8</opt>
</opts> </opts>
</jre> </jre>
<versionInfo>
<fileVersion>
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
</fileVersion>
<txtFileVersion>
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
</txtFileVersion>
<fileDescription>Forge</fileDescription>
<copyright>Forge</copyright>
<productVersion>
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
</productVersion>
<txtProductVersion>
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
</txtProductVersion>
<productName>Forge</productName>
<internalName>forge</internalName>
<originalFilename>forge-java8.exe</originalFilename>
</versionInfo>
</configuration>
</execution>
<!--extra-->
<execution>
<id>l4j-gui2</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
<configuration>
<headerType>gui</headerType>
<outfile>${project.build.directory}/forge.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle>
<downloadUrl>https://www.oracle.com/java/technologies/downloads/</downloadUrl>
<icon>src/main/config/forge.ico</icon>
<classPath>
<mainClass>forge.view.Main</mainClass>
<addDependencies>false</addDependencies>
<preCp>anything</preCp>
</classPath>
<jre>
<minVersion>11.0.1</minVersion>
<jdkPreference>jdkOnly</jdkPreference>
<maxHeapSize>4096</maxHeapSize>
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
<opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.text=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt>
</opts>
</jre>
<versionInfo> <versionInfo>
<fileVersion> <fileVersion>
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0 ${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
@@ -445,6 +503,7 @@
</versionInfo> </versionInfo>
</configuration> </configuration>
</execution> </execution>
<!--extra-->
</executions> </executions>
</plugin> </plugin>
@@ -482,11 +541,13 @@
<include name="res/**" /> <include name="res/**" />
<exclude name="res/cardsfolder/**" /> <exclude name="res/cardsfolder/**" />
</fileset> </fileset>
<fileset dir="${project.build.directory}" includes="forge-java8.exe" />
<fileset dir="${project.build.directory}" includes="forge.exe" /> <fileset dir="${project.build.directory}" includes="forge.exe" />
<fileset dir="${project.build.directory}" includes="${project.build.finalName}-jar-with-dependencies.jar" /> <fileset dir="${project.build.directory}" includes="${project.build.finalName}-jar-with-dependencies.jar" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.exe" /> <fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.exe" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.sh" /> <fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure-java8.exe" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.command" /> <fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.sh" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.command" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure-${project.version}-jar-with-dependencies.jar" /> <fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure-${project.version}-jar-with-dependencies.jar" />
</copy> </copy>
<mkdir dir="${project.build.directory}/${project.build.finalName}/res/cardsfolder" /> <mkdir dir="${project.build.directory}/${project.build.finalName}/res/cardsfolder" />
@@ -495,18 +556,22 @@
<chmod file="${project.build.directory}/${project.build.finalName}/forge.command" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge.command" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.sh" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.sh" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.command" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.command" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-java8.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge.exe" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.exe" perm="a+rx" /> <chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure.exe" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-adventure-java8.exe" perm="a+rx" />
<tar destfile="${project.build.directory}/${project.build.finalName}.tar.bz2" compression="bzip2"> <tar destfile="${project.build.directory}/${project.build.finalName}.tar.bz2" compression="bzip2">
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}"> <tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
<include name="forge.sh" /> <include name="forge.sh" />
<include name="forge.command" /> <include name="forge.command" />
<include name="forge-java8.exe" />
<include name="forge.exe" /> <include name="forge.exe" />
</tarfileset> </tarfileset>
<tarfileset dir="${project.build.directory}/${project.build.finalName}"> <tarfileset dir="${project.build.directory}/${project.build.finalName}">
<include name="**" /> <include name="**" />
<exclude name="forge.sh" /> <exclude name="forge.sh" />
<exclude name="forge.command" /> <exclude name="forge.command" />
<exclude name="forge-java8.exe" />
<exclude name="forge.exe" /> <exclude name="forge.exe" />
</tarfileset> </tarfileset>
</tar> </tar>

View File

@@ -172,10 +172,13 @@ public class FDeckViewer extends FDialog {
private void copyToClipboard() { private void copyToClipboard() {
final String nl = System.getProperty("line.separator"); final String nl = System.getProperty("line.separator");
final StringBuilder deckList = new StringBuilder(); final StringBuilder deckList = new StringBuilder();
final String dName = deck.getName(); String dName = deck.getName();
//fix copying a commander netdeck then importing it again...
if (dName.startsWith("[Commander")||dName.contains("Commander"))
dName = "";
String cardName; String cardName;
SortedMap<String, Integer> sectionCards; SortedMap<String, Integer> sectionCards;
deckList.append(dName == null ? "" : dName + nl + nl); deckList.append(dName == null ? "" : "Deck: "+dName + nl + nl);
for (DeckSection s : DeckSection.values()){ for (DeckSection s : DeckSection.values()){
CardPool cp = deck.get(s); CardPool cp = deck.get(s);

View File

@@ -32,7 +32,6 @@ import java.beans.PropertyChangeListener;
import javax.swing.JButton; import javax.swing.JButton;
import forge.control.FControl;
import forge.game.GameView; import forge.game.GameView;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.gui.FThreads; import forge.gui.FThreads;
@@ -43,7 +42,6 @@ import forge.model.FModel;
import forge.screens.match.CMatchUI; import forge.screens.match.CMatchUI;
import forge.screens.match.views.VPrompt; import forge.screens.match.views.VPrompt;
import forge.toolbox.FSkin; import forge.toolbox.FSkin;
import forge.util.Localizer;
/** /**
* Controls the prompt panel in the match UI. * Controls the prompt panel in the match UI.
@@ -157,9 +155,7 @@ public class CPrompt implements ICDoc {
matchUI.getGameController().selectButtonCancel(); matchUI.getGameController().selectButtonCancel();
} }
public void setMessage(final String s0) { public void setMessage(final String header) {
String header = FControl.instance.getCurrentScreen().getDaytime() != null ? "[" + Localizer.getInstance().getMessage("lbl"+FControl.instance.getCurrentScreen().getDaytime()) + "]\n\n" : "";
header += s0;
view.getTarMessage().setText(FSkin.encodeSymbols(header, false)); view.getTarMessage().setText(FSkin.encodeSymbols(header, false));
view.setCardView(null); view.setCardView(null);
} }

View File

@@ -1681,7 +1681,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
StringUtils.split("Balustrade Spy;Bloodstained Mire;Felidar Guardian;Field of the Dead;Flooded Strand;Inverter of Truth;Kethis, the Hidden Hand;Leyline of Abundance;Nexus of Fate;Oko, Thief of Crowns;Once Upon a Time;Polluted Delta;Smuggler's Copter;Teferi, Time Raveler;Undercity Informer;Underworld Breach;Uro, Titan of Nature's Wrath;Veil of Summer;Walking Ballista;Wilderness Reclamation;Windswept Heath;Wooded Foothills", StringUtils.split("Balustrade Spy;Bloodstained Mire;Felidar Guardian;Field of the Dead;Flooded Strand;Inverter of Truth;Kethis, the Hidden Hand;Leyline of Abundance;Nexus of Fate;Oko, Thief of Crowns;Once Upon a Time;Polluted Delta;Smuggler's Copter;Teferi, Time Raveler;Undercity Informer;Underworld Breach;Uro, Titan of Nature's Wrath;Veil of Summer;Walking Ballista;Wilderness Reclamation;Windswept Heath;Wooded Foothills",
';')); ';'));
List<String> allowedPioneerSets = Arrays.asList( List<String> allowedPioneerSets = Arrays.asList(
StringUtils.split("RTR,GTC,DGM,M14,THS,BNG,JOU,M15,KTK,FRF,DTK,ORI,BFZ,OGW,SOI,EMN,KLD,AER,AKH,HOU,XLN,RIX,DOM,M19,G18,GRN,RNA,WAR,M20,ELD,THB,IKO,M21,ZNR,KHM,STX,AFR,MID", StringUtils.split("RTR,GTC,DGM,M14,THS,BNG,JOU,M15,KTK,FRF,DTK,ORI,BFZ,OGW,SOI,EMN,KLD,AER,AKH,HOU,XLN,RIX,DOM,M19,G18,GRN,RNA,WAR,M20,ELD,THB,IKO,M21,ZNR,KHM,STX,AFR,MID,VOW",
",")); ","));
List<String> restrictedList = null; List<String> restrictedList = null;
@@ -1718,7 +1718,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
// SIMULATE A GAME OF VINTAGE // SIMULATE A GAME OF VINTAGE
DeckRecognizer recognizer = new DeckRecognizer(); DeckRecognizer recognizer = new DeckRecognizer();
List<String> allowedSetCodes = Arrays.asList( List<String> allowedSetCodes = Arrays.asList(
StringUtils.split("7ED, 9ED, ORI, M14, M15, 6ED, 8ED, M11, 3ED, M10, M12, 10E, M13, G18, M21, M20, M19, 5ED, 2ED, 4ED, LEB, LEA, 5DN, SOM, KTK, THS, DIS, JOU, MOR, TMP, SOI, FEM, USG, ALL, ROE, EXO, TSP, LRW, TOR, ALA, RIX, DGM, DKA, MBS, AER, RNA, GTC, CSP, HML, NPH, OGW, ZNR, EMN, UDS, SHM, BNG, SOK, EVE, INV, THB, DOM, NMS, VIS, WAR, GRN, PCY, SCG, MRD, XLN, ONS, IKO, MMQ, CHK, ULG, AKH, MIR, ISD, AVR, KLD, APC, RTR, WWK, PLC, HOU, LEG, AFR, ARN, ICE, STX, LGN, ARB, KHM, CFX, TSB, ZEN, ELD, JUD, GPT, BFZ, BOK, DTK, FRF, FUT, WTH, ODY, RAV, ATQ, DRK, PLS, STH, DST, TD2, HA1, ME4, HA3, HA2, HA5, HA4, MED, ANB, ME3, KLR, PZ2, ANA, PRM, PZ1, AJMP, ME2, TD1, TD0, TPR, VMA, AKR, MBP, PZEN, PGTW, PL21, PFUT, PWAR, PAL01, PJUD, PAL00, PTKDF, PWOR, PWP12, PSTH, POGW, PFRF, PG07, PSUS, PUST, J18, PWP10, PAL02, PAL03, PWP11, J19, PGRN, PM10, PDP14, PRTR, PMPS06, PBNG, PJ21, G09, PNPH, PM15, PAL06, G08, PDST, J20, PMBS, PMPS07, PEXO, PDOM, PONS, PRW2, PMPS11, PMPS, PM19, PWWK, PCEL, PAL04, PAL05, PMPS10, PDTK, PALP, F10, F04, PMOR, PAL99, PEMN, PCNS, PPLC, PRAV, PPP1, PI14, PXLN, PF20, PTSP, F05, F11, PSCG, PBOOK, F07, F13, PODY, PM12, P08, PSS1, P2HG, P09, PTOR, PDP13, F12, F06, PALA, PXTC, F02, F16, PHOU, PSOM, PI13, PCON, PDGM, PIDW, PMRD, PRNA, P9ED, PHEL, F17, F03, PURL, F15, F01, PWOS, PPC1, PBOK, PTMP, PS19, PS18, PF19, PGPT, PCHK, FNM, F14, PISD, PAKH, PDP15, PRIX, PS15, PPCY, OLGC, OVNT, PLGN, PS14, P03, PDTP, PM14, FS, PPLS, MPR, PKTK, PS16, PRWK, PS17, PBFZ, PSS2, PINV, G03, P8ED, PARL, P04, P10, PSDC, JGP, G99, WW, P11, P05, PDIS, PROE, PDP10, F08, P10E, PELP, PMH1, P07, P5DN, PGRU, SHC, PM11, P06, PUSG, PCMP, PULG, F09, PUDS, PARB, DRC94, PMPS09, PORI, J12, G06, PMMQ, G07, J13, PMPS08, PM20, PSOI, PJSE, G05, G11, PNAT, PSOK, PEVE, PRED, G10, G04, PSHM, PPRO, PAPC, PJJT, ARENA, PKLD, G00, J14, PLGM, P15A, PCSP, PWPN, PJAS, PWP21, PWP09, PDKA, PNEM, PPTK, J15, G01, PG08, PLRW, PMEI, PM13, PHJ, PGTC, J17, PRES, PWCQ, PJOU, PDP12, PAER, PAVR, PTHS, G02, J16, PSUM, PGPX, UGF, PSS3, MM2, MM3, MB1, FMB1, A25, 2XM, MMA, PLIST, CHR, EMA, IMA, TSR, UMA, PUMA, E02, DPA, ATH, MD1, GK1, GK2, CST, BRB, BTD, DKM, FVE, V17, V13, STA, MPS_RNA, V16, SLD, V12, CC1, MPS_GRN, DRB, FVR, SS3, SS1, MPS_AKH, FVL, V15, MPS_KLD, ZNE, PDS, SS2, PD3, SLU, V14, PD2, EXP, MPS_WAR, DDQ, DDE, GS1, DDS, DDU, DD1, DDL, DDF, DDP, DD2, DDR, DDH, DDT, DDK, DDG, DDC, DDM, DDJ, DDO, GVL, JVC, DDI, DVD, DDN, EVG, DDD, C18, C19, C21, C20, C13, CMA, C14, C15, KHC, ZNC, AFC, C17, C16, COM, CM1,CM2,PO2,S99,W16,W17,S00,PTK,CP3,POR,CP1,CP2,CMR,MH2,H1R,CNS,BBD,MH1,CN2,JMP,PCA,GNT,ARC,GN2,PC2,E01,HOP,PLG20,PLG21,CC2,MID,MIC", StringUtils.split("7ED, 9ED, ORI, M14, M15, 6ED, 8ED, M11, 3ED, M10, M12, 10E, M13, G18, M21, M20, M19, 5ED, 2ED, 4ED, LEB, LEA, 5DN, SOM, KTK, THS, DIS, JOU, MOR, TMP, SOI, FEM, USG, ALL, ROE, EXO, TSP, LRW, TOR, ALA, RIX, DGM, DKA, MBS, AER, RNA, GTC, CSP, HML, NPH, OGW, ZNR, EMN, UDS, SHM, BNG, SOK, EVE, INV, THB, DOM, NMS, VIS, WAR, GRN, PCY, SCG, MRD, XLN, ONS, IKO, MMQ, CHK, ULG, AKH, MIR, ISD, AVR, KLD, APC, RTR, WWK, PLC, HOU, LEG, AFR, ARN, ICE, STX, LGN, ARB, KHM, CFX, TSB, ZEN, ELD, JUD, GPT, BFZ, BOK, DTK, FRF, FUT, WTH, ODY, RAV, ATQ, DRK, PLS, STH, DST, TD2, HA1, ME4, HA3, HA2, HA5, HA4, MED, ANB, ME3, KLR, PZ2, ANA, PRM, PZ1, AJMP, ME2, TD1, TD0, TPR, VMA, AKR, MBP, PZEN, PGTW, PL21, PFUT, PWAR, PAL01, PJUD, PAL00, PTKDF, PWOR, PWP12, PSTH, POGW, PFRF, PG07, PSUS, PUST, J18, PWP10, PAL02, PAL03, PWP11, J19, PGRN, PM10, PDP14, PRTR, PMPS06, PBNG, PJ21, G09, PNPH, PM15, PAL06, G08, PDST, J20, PMBS, PMPS07, PEXO, PDOM, PONS, PRW2, PMPS11, PMPS, PM19, PWWK, PCEL, PAL04, PAL05, PMPS10, PDTK, PALP, F10, F04, PMOR, PAL99, PEMN, PCNS, PPLC, PRAV, PPP1, PI14, PXLN, PF20, PTSP, F05, F11, PSCG, PBOOK, F07, F13, PODY, PM12, P08, PSS1, P2HG, P09, PTOR, PDP13, F12, F06, PALA, PXTC, F02, F16, PHOU, PSOM, PI13, PCON, PDGM, PIDW, PMRD, PRNA, P9ED, PHEL, F17, F03, PURL, F15, F01, PWOS, PPC1, PBOK, PTMP, PS19, PS18, PF19, PGPT, PCHK, FNM, F14, PISD, PAKH, PDP15, PRIX, PS15, PPCY, OLGC, OVNT, PLGN, PS14, P03, PDTP, PM14, FS, PPLS, MPR, PKTK, PS16, PRWK, PS17, PBFZ, PSS2, PINV, G03, P8ED, PARL, P04, P10, PSDC, JGP, G99, WW, P11, P05, PDIS, PROE, PDP10, F08, P10E, PELP, PMH1, P07, P5DN, PGRU, SHC, PM11, P06, PUSG, PCMP, PULG, F09, PUDS, PARB, DRC94, PMPS09, PORI, J12, G06, PMMQ, G07, J13, PMPS08, PM20, PSOI, PJSE, G05, G11, PNAT, PSOK, PEVE, PRED, G10, G04, PSHM, PPRO, PAPC, PJJT, ARENA, PKLD, G00, J14, PLGM, P15A, PCSP, PWPN, PJAS, PWP21, PWP09, PDKA, PNEM, PPTK, J15, G01, PG08, PLRW, PMEI, PM13, PHJ, PGTC, J17, PRES, PWCQ, PJOU, PDP12, PAER, PAVR, PTHS, G02, J16, PSUM, PGPX, UGF, PSS3, MM2, MM3, MB1, FMB1, A25, 2XM, MMA, PLIST, CHR, EMA, IMA, TSR, UMA, PUMA, E02, DPA, ATH, MD1, GK1, GK2, CST, BRB, BTD, DKM, FVE, V17, V13, STA, MPS_RNA, V16, SLD, V12, CC1, MPS_GRN, DRB, FVR, SS3, SS1, MPS_AKH, FVL, V15, MPS_KLD, ZNE, PDS, SS2, PD3, SLU, V14, PD2, EXP, MPS_WAR, DDQ, DDE, GS1, DDS, DDU, DD1, DDL, DDF, DDP, DD2, DDR, DDH, DDT, DDK, DDG, DDC, DDM, DDJ, DDO, GVL, JVC, DDI, DVD, DDN, EVG, DDD, C18, C19, C21, C20, C13, CMA, C14, C15, KHC, ZNC, AFC, C17, C16, COM, CM1,CM2,PO2,S99,W16,W17,S00,PTK,CP3,POR,CP1,CP2,CMR,MH2,H1R,CNS,BBD,MH1,CN2,JMP,PCA,GNT,ARC,GN2,PC2,E01,HOP,PLG20,PLG21,CC2,MID,MIC,VOW,VOC",
",")); ","));
allowedSetCodes = allowedSetCodes.stream().map(String::trim).collect(Collectors.toList()); allowedSetCodes = allowedSetCodes.stream().map(String::trim).collect(Collectors.toList());
List<String> bannedCards = Arrays.asList( List<String> bannedCards = Arrays.asList(

View File

@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type> <packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory> <build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory> <build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.45.001</alpha-version> <alpha-version>1.6.46.001</alpha-version>
</properties> </properties>
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.46-SNAPSHOT</version> <version>1.6.47-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-ios</artifactId> <artifactId>forge-gui-ios</artifactId>

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ import java.util.Deque;
import java.util.List; import java.util.List;
public class Forge implements ApplicationListener { public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.45.001"; public static final String CURRENT_VERSION = "1.6.46.001";
private static ApplicationListener app = null; private static ApplicationListener app = null;
private static Clipboard clipboard; private static Clipboard clipboard;

View File

@@ -90,7 +90,7 @@ public class FDeckViewer extends FScreen {
//fix copying a commander netdeck then importing it again... //fix copying a commander netdeck then importing it again...
if (dName.startsWith("[Commander")||dName.contains("Commander")) if (dName.startsWith("[Commander")||dName.contains("Commander"))
dName = ""; dName = "";
deckList.append(dName == null ? "" : dName + nl + nl); deckList.append(dName == null ? "" : "Deck: "+dName + nl + nl);
for (DeckSection s : DeckSection.values()){ for (DeckSection s : DeckSection.values()){
CardPool cp = deck.get(s); CardPool cp = deck.get(s);

View File

@@ -528,13 +528,17 @@ public abstract class ItemManager<T extends InventoryItem> extends FContainer im
if (pool == null) { if (pool == null) {
return; return;
} }
pool.add(item, qty); try {
if (isUnfiltered()) { pool.add(item, qty);
model.addItem(item, qty); if (isUnfiltered()) {
model.addItem(item, qty);
}
List<T> items = new ArrayList<>();
items.add(item);
updateView(false, items);
} catch (Exception e) {
e.printStackTrace();
} }
List<T> items = new ArrayList<>();
items.add(item);
updateView(false, items);
} }
public void addItems(Iterable<Entry<T, Integer>> itemsToAdd) { public void addItems(Iterable<Entry<T, Integer>> itemsToAdd) {

View File

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

View File

@@ -1,5 +1,6 @@
#Add one announcement per line #Add one announcement per line
Get in the discord if you aren't yet. https://discord.gg/3v9JCVr Get in the discord if you aren't yet. https://discord.gg/3v9JCVr
A new game mode is now available as a separate executable - Forge Adventure, a Shandalar-like overworld adventure. This game mode is currently in its early stage of development, expect improvements and fixes in the future releases.
Forge now supports 100% of core and expansion set cards from Limited Edition Alpha to Innistrad: Midnight Hunt. This includes Equinox, Chaos Orb, and Falling Star. Forge now supports 100% of core and expansion set cards from Limited Edition Alpha to Innistrad: Midnight Hunt. This includes Equinox, Chaos Orb, and Falling Star.
Planar Conquest now features a new plane - Forgotten Realms, based on the AFR and AFC sets. Planar Conquest now features a new plane - Forgotten Realms, based on the AFR and AFC sets.
Several planes are now available in Planar Conquest in their Classic form, as originally intended by the author, without support for the newer sets and with the original events not modified with newer cards. These planes are available in addition to the contemporary versions of the planes and are marked as "Classic". Several planes are now available in Planar Conquest in their Classic form, as originally intended by the author, without support for the newer sets and with the original events not modified with newer cards. These planes are available in addition to the contemporary versions of the planes and are marked as "Classic".

View File

@@ -22,7 +22,7 @@ leriomaggio
Luke Luke
Marek14 Marek14
Marvel Marvel
mcrawford620 mcrawford
medusa medusa
Meerkov Meerkov
Monkey Gland Sauce Monkey Gland Sauce

View File

@@ -99,3 +99,4 @@ Strixhaven: School of Mages, 3/6/STX, STX
Modern Horizons 2, 3/6/MH2, MH2 Modern Horizons 2, 3/6/MH2, MH2
Adventures in the Forgotten Realms, 3/6/AFR, AFR Adventures in the Forgotten Realms, 3/6/AFR, AFR
Innistrad: Midnight Hunt, 3/6/MID, MID Innistrad: Midnight Hunt, 3/6/MID, MID
Innistrad: Crimson Vow, 3/6/VOW, VOW

View File

@@ -2,9 +2,7 @@ Name:Akoum Flameseeker
ManaCost:2 R ManaCost:2 R
Types:Creature Human Shaman Ally Types:Creature Human Shaman Ally
PT:3/2 PT:3/2
A:AB$ Discard | Cost$ T tapXType<1/Ally> | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw | PrecostDesc$ Cohort — | SpellDescription$ Discard a card. If you do, draw a card. A:AB$ Draw | Cost$ T tapXType<1/Ally> | NumCards$ 1 | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ You | PrecostDesc$ Cohort — | SpellDescription$ Discard a card. If you do, draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:All AI:RemoveDeck:All
DeckHints:Type$Ally DeckHints:Type$Ally
SVar:Picture:http://www.wizards.com/global/images/magic/general/akoum_flameseeker.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/akoum_flameseeker.jpg

View File

@@ -4,6 +4,6 @@ Types:Tribal Artifact Lhurgoyf
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards. T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.
SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ +X | NumDef$ +X SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ +X | NumDef$ +X
S:Mode$ Continuous | Affected$ Creature.Lhurgoyf+YouCtrl | AddKeyword$ Trample | Description$ Lhurgoyf creatures you control have trample. S:Mode$ Continuous | Affected$ Creature.Lhurgoyf+YouCtrl | AddKeyword$ Trample | Description$ Lhurgoyf creatures you control have trample.
SVar:X:Count$CardTypes.Graveyard SVar:X:Count$CardTypes.ValidGraveyard Card
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.\nLhurgoyf creatures you control have trample. Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.\nLhurgoyf creatures you control have trample.

View File

@@ -1,7 +1,6 @@
Name:Annihilating Fire Name:Annihilating Fire
ManaCost:1 R R ManaCost:1 R R
Types:Instant Types:Instant
A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBCleanup | SpellDescription$ CARDNAME deals 3 damage to any target. If a creature dealt damage this way would die this turn, exile it instead. A:SP$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered.Creature | SubAbility$ DBCleanup | SpellDescription$ CARDNAME deals 3 damage to any target. If a creature dealt damage this way would die this turn, exile it instead.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/annihilating_fire.jpg
Oracle:Annihilating Fire deals 3 damage to any target. If a creature dealt damage this way would die this turn, exile it instead. Oracle:Annihilating Fire deals 3 damage to any target. If a creature dealt damage this way would die this turn, exile it instead.

View File

@@ -5,7 +5,7 @@ A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell |
SVar:DelTrigSlowtrip:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | SubAbility$ DelTrigDrawTwo | TriggerDescription$ Draw a card. SVar:DelTrigSlowtrip:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | SubAbility$ DelTrigDrawTwo | TriggerDescription$ Draw a card.
SVar:DrawSlowtrip:DB$ Draw | NumCards$ 1 | Defined$ You SVar:DrawSlowtrip:DB$ Draw | NumCards$ 1 | Defined$ You
SVar:DelTrigDrawTwo:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | RememberObjects$ RememberedController | Execute$ DrawTwo | TriggerDescription$ Draw up to two cards. | SubAbility$ DBCleanup SVar:DelTrigDrawTwo:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | RememberObjects$ RememberedController | Execute$ DrawTwo | TriggerDescription$ Draw up to two cards. | SubAbility$ DBCleanup
SVar:DrawTwo:DB$ Draw | NumCards$ 2 | Defined$ DelayTriggerRemembered | Upto$ True SVar:DrawTwo:DB$ Draw | NumCards$ 2 | Defined$ DelayTriggerRemembered | Upto$ True | AILogic$ OptionalDraw
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/arcane_denial.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/arcane_denial.jpg
Oracle:Counter target spell. Its controller may draw up to two cards at the beginning of the next turn's upkeep.\nYou draw a card at the beginning of the next turn's upkeep. Oracle:Counter target spell. Its controller may draw up to two cards at the beginning of the next turn's upkeep.\nYou draw a card at the beginning of the next turn's upkeep.

View File

@@ -3,5 +3,5 @@ ManaCost:5 U U
Types:Sorcery Types:Sorcery
A:SP$ RollDice | Amount$ 2 | Sides$ 8 | ChosenSVar$ X | OtherSVar$ Y | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Roll two d8 and choose one result. Draw cards equal to that result. Then you may cast an instant or sorcery spell with mana value less than or equal to the other result from your hand without paying its mana cost. A:SP$ RollDice | Amount$ 2 | Sides$ 8 | ChosenSVar$ X | OtherSVar$ Y | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Roll two d8 and choose one result. Draw cards equal to that result. Then you may cast an instant or sorcery spell with mana value less than or equal to the other result from your hand without paying its mana cost.
SVar:DBDraw:DB$ Draw | NumCards$ X | SubAbility$ DBPlay | StackDescription$ None SVar:DBDraw:DB$ Draw | NumCards$ X | SubAbility$ DBPlay | StackDescription$ None
SVar:DBPlay:DB$ Play | Valid$ Card | ValidSA$ Instant.cmcLEY,Sorcery.cmcLEY | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True SVar:DBPlay:DB$ Play | Valid$ Card.YouOwn | ValidSA$ Instant.cmcLEY,Sorcery.cmcLEY | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True
Oracle:Roll two d8 and choose one result. Draw cards equal to that result. Then you may cast an instant or sorcery spell with mana value less than or equal to the other result from your hand without paying its mana cost. Oracle:Roll two d8 and choose one result. Draw cards equal to that result. Then you may cast an instant or sorcery spell with mana value less than or equal to the other result from your hand without paying its mana cost.

View File

@@ -3,7 +3,7 @@ ManaCost:4 G G
Types:Legendary Planeswalker Arlinn Types:Legendary Planeswalker Arlinn
Loyalty:7 Loyalty:7
K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Creature.Wolf+YouCtrl,Creature.Werewolf+YouCtrl K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Creature.Wolf+YouCtrl,Creature.Werewolf+YouCtrl
SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$Each creature you control that's a Wolf or a Werewolf enters the battlefield with an additional +1/+1 counter on it. SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Each creature you control that's a Wolf or a Werewolf enters the battlefield with an additional +1/+1 counter on it.
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ g_2_2_wolf | TokenOwner$ You | LegacyImage$ g 2 2 wolf war | SpellDescription$ Create a 2/2 green Wolf creature token. A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ g_2_2_wolf | TokenOwner$ You | LegacyImage$ g 2 2 wolf war | SpellDescription$ Create a 2/2 green Wolf creature token.
DeckHints:Type$Wolf & Type$Werewolf DeckHints:Type$Wolf & Type$Werewolf

View File

@@ -5,7 +5,7 @@ PT:6/6
R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ ExileTop | Description$ Binding Contract — If you would draw a card, exile the top card of your library face down instead. R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ ExileTop | Description$ Binding Contract — If you would draw a card, exile the top card of your library face down instead.
SVar:ExileTop:DB$ ChangeZone | Defined$ TopOfLibrary | Origin$ Library | Destination$ Exile | ExileFaceDown$ True SVar:ExileTop:DB$ ChangeZone | Defined$ TopOfLibrary | Origin$ Library | Destination$ Exile | ExileFaceDown$ True
A:AB$ Draw | Cost$ B B B | NumCards$ 7 | SpellDescription$ Draw seven cards. A:AB$ Draw | Cost$ B B B | NumCards$ 7 | SpellDescription$ Draw seven cards.
A:AB$ ChangeZoneAll | Cost$ B | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | RememberChanged$ True | SubAbility$ DBLoseLife | SpellDescription$ Return all cards exiled with CARDNAME to their owners hand and you lose that much life. A:AB$ ChangeZoneAll | Cost$ B | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | RememberChanged$ True | SubAbility$ DBLoseLife | SpellDescription$ Return all cards exiled with CARDNAME to their owner's hand and you lose that much life.
SVar:DBLoseLife:DB$ LoseLife | Defined$ You | LifeAmount$ X | SubAbility$ DBCleanup SVar:DBLoseLife:DB$ LoseLife | Defined$ You | LifeAmount$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Amount SVar:X:Remembered$Amount

View File

@@ -4,7 +4,7 @@ Types:Enchantment Saga
K:Saga:3:TrigToken1,TrigToken2,DBCopy K:Saga:3:TrigToken1,TrigToken2,DBCopy
SVar:TrigToken1:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 white Human Warrior creature token. SVar:TrigToken1:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 white Human Warrior creature token.
SVar:TrigToken2:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 green Elf Warrior creature token. SVar:TrigToken2:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 green Elf Warrior creature token.
SVar:DBCopy:DB$ CopyPermanent | Choices$ Artifact.token+YouCtrl,Creature.token+YouCtrl | WithDifferentNames$ True | SpellDescription$ Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token thats a copy of it. SVar:DBCopy:DB$ CopyPermanent | Choices$ Artifact.token+YouCtrl,Creature.token+YouCtrl | WithDifferentNames$ True | SpellDescription$ Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token that's a copy of it.
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create a 1/1 white Human Warrior creature token.\nII — Create a 1/1 green Elf Warrior creature token.\nIII — Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token that's a copy of it. Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create a 1/1 white Human Warrior creature token.\nII — Create a 1/1 green Elf Warrior creature token.\nIII — Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token that's a copy of it.

View File

@@ -3,7 +3,7 @@ ManaCost:2 B R
Types:Legendary Creature Imp Types:Legendary Creature Imp
PT:4/3 PT:4/3
K:Flying K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | TriggerZone$ Battlefield | Execute$ TrigControl | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player gains control of target permanent you control. Then each player loses life and discards cards equal to the number of permanents they control but don't own. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigControl | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player gains control of target permanent you control. Then each player loses life and discards cards equal to the number of permanents they control but don't own.
SVar:TrigControl:DB$ GainControl | ValidTgts$ Permanent.YouCtrl | TargetMin$ 1 | TgtPrompt$ Choose a permanent you control for damaged player to gain control of | NewController$ TriggeredTarget | SubAbility$ DBRepeatEach SVar:TrigControl:DB$ GainControl | ValidTgts$ Permanent.YouCtrl | TargetMin$ 1 | TgtPrompt$ Choose a permanent you control for damaged player to gain control of | NewController$ TriggeredTarget | SubAbility$ DBRepeatEach
SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBLoseLife SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBLoseLife
SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X | SubAbility$ DBDiscard SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X | SubAbility$ DBDiscard

View File

@@ -6,5 +6,5 @@ K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may exile target card from a graveyard. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may exile target card from a graveyard.
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard
T:Mode$ Surveil | ValidPlayer$ You | Execute$ TrigReturn | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | PresentPlayer$ You | TriggerDescription$ Whenever you surveil, if CARDNAME is in your graveyard, you may pay 3 life. If you do, return CARDNAME to your hand. T:Mode$ Surveil | ValidPlayer$ You | Execute$ TrigReturn | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | PresentPlayer$ You | TriggerDescription$ Whenever you surveil, if CARDNAME is in your graveyard, you may pay 3 life. If you do, return CARDNAME to your hand.
SVar:TrigReturn:AB$ChangeZone | Cost$ PayLife<3> | Defined$ Self | Origin$ Graveyard | Destination$ Hand SVar:TrigReturn:AB$ ChangeZone | Cost$ PayLife<3> | Defined$ Self | Origin$ Graveyard | Destination$ Hand
Oracle:Lifelink\nWhen Blood Operative enters the battlefield, you may exile target card from a graveyard.\nWhenever you surveil, if Blood Operative is in your graveyard, you may pay 3 life. If you do, return Blood Operative to your hand. Oracle:Lifelink\nWhen Blood Operative enters the battlefield, you may exile target card from a graveyard.\nWhenever you surveil, if Blood Operative is in your graveyard, you may pay 3 life. If you do, return Blood Operative to your hand.

View File

@@ -3,6 +3,6 @@ ManaCost:2 W
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant permanent K:Enchant permanent
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Permanent | AILogic$ Curse A:SP$ Attach | Cost$ 2 W | ValidTgts$ Permanent | AILogic$ Curse
S:Mode$ Continuous | Affected$ Permanent.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. & CARDNAME can't crew Vehicles. | Description$ Enchanted permanent cant attack, block, or crew Vehicles, and its activated abilities cant be activated unless theyre mana abilities. S:Mode$ Continuous | Affected$ Permanent.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. & CARDNAME can't crew Vehicles. | Description$ Enchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities.
S:Mode$ CantBeActivated | ValidCard$ Card.EnchantedBy | NonMana$ True S:Mode$ CantBeActivated | ValidCard$ Card.EnchantedBy | NonMana$ True
Oracle:Enchant permanent\nEnchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities. Oracle:Enchant permanent\nEnchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities.

View File

@@ -8,4 +8,4 @@ SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB
SVar:ETB:DB$ InternalEtbReplacement SVar:ETB:DB$ InternalEtbReplacement
T:Mode$ DayTimeChanges | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, CARDNAME deals 1 damage to each opponent. T:Mode$ DayTimeChanges | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, CARDNAME deals 1 damage to each opponent.
SVar:TrigDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1 SVar:TrigDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1
Oracle:Menace (This creature cant be blocked except by two or more creatures.)\nIf it's neither day nor night, it becomes day as Brimstone Vandal enters the battlefield.\nWhenever day becomes night or night becomes day, Brimstone Vandal deals 1 damage to each opponent. Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nIf it's neither day nor night, it becomes day as Brimstone Vandal enters the battlefield.\nWhenever day becomes night or night becomes day, Brimstone Vandal deals 1 damage to each opponent.

View File

@@ -4,6 +4,6 @@ Types:Creature Bringer
PT:5/5 PT:5/5
K:Trample K:Trample
SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost. SVar:AltCost:Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, you may draw two cards. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, you may draw two cards.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2 SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2 | OptionalDecider$ You
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may draw two cards. Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost.\nTrample\nAt the beginning of your upkeep, you may draw two cards.

View File

@@ -2,8 +2,7 @@ Name:Brutal Expulsion
ManaCost:2 U R ManaCost:2 U R
Types:Instant Types:Instant
K:Devoid K:Devoid
A:SP$ Charm | Cost$ 2 U R | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBReturn,DBDmg A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBReturn,DBDmg
SVar:DBReturn:DB$ ChangeZone | ValidTgts$ Creature,Card.inZoneStack | TgtZone$ Stack,Battlefield | Origin$ Battlefield,Stack | Fizzle$ True | Destination$ Hand | SpellDescription$ Return target spell or creature to its owner's hand. SVar:DBReturn:DB$ ChangeZone | ValidTgts$ Creature,Card.inZoneStack | TgtZone$ Stack,Battlefield | Origin$ Battlefield,Stack | Fizzle$ True | Destination$ Hand | SpellDescription$ Return target spell or creature to its owner's hand.
SVar:DBDmg:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ 2 | ReplaceDyingDefined$ Targeted | SpellDescription$ CARDNAME deals 2 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead.AI:RemoveDeck:All SVar:DBDmg:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | NumDmg$ 2 | ReplaceDyingDefined$ Targeted | SpellDescription$ CARDNAME deals 2 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead.
SVar:Picture:http://www.wizards.com/global/images/magic/general/brutal_expulsion.jpg
Oracle:Devoid (This card has no color.)\nChoose one or both —\n• Return target spell or creature to its owner's hand.\n• Brutal Expulsion deals 2 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead. Oracle:Devoid (This card has no color.)\nChoose one or both —\n• Return target spell or creature to its owner's hand.\n• Brutal Expulsion deals 2 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead.

View File

@@ -1,9 +1,8 @@
Name:Burn from Within Name:Burn from Within
ManaCost:X R ManaCost:X R
Types:Sorcery Types:Sorcery
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | RememberDamaged$ True | ReplaceDyingDefined$ Remembered.Creature | SubAbility$ DBDebuff | SpellDescription$ CARDNAME deals X damage to any target. If a creature is dealt damage this way, it loses indestructible until end of turn. If that creature would die this turn, exile it instead. A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | RememberDamaged$ True | ReplaceDyingDefined$ Remembered.Creature | SubAbility$ DBDebuff | StackDescription$ CARDNAME deals X damage to {p:Targeted}{c:Targeted}. If a creature is dealt damage this way, it loses indestructible until end of turn. If that creature would die this turn, exile it instead. | SpellDescription$ CARDNAME deals X damage to any target. If a creature is dealt damage this way, it loses indestructible until end of turn. If that creature would die this turn, exile it instead.
SVar:DBDebuff:DB$ Debuff | Defined$ Remembered.Creature | Keywords$ Indestructible | SubAbility$ DBCleanup SVar:DBDebuff:DB$ Debuff | Defined$ Remembered.Creature | Keywords$ Indestructible | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid SVar:X:Count$xPaid
SVar:Picture:http://www.wizards.com/global/images/magic/general/burn_from_within.jpg
Oracle:Burn from Within deals X damage to any target. If a creature is dealt damage this way, it loses indestructible until end of turn. If that creature would die this turn, exile it instead. Oracle:Burn from Within deals X damage to any target. If a creature is dealt damage this way, it loses indestructible until end of turn. If that creature would die this turn, exile it instead.

View File

@@ -1,6 +1,6 @@
Name:Carbonize Name:Carbonize
ManaCost:2 R ManaCost:2 R
Types:Instant Types:Instant
A:SP$ DealDamage | Cost$ 2 R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SubAbility$ DB | ReplaceDyingDefined$ Targeted.Creature | SpellDescription$ CARDNAME deals 3 damage to any target. If it's a creature, it can't be regenerated this turn, and if it would die this turn, exile it instead. A:SP$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SubAbility$ DB | ReplaceDyingDefined$ Targeted.Creature | SpellDescription$ CARDNAME deals 3 damage to any target. If it's a creature, it can't be regenerated this turn, and if it would die this turn, exile it instead.
SVar:DB:DB$ Pump | KW$ HIDDEN CARDNAME can't be regenerated. | Defined$ Targeted.Creature SVar:DB:DB$ Pump | KW$ HIDDEN CARDNAME can't be regenerated. | Defined$ Targeted.Creature | StackDescription$ None
Oracle:Carbonize deals 3 damage to any target. If it's a creature, it can't be regenerated this turn, and if it would die this turn, exile it instead. Oracle:Carbonize deals 3 damage to any target. If it's a creature, it can't be regenerated this turn, and if it would die this turn, exile it instead.

View File

@@ -2,9 +2,8 @@ Name:Champion of Wits
ManaCost:2 U ManaCost:2 U
Types:Creature Naga Wizard Types:Creature Naga Wizard
PT:2/1 PT:2/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may draw cards equal to its power. If you do, discard two cards. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, you may draw cards equal to its power. If you do, discard two cards.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X | SubAbility$ DBDiscard SVar:TrigDraw:AB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 2 | Cost$ Draw<X/You>
SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 2
K:Eternalize:5 U U K:Eternalize:5 U U
SVar:X:Count$CardPower SVar:X:Count$CardPower
SVar:Picture:http://www.wizards.com/global/images/magic/general/champion_of_wits.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/champion_of_wits.jpg

View File

@@ -1,9 +1,7 @@
Name:Chandra's Defeat Name:Chandra's Defeat
ManaCost:R ManaCost:R
Types:Instant Types:Instant
A:SP$ DealDamage | Cost$ R | ValidTgts$ Creature.Red,Planeswalker.Red | TgtPrompt$ Select target red creature or red planeswalker | NumDmg$ 5 | SubAbility$ DBDiscard | SpellDescription$ CARDNAME deals 5 damage to target red creature or red planeswalker. If that permanent is a Chandra planeswalker, you may discard a card. If you do, draw a card. A:SP$ DealDamage | Cost$ R | ValidTgts$ Creature.Red,Planeswalker.Red | TgtPrompt$ Select target red creature or red planeswalker | NumDmg$ 5 | SubAbility$ DBDraw | SpellDescription$ CARDNAME deals 5 damage to target red creature or red planeswalker. If that permanent is a Chandra planeswalker, you may discard a card. If you do, draw a card.
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw | ConditionDefined$ Targeted | ConditionPresent$ Planeswalker.Chandra | Optional$ True SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Targeted | ConditionPresent$ Planeswalker.Chandra | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ You
SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/chandras_defeat.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/chandras_defeat.jpg
Oracle:Chandra's Defeat deals 5 damage to target red creature or red planeswalker. If that permanent is a Chandra planeswalker, you may discard a card. If you do, draw a card. Oracle:Chandra's Defeat deals 5 damage to target red creature or red planeswalker. If that permanent is a Chandra planeswalker, you may discard a card. If you do, draw a card.

View File

@@ -4,7 +4,7 @@ Types:Creature Lhurgoyf Imp
PT:*/1+* PT:*/1+*
K:Flying K:Flying
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ Y | Description$ CARDNAME's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ Y | Description$ CARDNAME's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1.
SVar:X:Count$CardTypes.Graveyard SVar:X:Count$CardTypes.ValidGraveyard Card
SVar:Y:SVar$X/Plus.1 SVar:Y:SVar$X/Plus.1
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, target opponent puts a card from their hand on top of their library. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, target opponent puts a card from their hand on top of their library.
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | Chooser$ Opponent | Mandatory$ True | IsCurse$ True SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | Chooser$ Opponent | Mandatory$ True | IsCurse$ True

View File

@@ -3,8 +3,8 @@ ManaCost:1 GU GU
Types:Creature Merfolk Rogue Types:Creature Merfolk Rogue
PT:1/1 PT:1/1
K:Islandwalk K:Islandwalk
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigDraw | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may draw that many cards. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigDraw | CombatDamage$ True | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may draw that many cards.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X | OptionalDecider$ You
SVar:X:TriggerCount$DamageAmount SVar:X:TriggerCount$DamageAmount
SVar:Picture:http://www.wizards.com/global/images/magic/general/cold_eyed_selkie.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/cold_eyed_selkie.jpg
Oracle:Islandwalk (This creature can't be blocked as long as defending player controls an Island.)\nWhenever Cold-Eyed Selkie deals combat damage to a player, you may draw that many cards. Oracle:Islandwalk (This creature can't be blocked as long as defending player controls an Island.)\nWhenever Cold-Eyed Selkie deals combat damage to a player, you may draw that many cards.

View File

@@ -3,7 +3,7 @@ ManaCost:4 U U
Types:Creature Sphinx Types:Creature Sphinx
PT:4/6 PT:4/6
K:Flying K:Flying
T:Mode$ Drawn | ValidCard$ Card.OppOwn | TriggerZones$ Battlefield | Execute$ TrigDraw | OptionalDecider$ You | TriggerDescription$ Whenever an opponent draws a card, you may draw two cards. T:Mode$ Drawn | ValidCard$ Card.OppOwn | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent draws a card, you may draw two cards.
SVar:TrigDraw:DB$ Draw | NumCards$ 2 SVar:TrigDraw:DB$ Draw | NumCards$ 2 | OptionalDecider$ You
SVar:Picture:http://www.wizards.com/global/images/magic/general/consecrated_sphinx.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/consecrated_sphinx.jpg
Oracle:Flying\nWhenever an opponent draws a card, you may draw two cards. Oracle:Flying\nWhenever an opponent draws a card, you may draw two cards.

View File

@@ -4,6 +4,6 @@ Types:Creature Whale
PT:*/* PT:*/*
K:This spell can't be countered. K:This spell can't be countered.
K:Shroud K:Shroud
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of turns youve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.) S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of turns you've taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
SVar:X:Count$YourTurns SVar:X:Count$YourTurns
Oracle:This spell can't be countered.\nShroud\nControl Win Condition's power and toughness are each equal to the number of turns you've taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.) Oracle:This spell can't be countered.\nShroud\nControl Win Condition's power and toughness are each equal to the number of turns you've taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)

View File

@@ -1,7 +1,7 @@
Name:Corpse Cobble Name:Corpse Cobble
ManaCost:U B ManaCost:U B
Types:Instant Types:Instant
A:SP$ Token | Cost$ U B Sac<X/Creature/creature> | TokenScript$ ub_x_x_zombie_menace | TokenPower$ Y | TokenToughness$ Y | SpellDescription$ Create an X/X blue and black Zombie creature token with menace, where X is the total power of the sacrificed creatures. A:SP$ Token | Cost$ U B Sac<X/Creature/creature> | TokenScript$ ub_x_x_zombie_menace | AnnounceTitle$ number of creatures to sacrifice | TokenPower$ Y | TokenToughness$ Y | SpellDescription$ Create an X/X blue and black Zombie creature token with menace, where X is the total power of the sacrificed creatures.
K:Flashback:3 U B K:Flashback:3 U B
SVar:X:Count$xPaid SVar:X:Count$xPaid
SVar:Y:Sacrificed$CardPower SVar:Y:Sacrificed$CardPower

View File

@@ -4,7 +4,7 @@ Types:Instant
A:SP$ Effect | ReplacementEffects$ ReplaceGrave | SpellDescription$ If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step. A:SP$ Effect | ReplacementEffects$ ReplaceGrave | SpellDescription$ If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step.
SVar:ReplaceGrave:Event$ Moved | ValidCard$ Permanent.YouCtrl | Origin$ Battlefield | Destination$ Graveyard | ReplaceWith$ Exile | ActiveZone$ Command | Description$ If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step. SVar:ReplaceGrave:Event$ Moved | ValidCard$ Permanent.YouCtrl | Origin$ Battlefield | Destination$ Graveyard | ReplaceWith$ Exile | ActiveZone$ Command | Description$ If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step.
SVar:Exile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ All | Destination$ Exile | SubAbility$ DBDelayTrigger SVar:Exile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ All | Destination$ Exile | SubAbility$ DBDelayTrigger
SVar:DBDelayTrigger:DB$ DelayedTrigger | RememberObjects$ ReplacedCard | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ Return it to the battlefield under its owners control at the beginning of the next end step. SVar:DBDelayTrigger:DB$ DelayedTrigger | RememberObjects$ ReplacedCard | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ Return it to the battlefield under its owner's control at the beginning of the next end step.
SVar:TrigReturn:DB$ ChangeZone | Defined$ DelayTriggerRemembered | Destination$ Battlefield SVar:TrigReturn:DB$ ChangeZone | Defined$ DelayTriggerRemembered | Destination$ Battlefield
K:Foretell:1 W K:Foretell:1 W
Oracle:If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step.\nForetell {1}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) Oracle:If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step.\nForetell {1}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.)

View File

@@ -5,7 +5,7 @@ PT:3/3
K:Flash K:Flash
K:Flying K:Flying
S:Mode$ ReduceCost | ValidSpell$ Static.Foretelling | Activator$ You | Amount$ 1 | Description$ Foretelling cards from your hand costs {1} less and can be done on any player's turn. S:Mode$ ReduceCost | ValidSpell$ Static.Foretelling | Activator$ You | Amount$ 1 | Description$ Foretelling cards from your hand costs {1} less and can be done on any player's turn.
S:Mode$ Continuous | Affected$ You | AddKeyword$ Foretell on any players turn | Secondary$ True | Description$ Foretelling cards from your hand on any players turn. S:Mode$ Continuous | Affected$ You | AddKeyword$ Foretell on any player's turn | Secondary$ True | Description$ Foretelling cards from your hand on any player's turn.
K:Foretell:2 U K:Foretell:2 U
Oracle:Flash\nFlying\nForetelling cards from your hand costs {1} less and can be done on any player's turn.\nForetell {2}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) Oracle:Flash\nFlying\nForetelling cards from your hand costs {1} less and can be done on any player's turn.\nForetell {2}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.)

View File

@@ -4,9 +4,7 @@ Types:Enchantment Aura Curse
K:Enchant player K:Enchant player
A:SP$ Attach | Cost$ 2 R | ValidTgts$ Player | AILogic$ Curse A:SP$ Attach | Cost$ 2 R | ValidTgts$ Player | AILogic$ Curse
T:Mode$ AttackersDeclared | Execute$ TrigDiscard | TriggerZones$ Battlefield | AttackedTarget$ Player.EnchantedBy | OptionalDecider$ TriggeredAttackingPlayer | TriggerDescription$ Whenever a player attacks enchanted player with one or more creatures, that attacking player may discard a card. If the player does, they draw a card. T:Mode$ AttackersDeclared | Execute$ TrigDiscard | TriggerZones$ Battlefield | AttackedTarget$ Player.EnchantedBy | OptionalDecider$ TriggeredAttackingPlayer | TriggerDescription$ Whenever a player attacks enchanted player with one or more creatures, that attacking player may discard a card. If the player does, they draw a card.
SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBDraw | RememberDiscarded$ True SVar:TrigDiscard:DB$ Draw | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ TriggeredAttackingPlayer
SVar:DBDraw:DB$ Draw | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/curse_of_chaos.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/curse_of_chaos.jpg
Oracle:Enchant player\nWhenever a player attacks enchanted player with one or more creatures, that attacking player may discard a card. If the player does, they draw a card. Oracle:Enchant player\nWhenever a player attacks enchanted player with one or more creatures, that attacking player may discard a card. If the player does, they draw a card.

View File

@@ -3,6 +3,6 @@ ManaCost:6 U
Types:Enchantment Aura Curse Types:Enchantment Aura Curse
K:Enchant player K:Enchant player
A:SP$ Attach | ValidTgts$ Player | TgtPrompt$ Select player to curse | AILogic$ Curse A:SP$ Attach | ValidTgts$ Player | TgtPrompt$ Select player to curse | AILogic$ Curse
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedBy | TriggerZone$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card on the battlefield under your control. That player puts the rest of the revealed cards into their graveyard. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card on the battlefield under your control. That player puts the rest of the revealed cards into their graveyard.
SVar:TrigDig:DB$ DigUntil | Defined$ Player.EnchantedBy | Valid$ Creature | FoundDestination$ Battlefield | RevealedDestination$ Graveyard | GainControl$ True SVar:TrigDig:DB$ DigUntil | Defined$ Player.EnchantedBy | Valid$ Creature | FoundDestination$ Battlefield | RevealedDestination$ Graveyard | GainControl$ True
Oracle:Enchant player\nAt the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card on the battlefield under your control. That player puts the rest of the revealed cards into their graveyard. Oracle:Enchant player\nAt the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card on the battlefield under your control. That player puts the rest of the revealed cards into their graveyard.

View File

@@ -3,9 +3,7 @@ ManaCost:1 B R
Types:Legendary Planeswalker Daretti Types:Legendary Planeswalker Daretti
Loyalty:3 Loyalty:3
A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ c_1_1_a_construct_defender | TokenOwner$ You | LegacyImage$ c 1 1 a construct defender cn2 | SpellDescription$ Create a 1/1 colorless Construct artifact creature token with defender. A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ c_1_1_a_construct_defender | TokenOwner$ You | LegacyImage$ c 1 1 a construct defender cn2 | SpellDescription$ Create a 1/1 colorless Construct artifact creature token with defender.
A:AB$ Sacrifice | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | Defined$ You | Amount$ 1 | SacValid$ Artifact | RememberSacrificed$ True | Optional$ True | SubAbility$ DBDestroy | SpellDescription$ You may sacrifice an artifact. If you do, destroy target artifact or creature. | StackDescription$ SpellDescription A:AB$ Destroy | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | UnlessCost$ Sac<1/Artifact> | UnlessSwitched$ True | UnlessPayer$ You | SpellDescription$ You may sacrifice an artifact. If you do, destroy target artifact or creature. | StackDescription$ SpellDescription
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ CopyPermanent | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Artifact | TgtZone$ Battlefield,Graveyard | TgtPrompt$ Select an artifact in graveyard or the battlefield | NumCopies$ 3 | SpellDescription$ Choose target artifact card in a graveyard or artifact on the battlefield. Create three tokens that are copies of it. A:AB$ CopyPermanent | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Artifact | TgtZone$ Battlefield,Graveyard | TgtPrompt$ Select an artifact in graveyard or the battlefield | NumCopies$ 3 | SpellDescription$ Choose target artifact card in a graveyard or artifact on the battlefield. Create three tokens that are copies of it.
SVar:Picture:http://www.wizards.com/global/images/magic/general/daretti_ingenious_iconoclast.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/daretti_ingenious_iconoclast.jpg
Oracle:[+1]: Create a 1/1 colorless Construct artifact creature token with defender.\n[1]: You may sacrifice an artifact. If you do, destroy target artifact or creature.\n[6]: Choose target artifact card in a graveyard or artifact on the battlefield. Create three tokens that are copies of it. Oracle:[+1]: Create a 1/1 colorless Construct artifact creature token with defender.\n[1]: You may sacrifice an artifact. If you do, destroy target artifact or creature.\n[6]: Choose target artifact card in a graveyard or artifact on the battlefield. Create three tokens that are copies of it.

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