mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 02:08:00 +00:00
Merge branch 'master' of https://git.cardforge.org/core-developers/forge into adventure
This commit is contained in:
@@ -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)
|
||||
|
||||
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||
Discord channel [here](https://discord.gg/fWfNgCUNRq)
|
||||
|
||||
## Requirements / Tools
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<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>
|
||||
<dontWrapJar>true</dontWrapJar>
|
||||
<errTitle>forge</errTitle>
|
||||
@@ -66,6 +66,60 @@
|
||||
<opt>-Dfile.encoding=UTF-8</opt>
|
||||
</opts>
|
||||
</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>
|
||||
<fileVersion>
|
||||
1.0.0.0
|
||||
@@ -87,6 +141,7 @@
|
||||
</versionInfo>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!--extra-->
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
@@ -220,7 +275,7 @@
|
||||
<dependency>
|
||||
<groupId>forge</groupId>
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -39,6 +41,7 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.trigger.Trigger;
|
||||
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
|
||||
/**
|
||||
* <p>
|
||||
@@ -725,8 +757,9 @@ public class AiBlockController {
|
||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
// TODO - Instead of filtering out rampage-like and similar triggers, make the AI properly count P/T and
|
||||
// reinforce when actually possible without losing material.
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(changesPTWhenBlocked(true)));
|
||||
|
||||
for (final Card attacker : tramplingAttackers) {
|
||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
|
||||
@@ -755,8 +788,9 @@ public class AiBlockController {
|
||||
List<Card> blockers;
|
||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||
// the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
// TODO - Instead of filtering out rampage-like and similar triggers, make the AI properly count P/T and
|
||||
// reinforce when actually possible without losing material.
|
||||
targetAttackers = CardLists.filter(targetAttackers, Predicates.not(changesPTWhenBlocked(false)));
|
||||
|
||||
for (final Card attacker : targetAttackers) {
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
|
||||
@@ -1929,9 +1929,13 @@ public class AiController {
|
||||
int cardsInPlay = player.getCardsIn(ZoneType.Battlefield).size();
|
||||
return Math.min(chosenMax, cardsInPlay);
|
||||
} 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 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);
|
||||
} else if ("RepeatDraw".equals(logic)) {
|
||||
int remaining = player.getMaxHandSize() - player.getCardsIn(ZoneType.Hand).size()
|
||||
|
||||
@@ -145,13 +145,20 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostDraw cost) {
|
||||
if (!cost.canPay(ability, player)) {
|
||||
return null;
|
||||
}
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
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
|
||||
|
||||
@@ -1044,8 +1044,7 @@ public class ComputerUtil {
|
||||
if (ai.getManaPool().willManaBeLostAtEndOfPhase()) {
|
||||
boolean canUseToPayCost = false;
|
||||
for (byte color : MagicColor.WUBRGC) {
|
||||
if (ai.getManaPool().getAmountOfColor(color) > 0
|
||||
&& ((card.getManaCost().getColorProfile() & color) == color)) {
|
||||
if (ai.getManaPool().getAmountOfColor(color) > 0 && card.getManaCost().canBePaidWithAvailable(color)) {
|
||||
canUseToPayCost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1326,7 +1326,7 @@ public class ComputerUtilCard {
|
||||
// will the creature attack (only relevant for sorcery speed)?
|
||||
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& phase.isPlayerTurn(ai)
|
||||
&& SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred
|
||||
&& (SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred)
|
||||
&& power > 0
|
||||
&& doesCreatureAttackAI(ai, c)) {
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
@@ -590,7 +591,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
@@ -662,7 +663,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
} else if ("RiskFactor".equals(aiLogic)) {
|
||||
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;
|
||||
}
|
||||
} else if ("MorePowerful".equals(aiLogic)) {
|
||||
|
||||
@@ -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
|
||||
public static class ForceOfWill {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
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.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
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.ai.*;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
@@ -35,12 +15,14 @@ import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -50,8 +32,12 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ChangeZoneAi extends SpellAbilityAi {
|
||||
/*
|
||||
@@ -1211,7 +1197,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (choice == null) { // Could not find a creature.
|
||||
if (ai.getLife() <= 5) { // Desperate?
|
||||
// Get something AI can cast soon.
|
||||
System.out.println("5 Life or less, trying to find something castable.");
|
||||
CardLists.sortByCmcDesc(nonLands);
|
||||
for (Card potentialCard : nonLands) {
|
||||
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) {
|
||||
@@ -1221,7 +1206,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// Get the best card in there.
|
||||
System.out.println("No creature and lots of life, finding something good.");
|
||||
choice = ComputerUtilCard.getBestAI(nonLands);
|
||||
}
|
||||
}
|
||||
@@ -1487,7 +1471,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (choice == null) { // Could not find a creature.
|
||||
if (ai.getLife() <= 5) { // Desperate?
|
||||
// Get something AI can cast soon.
|
||||
System.out.println("5 Life or less, trying to find something castable.");
|
||||
CardLists.sortByCmcDesc(nonLands);
|
||||
for (Card potentialCard : nonLands) {
|
||||
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), ai)) {
|
||||
@@ -1497,7 +1480,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// Get the best card in there.
|
||||
System.out.println("No creature and lots of life, finding something good.");
|
||||
choice = ComputerUtilCard.getBestAI(nonLands);
|
||||
}
|
||||
}
|
||||
@@ -1544,11 +1526,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
if ("DeathgorgeScavenger".equals(logic)) {
|
||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||
}
|
||||
if ("ExtraplanarLens".equals(logic)) {
|
||||
} else if ("ExtraplanarLens".equals(logic)) {
|
||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||
}
|
||||
if ("ExileCombatThreat".equals(logic)) {
|
||||
} else if ("ExileCombatThreat".equals(logic)) {
|
||||
return doExileCombatThreatLogic(ai, sa);
|
||||
}
|
||||
|
||||
@@ -1574,7 +1554,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return null;
|
||||
}
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
if ("NeverBounceItself".equals(logic)) {
|
||||
Card source = sa.getHostCard();
|
||||
if (fetchList.contains(source) && (fetchList.size() > 1 || !sa.getRootAbility().isMandatory())) {
|
||||
@@ -1597,6 +1577,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
multipleCardsToChoose.remove(0);
|
||||
return choice;
|
||||
}
|
||||
} else if (logic.startsWith("ExilePreference")) {
|
||||
return doExilePreferenceLogic(decider, sa, fetchList);
|
||||
}
|
||||
}
|
||||
if (fetchList.isEmpty()) {
|
||||
@@ -1680,12 +1662,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
canCastSomething = canCastSomething || ComputerUtilMana.hasEnoughManaSourcesToCast(cardInHand.getFirstSpellAbility(), decider);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (c == null) {
|
||||
System.out.println("Don't need a land or none available; trying for a creature.");
|
||||
fetchList = CardLists.getNotType(fetchList, "Land");
|
||||
// Prefer to pull a creature, generally more useful for AI.
|
||||
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 (decider.getLife() <= 5) { // Desperate?
|
||||
// Get something AI can cast soon.
|
||||
System.out.println("5 Life or less, trying to find something castable.");
|
||||
CardLists.sortByCmcDesc(fetchList);
|
||||
for (Card potentialCard : fetchList) {
|
||||
if (ComputerUtilMana.hasEnoughManaSourcesToCast(potentialCard.getFirstSpellAbility(), decider)) {
|
||||
@@ -1703,7 +1682,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// Get the best card in there.
|
||||
System.out.println("No creature and lots of life, finding something good.");
|
||||
c = ComputerUtilCard.getBestAI(fetchList);
|
||||
}
|
||||
}
|
||||
@@ -1786,7 +1764,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
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
|
||||
if (params.containsKey("Attacker")) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
}
|
||||
return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
|
||||
@@ -1794,7 +1772,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
// should not be reached
|
||||
@@ -2037,6 +2015,143 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
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) {
|
||||
// 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.
|
||||
|
||||
@@ -247,7 +247,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
@@ -257,7 +257,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
// should not be reached
|
||||
|
||||
@@ -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)) {
|
||||
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
|
||||
// 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);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
|
||||
int totalTargets = list.size();
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -2,13 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
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.ai.*;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -67,6 +61,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if ("FellTheMighty".equals(aiLogic)) {
|
||||
return SpecialCardAi.FellTheMighty.consider(ai, sa);
|
||||
}
|
||||
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,12 @@ public class DigAi extends SpellAbilityAi {
|
||||
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
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
@@ -183,7 +189,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
// an opponent choose a card from
|
||||
@@ -192,7 +198,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
// should not be reached
|
||||
|
||||
@@ -224,7 +224,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
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
|
||||
|
||||
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
@@ -266,7 +266,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// [Necrologia, Pay X Life : Draw X Cards]
|
||||
// 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);
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && (numCards > 0))) {
|
||||
while (ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && numCards > 0) {
|
||||
numCards--;
|
||||
}
|
||||
}
|
||||
@@ -310,8 +310,20 @@ public class DrawAi extends SpellAbilityAi {
|
||||
PlayerCollection opps = players.filter(PlayerPredicates.isOpponentOf(ai));
|
||||
|
||||
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
|
||||
if (oppA.cantLose()) {
|
||||
if (oppA.cantLose() || !oppA.canDraw()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -376,7 +388,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
boolean aiTarget = sa.canTarget(ai);
|
||||
// checks what the ai prevent from casting it on itself
|
||||
// if spell is not mandatory
|
||||
if (aiTarget && !ai.cantLose()) {
|
||||
if (aiTarget && !ai.cantLose() && ai.canDraw()) {
|
||||
if (numCards >= computerLibrarySize - 3) {
|
||||
if (xPaid) {
|
||||
numCards = computerLibrarySize - 1;
|
||||
@@ -436,7 +448,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// try to benefit ally
|
||||
for (Player ally : ai.getAllies()) {
|
||||
// try to select ally to help
|
||||
if (!sa.canTarget(ally)) {
|
||||
if (!sa.canTarget(ally) || !ally.canDraw()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -489,9 +501,14 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
} else if (!mandatory) {
|
||||
// ability is not targeted
|
||||
|
||||
// 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 (ai.isCardInPlay("Laboratory Maniac")) {
|
||||
return true;
|
||||
@@ -500,10 +517,6 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numCards == 0 && !drawback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((computerHandSize + numCards > computerMaxHandSize)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||
&& !sa.isTrigger()
|
||||
@@ -526,7 +539,6 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return targetAI(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
|
||||
@@ -36,16 +36,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("FellTheMighty".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)) {
|
||||
if ("MoveCounter".equals(aiLogic)) {
|
||||
final Game game = ai.getGame();
|
||||
List<Card> tgtCards = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.isTargetableBy(sa));
|
||||
@@ -70,10 +61,6 @@ public class PumpAi extends PumpAiBase {
|
||||
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
||||
} else if (aiLogic.startsWith("AristocratCounters")) {
|
||||
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")) {
|
||||
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||
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")) {
|
||||
// Donate step 1 - try to target an opponent, preferably one who does not have a donate target yet
|
||||
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)) {
|
||||
return true;
|
||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||
if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
||||
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
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"
|
||||
CardCollection exiled = new CardCollection(Iterables.filter(sa.getHostCard().getRemembered(), Card.class));
|
||||
exiled = CardLists.filter(exiled, Presets.PERMANENTS);
|
||||
CardCollection exiled = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Exile), "Permanent.nonAura+IsRemembered", ai, sa.getHostCard(), sa);
|
||||
if (ComputerUtilCard.evaluatePermanentList(exiled) > 20) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
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 Iterables.getFirst(options, null);
|
||||
@@ -325,7 +325,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
// should not be reached
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -332,6 +332,10 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
sumWeights += editionsCount;
|
||||
weightedMean += weightedFrequency;
|
||||
}
|
||||
|
||||
if (frequencyValues.isEmpty())
|
||||
return null;
|
||||
|
||||
int totalNoCards = (int)weightedMean;
|
||||
weightedMean /= sumWeights;
|
||||
|
||||
|
||||
@@ -388,6 +388,8 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
// == Get the most representative (Pivot) Edition in the Pool
|
||||
// Note: Card Art Updates (if any) will be determined based on the Pivot Edition.
|
||||
CardEdition pivotEdition = pool.getPivotCardEdition(isCardArtPreferenceLatestArt);
|
||||
if (pivotEdition == null)
|
||||
continue;
|
||||
|
||||
// == Inspect and Update the Pool
|
||||
Date releaseDatePivotEdition = pivotEdition.getDate();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -175,6 +175,10 @@ public final class GameActionUtil {
|
||||
final String keyword = inst.getOriginal();
|
||||
|
||||
if (keyword.startsWith("Disturb")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost disturbCost = new Cost(k[1], true);
|
||||
|
||||
@@ -203,6 +207,10 @@ public final class GameActionUtil {
|
||||
|
||||
alternatives.add(newSA);
|
||||
} else if (keyword.startsWith("Escape")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost escapeCost = new Cost(k[1], true);
|
||||
|
||||
@@ -224,6 +232,10 @@ public final class GameActionUtil {
|
||||
|
||||
alternatives.add(newSA);
|
||||
} else if (keyword.startsWith("Flashback")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if source has No Mana cost, and flashback doesn't have own one,
|
||||
// flashback can't work
|
||||
if (keyword.equals("Flashback") && source.getManaCost().isNoCost()) {
|
||||
|
||||
@@ -2365,6 +2365,20 @@ public class AbilityUtils {
|
||||
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
|
||||
if (sq[0].equals("TopOfLibraryCMC")) {
|
||||
int cmc = player.getCardsIn(ZoneType.Library).isEmpty() ? 0 :
|
||||
@@ -2431,6 +2445,19 @@ public class AbilityUtils {
|
||||
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")) {
|
||||
CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield),
|
||||
"Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb);
|
||||
@@ -2600,7 +2627,7 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
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")) {
|
||||
@@ -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")) {
|
||||
final Set<Integer> diffCMC = new HashSet<>();
|
||||
for (final Card card : paidList) {
|
||||
|
||||
@@ -521,7 +521,7 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ " + valid +
|
||||
"| 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;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AmassEffect extends TokenEffectBase {
|
||||
|
||||
sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter"));
|
||||
|
||||
sb.append("on an Army you control. If you don’t 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();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||
if (!p.hasKeyword("You can’t become the monarch this turn.")) {
|
||||
if (!p.hasKeyword("You can't become the monarch this turn.")) {
|
||||
p.getGame().getAction().becomeMonarch(p, set);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +137,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0;
|
||||
|
||||
if (!random) {
|
||||
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck)
|
||||
&& !sa.hasParam("Shuffle") && cards.size() >= 2) {
|
||||
if (!random && !((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) && sa.hasParam("Shuffle"))) {
|
||||
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) && cards.size() >= 2) {
|
||||
Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0);
|
||||
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.
|
||||
|
||||
@@ -63,8 +63,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
return;
|
||||
}
|
||||
|
||||
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host,
|
||||
sa.getParam("NumCopies"), sa) : 1;
|
||||
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, sa.getParam("NumCopies"), sa) : 1;
|
||||
|
||||
Player controller = null;
|
||||
if (sa.hasParam("Controller")) {
|
||||
|
||||
@@ -121,6 +121,17 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -255,6 +266,16 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
protected void internalDamageDeal(SpellAbility sa, Card sourceLKI, Card c, int dmg, CardDamageMap damageMap) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
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")) {
|
||||
c.setDamage(0);
|
||||
@@ -263,11 +284,6 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
} else {
|
||||
if (sa.hasParam("ExcessDamage") && (!sa.hasParam("ExcessDamageCondition") ||
|
||||
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);
|
||||
|
||||
@@ -276,10 +292,13 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
list.addAll(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("ExcessDamage"), sa));
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
damageMap.put(sourceLKI, list.get(0), dmg - dmgToTarget);
|
||||
damageMap.put(sourceLKI, list.get(0), excess);
|
||||
}
|
||||
} else {
|
||||
damageMap.put(sourceLKI, c, dmg);
|
||||
if (sa.hasParam("ExcessSVar")) {
|
||||
hostCard.setSVar(sa.getParam("ExcessSVar"), Integer.toString(excess));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityCantDraw;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
|
||||
@@ -40,19 +40,34 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
final Card source = sa.getHostCard();
|
||||
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 optional = sa.hasParam("OptionalDecider") || upto;
|
||||
|
||||
for (final Player p : getDefinedPlayersOrTargeted(sa)) {
|
||||
if ((tgt == null) || p.canBeTargetedBy(sa))
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card"))))
|
||||
// TODO can this be removed?
|
||||
if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
@@ -68,6 +83,7 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
source.addRemembered(c);
|
||||
}
|
||||
}
|
||||
sa.setSVar("AFNotDrawnNum_" + p.getId(), "Number$" + drawn.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,25 @@ public class LifeGainEffect extends SpellAbilityEffect {
|
||||
*/
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final int lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);
|
||||
|
||||
List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
if (tgtPlayers.isEmpty()) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,18 +100,22 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
|
||||
final boolean destroy = sa.hasParam("Destroy");
|
||||
final boolean remSacrificed = sa.hasParam("RememberSacrificed");
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
CardZoneTable table = new CardZoneTable();
|
||||
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
||||
params.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
|
||||
|
||||
if (valid.equals("Self") && game.getZoneOf(card) != null) {
|
||||
if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
|
||||
if (!optional || activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()))) {
|
||||
if (game.getAction().sacrifice(card, sa, table, params) != null) {
|
||||
if (remSacrificed) {
|
||||
card.addRemembered(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CardCollectionView choosenToSacrifice = null;
|
||||
for (final Player p : tgts) {
|
||||
@@ -147,12 +151,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Random")) {
|
||||
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;
|
||||
} else {
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
boolean isStrict = sa.hasParam("StrictAmount");
|
||||
int minTargets = isOptional ? 0 : amount;
|
||||
int minTargets = optional ? 0 : amount;
|
||||
boolean notEnoughTargets = isStrict && validTargets.size() < minTargets;
|
||||
|
||||
if (!notEnoughTargets) {
|
||||
|
||||
@@ -39,7 +39,6 @@ import forge.game.zone.ZoneType;
|
||||
public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
|
||||
protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
|
||||
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
for (final Player owner : players) {
|
||||
for (String script : tokenScripts) {
|
||||
@@ -83,7 +82,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
// support PlayerCollection for affected
|
||||
Set<Player> toRemove = Sets.newHashSet();
|
||||
for (Player p : tokenTable.rowKeySet()) {
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
|
||||
repParams.put(AbilityKey.Token, tokenTable);
|
||||
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
|
||||
|
||||
@@ -56,6 +56,7 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityCantPutCounter;
|
||||
import forge.game.staticability.StaticAbilityCantTransform;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -1364,14 +1365,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
@Override
|
||||
public final boolean canReceiveCounters(final CounterType type) {
|
||||
// CantPutCounter static abilities
|
||||
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.applyAbility("CantPutCounter", this, type)) {
|
||||
if (StaticAbilityCantPutCounter.anyCantPutCounter(this, type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1915,7 +1911,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
} else if (keyword.equals("Unblockable")) {
|
||||
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")) {
|
||||
String[] k = keyword.split(":");
|
||||
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");
|
||||
} else if (keyword.startsWith("Ripple")) {
|
||||
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")
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Disturb")) {
|
||||
|| keyword.startsWith("Disturb") || keyword.startsWith("Madness:")){
|
||||
String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]);
|
||||
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("\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")) {
|
||||
final String[] k = keyword.split(":");
|
||||
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());
|
||||
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
|
||||
// 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, ")
|
||||
&& keyword.contains(" unless you pay")) {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
|
||||
@@ -134,9 +134,21 @@ public class CardFactory {
|
||||
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)
|
||||
if (sourceSA.hasParam("CopyIsColor")) {
|
||||
ColorSet finalColors = ColorSet.getNullColor();
|
||||
ColorSet finalColors;
|
||||
final String newColor = sourceSA.getParam("CopyIsColor");
|
||||
if (newColor.equals("ChosenColor")) {
|
||||
finalColors = ColorSet.fromNames(source.getChosenColors());
|
||||
|
||||
@@ -982,7 +982,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.equals("Daybound")) {
|
||||
// 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 it’s 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";
|
||||
|
||||
Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic);
|
||||
@@ -1343,7 +1343,7 @@ public class CardFactoryUtil {
|
||||
triggers.add(gainControlTrigger);
|
||||
|
||||
// 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";
|
||||
changeZoneTrigger.setOverridingAbility(AbilityFactory.getAbility(cleanupStr, card));
|
||||
triggers.add(changeZoneTrigger);
|
||||
@@ -1528,7 +1528,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Nightbound")) {
|
||||
// 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 it’s 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";
|
||||
|
||||
Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic);
|
||||
@@ -2875,7 +2875,7 @@ public class CardFactoryUtil {
|
||||
Player activator = this.getActivatingPlayer();
|
||||
final Game game = activator.getGame();
|
||||
|
||||
if (!activator.hasKeyword("Foretell on any player’s turn") && !game.getPhaseHandler().isPlayerTurn(activator)) {
|
||||
if (!activator.hasKeyword("Foretell on any player's turn") && !game.getPhaseHandler().isPlayerTurn(activator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3480,7 +3480,7 @@ public class CardFactoryUtil {
|
||||
} else if (keyword.startsWith("Dash")) {
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
|
||||
} else if (keyword.equals("Daybound")) {
|
||||
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can’t 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")) {
|
||||
effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
|
||||
"Secondary$ True";
|
||||
@@ -3534,7 +3534,7 @@ public class CardFactoryUtil {
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " +
|
||||
" | Description$ Intimidate ( " + inst.getReminderText() + ")";
|
||||
} else if (keyword.equals("Nightbound")) {
|
||||
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can’t 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")) {
|
||||
String valid = getProtectionValid(keyword, false);
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self ";
|
||||
|
||||
@@ -20,6 +20,8 @@ import forge.game.mana.Mana;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.OptionalCost;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
@@ -380,6 +382,15 @@ public class CardProperty {
|
||||
if (!card.equals(source.getEffectSource())) {
|
||||
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) {
|
||||
if (!card.canBeSacrificedBy((SpellAbility) spellAbility)) {
|
||||
return false;
|
||||
|
||||
@@ -1428,6 +1428,49 @@ public class CardView extends GameEntityView {
|
||||
if (sa.getManaPart().isAnyMana()) {
|
||||
anyMana = true;
|
||||
}
|
||||
if (sa.getManaPart().isComboMana()) {
|
||||
String[] colorsProduced = sa.getManaPart().getComboColors().split(" ");
|
||||
//todo improve this
|
||||
for (final String s : colorsProduced) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (sa.getManaPart().getOrigProduced()) {
|
||||
case "R":
|
||||
if (!rMana) {
|
||||
@@ -1467,6 +1510,7 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isForest())
|
||||
basicLandTypes += 1;
|
||||
if (isMountain())
|
||||
|
||||
@@ -51,6 +51,8 @@ public enum CounterEnumType {
|
||||
|
||||
BLOOD("BLOOD", 255, 108, 111),
|
||||
|
||||
BLOODLINE("BLDLN", 224, 44, 44),
|
||||
|
||||
BOUNTY("BOUNT", 255, 158, 0),
|
||||
|
||||
BRIBERY("BRIBE", 172, 201, 235),
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
*/
|
||||
package forge.game.cost;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
@@ -63,11 +62,17 @@ public class CostDraw extends CostPart {
|
||||
* @param payer
|
||||
* @param source
|
||||
*/
|
||||
private List<Player> getPotentialPlayers(final Player payer, final Card source) {
|
||||
List<Player> res = new ArrayList<>();
|
||||
public PlayerCollection getPotentialPlayers(final Player payer, final SpellAbility ability) {
|
||||
PlayerCollection res = new PlayerCollection();
|
||||
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()) {
|
||||
if (p.isValid(type, payer, source, null) && p.canDraw()) {
|
||||
if (p.isValid(type, payer, source, ability) && p.canDrawAmount(c)) {
|
||||
res.add(p);
|
||||
}
|
||||
}
|
||||
@@ -83,8 +88,7 @@ public class CostDraw extends CostPart {
|
||||
*/
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer) {
|
||||
List<Player> potentials = getPotentialPlayers(payer, ability.getHostCard());
|
||||
return !potentials.isEmpty();
|
||||
return !getPotentialPlayers(payer, ability).isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -95,7 +99,7 @@ public class CostDraw extends CostPart {
|
||||
*/
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -117,6 +117,11 @@ public class CostExile extends CostPartWithList {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -159,7 +164,7 @@ public class CostExile extends CostPartWithList {
|
||||
amount++;
|
||||
}
|
||||
|
||||
if ((amount != null) && (list.size() < amount)) {
|
||||
if (amount != null && list.size() < amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ public class CostPayLife extends CostPart {
|
||||
* Serializables need a version ID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
int paidAmount = 0;
|
||||
|
||||
/**
|
||||
* Instantiates a new cost pay life.
|
||||
@@ -92,7 +91,7 @@ public class CostPayLife extends CostPart {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -101,9 +100,7 @@ public class CostPayLife extends CostPart {
|
||||
|
||||
@Override
|
||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||
// TODO Auto-generated method stub
|
||||
paidAmount = decision.c;
|
||||
return ai.payLife(paidAmount, null);
|
||||
return ai.payLife(decision.c, null);
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
|
||||
@@ -141,7 +141,7 @@ public class CostPayment extends ManaConversionMatrix {
|
||||
|
||||
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) {
|
||||
((CostPartMana)part).setCardMatrix(this);
|
||||
}
|
||||
@@ -187,9 +187,10 @@ public class CostPayment extends ManaConversionMatrix {
|
||||
|
||||
for (final CostPart part : parts) {
|
||||
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
|
||||
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
|
||||
game.costPaymentStack.push(part, this);
|
||||
|
||||
@@ -72,7 +72,7 @@ public class CostReveal extends CostPartWithList {
|
||||
modifiedHand.remove(source); // can't pay for itself
|
||||
handList = modifiedHand;
|
||||
}
|
||||
handList = CardLists.getValidCards(handList, getType().split(";"), payer, source, ability);
|
||||
handList = CardLists.getValidCards(handList, getType().split(","), payer, source, ability);
|
||||
|
||||
return handList.size();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ public class PaymentDecision {
|
||||
}
|
||||
|
||||
public static PaymentDecision players(List<Player> players) {
|
||||
// TODO Auto-generated method stub
|
||||
return new PaymentDecision(null, null, players, null, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -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."),
|
||||
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}."),
|
||||
INDESTRUCTIBLE("Indestructible", SimpleKeyword.class, true, "Effects that say \"destroy\" don’t 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."),
|
||||
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."),
|
||||
@@ -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."),
|
||||
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."),
|
||||
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."),
|
||||
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."),
|
||||
@@ -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."),
|
||||
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."),
|
||||
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."),
|
||||
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."),
|
||||
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."),
|
||||
|
||||
@@ -73,7 +73,6 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
protected abstract void parse(String details);
|
||||
protected abstract String formatReminderText(String reminderText);
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean)
|
||||
|
||||
@@ -15,7 +15,7 @@ public class Trample extends KeywordInstance<Trample> {
|
||||
@Override
|
||||
protected String formatReminderText(String reminderText) {
|
||||
if (!type.isEmpty()) {
|
||||
return "This creature can deal excess combat damage to the controller of the planeswalker it’s attacking.";
|
||||
return "This creature can deal excess combat damage to the controller of the planeswalker it's attacking.";
|
||||
}
|
||||
return reminderText;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantBeCast;
|
||||
import forge.game.staticability.StaticAbilityCantDraw;
|
||||
import forge.game.staticability.StaticAbilityCantPutCounter;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -862,14 +864,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
public final boolean canReceiveCounters(final CounterType type) {
|
||||
// CantPutCounter static abilities
|
||||
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.applyAbility("CantPutCounter", this, type)) {
|
||||
if (StaticAbilityCantPutCounter.anyCantPutCounter(this, type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1284,13 +1281,11 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
public final boolean canDraw() {
|
||||
if (hasKeyword("You can't draw cards.")) {
|
||||
return false;
|
||||
return canDrawAmount(1);
|
||||
}
|
||||
if (hasKeyword("You can't draw more than one card each turn.")) {
|
||||
return numDrawnThisTurn < 1;
|
||||
}
|
||||
return true;
|
||||
|
||||
public final boolean canDrawAmount(int amount) {
|
||||
return StaticAbilityCantDraw.canDrawThisAmount(this, amount);
|
||||
}
|
||||
|
||||
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) {
|
||||
final CardCollection drawn = new CardCollection();
|
||||
if (n <= 0) {
|
||||
return drawn;
|
||||
}
|
||||
|
||||
// Replacement effects
|
||||
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.";
|
||||
} else {
|
||||
// rule 903.9b
|
||||
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.";
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -564,17 +564,19 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
return TextUtil.fastReplace(origProduced, "Combo ", "");
|
||||
}
|
||||
// ColorIdentity
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (getSourceCard().getController() != null) {
|
||||
List<Card> commanders = getSourceCard().getController().getCommanders();
|
||||
if (commanders.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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}.
|
||||
return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -330,42 +329,6 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
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) {
|
||||
// don't apply the ability if it hasn't got the right mode
|
||||
if (!getParam("Mode").equals(mode)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,45 @@
|
||||
package forge.game.staticability;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
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) {
|
||||
if (stAb.hasParam("CounterType")) {
|
||||
CounterType t = CounterType.getType(stAb.getParam("CounterType"));
|
||||
|
||||
@@ -61,7 +61,8 @@ public class PlayerZone extends Zone {
|
||||
}
|
||||
|
||||
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);
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
final ZoneType restrictZone = sa.getRestrictions().getZone();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="forge.app"
|
||||
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
|
||||
android:minSdkVersion="19"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms1024m</build.min.memory>
|
||||
<build.max.memory>-Xmx1536m</build.max.memory>
|
||||
<alpha-version>1.6.45.001</alpha-version>
|
||||
<alpha-version>1.6.46.001</alpha-version>
|
||||
<sign.keystore>keystore</sign.keystore>
|
||||
<sign.alias>alias</sign.alias>
|
||||
<sign.storepass>storepass</sign.storepass>
|
||||
@@ -19,7 +19,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
@@ -250,7 +250,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<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>
|
||||
<dontWrapJar>true</dontWrapJar>
|
||||
<errTitle>forge</errTitle>
|
||||
@@ -284,7 +284,7 @@
|
||||
</txtProductVersion>
|
||||
<productName>Forge</productName>
|
||||
<internalName>forge</internalName>
|
||||
<originalFilename>forge.exe</originalFilename>
|
||||
<originalFilename>forge-java8.exe</originalFilename>
|
||||
</versionInfo>
|
||||
</configuration>
|
||||
</execution>
|
||||
@@ -322,6 +322,7 @@
|
||||
<include name="res/**" />
|
||||
<exclude name="res/cardsfolder/**" />
|
||||
</fileset>
|
||||
<fileset dir="${project.build.directory}" includes="forge-java8.exe" />
|
||||
<fileset dir="${project.build.directory}" includes="forge.exe" />
|
||||
<fileset dir="${project.build.directory}" includes="${project.build.finalName}-jar-with-dependencies.jar" />
|
||||
</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" />
|
||||
<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-java8.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">
|
||||
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
|
||||
<include name="forge.sh" />
|
||||
<include name="forge.command" />
|
||||
<include name="forge.exe" />
|
||||
<include name="forge-java8.exe" />
|
||||
</tarfileset>
|
||||
<tarfileset dir="${project.build.directory}/${project.build.finalName}">
|
||||
<include name="**" />
|
||||
<exclude name="forge.sh" />
|
||||
<exclude name="forge.command" />
|
||||
<exclude name="forge.exe" />
|
||||
<exclude name="forge-java8.exe" />
|
||||
</tarfileset>
|
||||
</tar>
|
||||
</target>
|
||||
@@ -407,7 +411,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<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>
|
||||
<dontWrapJar>true</dontWrapJar>
|
||||
<errTitle>forge</errTitle>
|
||||
@@ -424,6 +428,60 @@
|
||||
<opt>-Dfile.encoding=UTF-8</opt>
|
||||
</opts>
|
||||
</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>
|
||||
<fileVersion>
|
||||
${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.0
|
||||
@@ -445,6 +503,7 @@
|
||||
</versionInfo>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!--extra-->
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
@@ -482,9 +541,11 @@
|
||||
<include name="res/**" />
|
||||
<exclude name="res/cardsfolder/**" />
|
||||
</fileset>
|
||||
<fileset dir="${project.build.directory}" includes="forge-java8.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}/../../forge-adventure/target" includes="forge-adventure.exe" />
|
||||
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure-java8.exe" />
|
||||
<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" />
|
||||
@@ -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-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-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-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">
|
||||
<tarfileset filemode="755" dir="${project.build.directory}/${project.build.finalName}">
|
||||
<include name="forge.sh" />
|
||||
<include name="forge.command" />
|
||||
<include name="forge-java8.exe" />
|
||||
<include name="forge.exe" />
|
||||
</tarfileset>
|
||||
<tarfileset dir="${project.build.directory}/${project.build.finalName}">
|
||||
<include name="**" />
|
||||
<exclude name="forge.sh" />
|
||||
<exclude name="forge.command" />
|
||||
<exclude name="forge-java8.exe" />
|
||||
<exclude name="forge.exe" />
|
||||
</tarfileset>
|
||||
</tar>
|
||||
|
||||
@@ -172,10 +172,13 @@ public class FDeckViewer extends FDialog {
|
||||
private void copyToClipboard() {
|
||||
final String nl = System.getProperty("line.separator");
|
||||
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;
|
||||
SortedMap<String, Integer> sectionCards;
|
||||
deckList.append(dName == null ? "" : dName + nl + nl);
|
||||
deckList.append(dName == null ? "" : "Deck: "+dName + nl + nl);
|
||||
|
||||
for (DeckSection s : DeckSection.values()){
|
||||
CardPool cp = deck.get(s);
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
|
||||
import forge.control.FControl;
|
||||
import forge.game.GameView;
|
||||
import forge.game.card.CardView;
|
||||
import forge.gui.FThreads;
|
||||
@@ -43,7 +42,6 @@ import forge.model.FModel;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.screens.match.views.VPrompt;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.util.Localizer;
|
||||
|
||||
/**
|
||||
* Controls the prompt panel in the match UI.
|
||||
@@ -157,9 +155,7 @@ public class CPrompt implements ICDoc {
|
||||
matchUI.getGameController().selectButtonCancel();
|
||||
}
|
||||
|
||||
public void setMessage(final String s0) {
|
||||
String header = FControl.instance.getCurrentScreen().getDaytime() != null ? "[" + Localizer.getInstance().getMessage("lbl"+FControl.instance.getCurrentScreen().getDaytime()) + "]\n\n" : "";
|
||||
header += s0;
|
||||
public void setMessage(final String header) {
|
||||
view.getTarMessage().setText(FSkin.encodeSymbols(header, false));
|
||||
view.setCardView(null);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
';'));
|
||||
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;
|
||||
|
||||
@@ -1718,7 +1718,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
// SIMULATE A GAME OF VINTAGE
|
||||
DeckRecognizer recognizer = new DeckRecognizer();
|
||||
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());
|
||||
List<String> bannedCards = Arrays.asList(
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms128m</build.min.memory>
|
||||
<build.max.memory>-Xmx2048m</build.max.memory>
|
||||
<alpha-version>1.6.45.001</alpha-version>
|
||||
<alpha-version>1.6.46.001</alpha-version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -37,7 +37,7 @@ import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
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 Clipboard clipboard;
|
||||
|
||||
@@ -90,7 +90,7 @@ public class FDeckViewer extends FScreen {
|
||||
//fix copying a commander netdeck then importing it again...
|
||||
if (dName.startsWith("[Commander")||dName.contains("Commander"))
|
||||
dName = "";
|
||||
deckList.append(dName == null ? "" : dName + nl + nl);
|
||||
deckList.append(dName == null ? "" : "Deck: "+dName + nl + nl);
|
||||
|
||||
for (DeckSection s : DeckSection.values()){
|
||||
CardPool cp = deck.get(s);
|
||||
|
||||
@@ -528,6 +528,7 @@ public abstract class ItemManager<T extends InventoryItem> extends FContainer im
|
||||
if (pool == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
pool.add(item, qty);
|
||||
if (isUnfiltered()) {
|
||||
model.addItem(item, qty);
|
||||
@@ -535,6 +536,9 @@ public abstract class ItemManager<T extends InventoryItem> extends FContainer im
|
||||
List<T> items = new ArrayList<>();
|
||||
items.add(item);
|
||||
updateView(false, items);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void addItems(Iterable<Entry<T, Integer>> itemsToAdd) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.46-SNAPSHOT</version>
|
||||
<version>1.6.47-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui</artifactId>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#Add one announcement per line
|
||||
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.
|
||||
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".
|
||||
|
||||
@@ -22,7 +22,7 @@ leriomaggio
|
||||
Luke
|
||||
Marek14
|
||||
Marvel
|
||||
mcrawford620
|
||||
mcrawford
|
||||
medusa
|
||||
Meerkov
|
||||
Monkey Gland Sauce
|
||||
|
||||
@@ -99,3 +99,4 @@ Strixhaven: School of Mages, 3/6/STX, STX
|
||||
Modern Horizons 2, 3/6/MH2, MH2
|
||||
Adventures in the Forgotten Realms, 3/6/AFR, AFR
|
||||
Innistrad: Midnight Hunt, 3/6/MID, MID
|
||||
Innistrad: Crimson Vow, 3/6/VOW, VOW
|
||||
|
||||
@@ -2,9 +2,7 @@ Name:Akoum Flameseeker
|
||||
ManaCost:2 R
|
||||
Types:Creature Human Shaman Ally
|
||||
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.
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
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.
|
||||
AI:RemoveDeck:All
|
||||
DeckHints:Type$Ally
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/akoum_flameseeker.jpg
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
SVar:X:Count$CardTypes.Graveyard
|
||||
SVar:X:Count$CardTypes.ValidGraveyard Card
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
Name:Annihilating Fire
|
||||
ManaCost:1 R R
|
||||
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: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.
|
||||
|
||||
@@ -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: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: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: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.
|
||||
|
||||
@@ -3,5 +3,5 @@ ManaCost:5 U U
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:4 G G
|
||||
Types:Legendary Planeswalker Arlinn
|
||||
Loyalty:7
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
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$ 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.
|
||||
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:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:Remembered$Amount
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Enchantment Saga
|
||||
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: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 that’s 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
|
||||
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.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:2 B R
|
||||
Types:Legendary Creature Imp
|
||||
PT:4/3
|
||||
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:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBLoseLife
|
||||
SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X | SubAbility$ DBDiscard
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:2 W
|
||||
Types:Enchantment Aura
|
||||
K:Enchant permanent
|
||||
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 can’t attack, block, or crew Vehicles, and its activated abilities can’t be activated unless they’re 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
|
||||
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.
|
||||
|
||||
@@ -8,4 +8,4 @@ SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB
|
||||
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.
|
||||
SVar:TrigDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Bringer
|
||||
PT:5/5
|
||||
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.
|
||||
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.
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2
|
||||
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 | 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.
|
||||
|
||||
@@ -2,8 +2,7 @@ Name:Brutal Expulsion
|
||||
ManaCost:2 U R
|
||||
Types:Instant
|
||||
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: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:Picture:http://www.wizards.com/global/images/magic/general/brutal_expulsion.jpg
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
Name:Burn from Within
|
||||
ManaCost:X R
|
||||
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:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Carbonize
|
||||
ManaCost:2 R
|
||||
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.
|
||||
SVar:DB:DB$ Pump | KW$ HIDDEN CARDNAME can't be regenerated. | Defined$ Targeted.Creature
|
||||
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 | 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.
|
||||
|
||||
@@ -2,9 +2,8 @@ Name:Champion of Wits
|
||||
ManaCost:2 U
|
||||
Types:Creature Naga Wizard
|
||||
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.
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X | SubAbility$ DBDiscard
|
||||
SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 2
|
||||
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:AB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 2 | Cost$ Draw<X/You>
|
||||
K:Eternalize:5 U U
|
||||
SVar:X:Count$CardPower
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/champion_of_wits.jpg
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Name:Chandra's Defeat
|
||||
ManaCost:R
|
||||
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.
|
||||
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$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
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:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Targeted | ConditionPresent$ Planeswalker.Chandra | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ You
|
||||
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.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Creature Lhurgoyf Imp
|
||||
PT:*/1+*
|
||||
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.
|
||||
SVar:X:Count$CardTypes.Graveyard
|
||||
SVar:X:Count$CardTypes.ValidGraveyard Card
|
||||
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.
|
||||
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | Chooser$ Opponent | Mandatory$ True | IsCurse$ True
|
||||
|
||||
@@ -3,8 +3,8 @@ ManaCost:1 GU GU
|
||||
Types:Creature Merfolk Rogue
|
||||
PT:1/1
|
||||
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.
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X
|
||||
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 | OptionalDecider$ You
|
||||
SVar:X:TriggerCount$DamageAmount
|
||||
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.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:4 U U
|
||||
Types:Creature Sphinx
|
||||
PT:4/6
|
||||
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.
|
||||
SVar:TrigDraw:DB$ Draw | NumCards$ 2
|
||||
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 | OptionalDecider$ You
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Whale
|
||||
PT:*/*
|
||||
K:This spell can't be countered.
|
||||
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 you’ve 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
|
||||
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.)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Corpse Cobble
|
||||
ManaCost:U B
|
||||
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
|
||||
SVar:X:Count$xPaid
|
||||
SVar:Y:Sacrificed$CardPower
|
||||
|
||||
@@ -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.
|
||||
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: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: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
|
||||
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.)
|
||||
|
||||
@@ -5,7 +5,7 @@ PT:3/3
|
||||
K:Flash
|
||||
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$ Continuous | Affected$ You | AddKeyword$ Foretell on any player’s turn | Secondary$ True | Description$ Foretelling cards from your hand on any player’s 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
|
||||
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.)
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ Types:Enchantment Aura Curse
|
||||
K:Enchant player
|
||||
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.
|
||||
SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBDraw | RememberDiscarded$ True
|
||||
SVar:DBDraw:DB$ Draw | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:TrigDiscard:DB$ Draw | Defined$ TriggeredAttackingPlayer | NumCards$ 1 | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ TriggeredAttackingPlayer
|
||||
AI:RemoveDeck:All
|
||||
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.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:6 U
|
||||
Types:Enchantment Aura Curse
|
||||
K:Enchant player
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -3,9 +3,7 @@ ManaCost:1 B R
|
||||
Types:Legendary Planeswalker Daretti
|
||||
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$ 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
|
||||
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$ 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
|
||||
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
|
||||
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
Reference in New Issue
Block a user