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

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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()

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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)) {

View File

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

View File

@@ -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.

View File

@@ -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

View File

@@ -488,6 +488,9 @@ public class CountersPutAi extends CountersAi {
}
});
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
if (abCost.hasSpecificCostType(CostSacrifice.class)) {
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();

View File

@@ -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;
@@ -66,7 +60,13 @@ public class DestroyAllAi extends SpellAbilityAi {
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
final String aiLogic = sa.getParamOrDefault("AILogic", "");
if ("FellTheMighty".equals(aiLogic)) {
return SpecialCardAi.FellTheMighty.consider(ai, sa);
}
return doMassRemovalLogic(ai, sa);
}

View File

@@ -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

View File

@@ -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)
*/

View File

@@ -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)) {
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)) {
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);
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -388,6 +388,8 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
// == Get the most representative (Pivot) Edition in the Pool
// 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();

View File

@@ -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>

View File

@@ -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()) {

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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 dont control one, create a 0/0 black Zombie Army creature token first.)");
sb.append("on an Army you control. If you don't control one, create a 0/0 black Zombie Army creature token first.)");
return sb.toString();
}

View File

@@ -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 cant become the monarch this turn.")) {
if (!p.hasKeyword("You can't become the monarch this turn.")) {
p.getGame().getAction().becomeMonarch(p, set);
}
}

View File

@@ -137,9 +137,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0;
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.

View File

@@ -60,11 +60,10 @@ public class CopyPermanentEffect extends TokenEffectBase {
final Game game = host.getGame();
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
return;
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")) {

View File

@@ -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));
}
}
}
}

View File

@@ -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;
@@ -21,7 +21,7 @@ public class DrawEffect extends SpellAbilityEffect {
if (!tgtPlayers.isEmpty()) {
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
sb.append(Lang.joinHomogenous(tgtPlayers));
if (tgtPlayers.size() > 1) {
@@ -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"))))
continue;
// TODO can this be removed?
if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
continue;
}
int actualNum = numCards;
// it is optional, not upto and player can't choose to draw that many cards
if (optional && !upto && !p.canDrawAmount(numCards)) {
continue;
}
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")))) {
continue;
}
int actualNum = numCards;
if (upto) { // if it is upto, player can only choose how many cards they can draw
actualNum = StaticAbilityCantDraw.canDrawAmount(p, actualNum);
}
if (actualNum <= 0) {
continue;
}
if (upto) {
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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -100,15 +100,19 @@ 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 (game.getAction().sacrifice(card, sa, table, params) != null) {
if (remSacrificed) {
card.addRemembered(card);
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);
}
}
}
}
@@ -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) {

View File

@@ -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?

View File

@@ -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,13 +1365,8 @@ 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)) {
return false;
}
}
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");

View File

@@ -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());

View File

@@ -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 its neither day nor night, it becomes day.";
final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with daybound, if it's neither day nor night, it becomes day.";
String setDayEff = "DB$ DayTime | Value$ Day";
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 its neither day nor night and there are no permanents with daybound on the battlefield, it becomes night.";
final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | IsPresent$ Card.Daybound | PresentCompare$ EQ0 | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with nightbound, if it's neither day nor night and there are no permanents with daybound on the battlefield, it becomes night.";
String setDayEff = "DB$ DayTime | Value$ Night";
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 players 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 cant be transformed except by its daybound ability.";
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
} else if (keyword.equals("Decayed")) {
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 cant be transformed except by its nightbound ability.";
effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can't be transformed except by its nightbound ability.";
} else if (keyword.startsWith("Protection")) {
String valid = getProtectionValid(keyword, false);
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self ";

View File

@@ -20,6 +20,8 @@ import forge.game.mana.Mana;
import forge.game.player.Player;
import forge.game.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;

View File

@@ -1428,42 +1428,86 @@ public class CardView extends GameEntityView {
if (sa.getManaPart().isAnyMana()) {
anyMana = true;
}
switch (sa.getManaPart().getOrigProduced()) {
case "R":
if (!rMana) {
count += 1;
rMana = 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;
}
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) {
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;
}
}
}
}

View File

@@ -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),

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -95,7 +95,7 @@ public enum Keyword {
HIDEAWAY("Hideaway", SimpleKeyword.class, false, "This permanent enters the battlefield tapped. When it does, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library."),
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\" dont destroy this."),
INDESTRUCTIBLE("Indestructible", SimpleKeyword.class, true, "Effects that say \"destroy\" don't destroy this."),
INFECT("Infect", SimpleKeyword.class, true, "This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters."),
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 spells mana cost if a player was dealt combat damage this turn by a source that, at the time it dealt that damage, was under your control and had any of this spells creature types."),
PROWL("Prowl", KeywordWithCost.class, false, "You may pay %s rather than pay this spell's mana cost if a player was dealt combat damage this turn by a source that, at the time it dealt that damage, was under your control and had any of this spell's creature types."),
RAMPAGE("Rampage", KeywordWithAmount.class, false, "Whenever this creature becomes blocked, it gets +%1$d/+%1$d until end of turn for each creature blocking it beyond the first."),
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."),

View File

@@ -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)

View File

@@ -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 its attacking.";
return "This creature can deal excess combat damage to the controller of the planeswalker it's attacking.";
}
return reminderText;
}

View File

@@ -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,13 +864,8 @@ 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)) {
return false;
}
}
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;
}
if (hasKeyword("You can't draw more than one card each turn.")) {
return numDrawnThisTurn < 1;
}
return true;
return canDrawAmount(1);
}
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 owners hand or library from anywhere, its owner may put it into the command zone instead.";
moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead.";
}
eff.addReplacementEffect(ReplacementHandler.parseReplacement(moved, eff, true));
}

View File

@@ -564,17 +564,19 @@ public class AbilityManaPart implements java.io.Serializable {
return TextUtil.fastReplace(origProduced, "Combo ", "");
}
// ColorIdentity
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 "); }
if (getSourceCard().getController() != null) {
List<Card> commanders = getSourceCard().getController().getCommanders();
if (commanders.isEmpty()) {
return "";
}
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);
}

View File

@@ -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)) {

View File

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

View File

@@ -1,11 +1,45 @@
package forge.game.staticability;
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"));

View File

@@ -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();

View File

@@ -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"

View File

@@ -6,7 +6,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.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>

View File

@@ -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,11 +541,13 @@
<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.sh" />
<fileset dir="${project.build.directory}/../../forge-adventure/target" includes="forge-adventure.command" />
<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" />
</copy>
<mkdir dir="${project.build.directory}/${project.build.finalName}/res/cardsfolder" />
@@ -495,18 +556,22 @@
<chmod file="${project.build.directory}/${project.build.finalName}/forge.command" perm="a+rx" />
<chmod file="${project.build.directory}/${project.build.finalName}/forge-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>

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -1681,7 +1681,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
StringUtils.split("Balustrade Spy;Bloodstained Mire;Felidar Guardian;Field of the Dead;Flooded Strand;Inverter of Truth;Kethis, the Hidden Hand;Leyline of Abundance;Nexus of Fate;Oko, Thief of Crowns;Once Upon a Time;Polluted Delta;Smuggler's Copter;Teferi, Time Raveler;Undercity Informer;Underworld Breach;Uro, Titan of Nature's Wrath;Veil of Summer;Walking Ballista;Wilderness Reclamation;Windswept Heath;Wooded Foothills",
';'));
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(

View File

@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);

View File

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

View File

@@ -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>

View File

@@ -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".

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -4,6 +4,6 @@ Types:Tribal Artifact Lhurgoyf
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.
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.

View File

@@ -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.

View File

@@ -5,7 +5,7 @@ A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell |
SVar:DelTrigSlowtrip:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | SubAbility$ DelTrigDrawTwo | TriggerDescription$ Draw a card.
SVar: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.

View File

@@ -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.

View File

@@ -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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ ManaCost:2 B R
Types:Legendary Creature Imp
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

View File

@@ -6,5 +6,5 @@ K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may exile target card from a graveyard.
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.

View File

@@ -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 cant attack, block, or crew Vehicles, and its activated abilities cant be activated unless theyre mana abilities.
S:Mode$ Continuous | Affected$ Permanent.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. & CARDNAME can't crew Vehicles. | Description$ Enchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities.
S:Mode$ CantBeActivated | ValidCard$ Card.EnchantedBy | NonMana$ True
Oracle:Enchant permanent\nEnchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities.

View File

@@ -8,4 +8,4 @@ SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB
SVar:ETB:DB$ InternalEtbReplacement
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 cant be blocked except by two or more creatures.)\nIf it's neither day nor night, it becomes day as Brimstone Vandal enters the battlefield.\nWhenever day becomes night or night becomes day, Brimstone Vandal deals 1 damage to each opponent.
Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nIf it's neither day nor night, it becomes day as Brimstone Vandal enters the battlefield.\nWhenever day becomes night or night becomes day, Brimstone Vandal deals 1 damage to each opponent.

View File

@@ -4,6 +4,6 @@ Types:Creature Bringer
PT:5/5
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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 youve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of turns you've taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
SVar:X:Count$YourTurns
Oracle:This spell can't be countered.\nShroud\nControl Win Condition's power and toughness are each equal to the number of turns you've taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)

View File

@@ -1,7 +1,7 @@
Name:Corpse Cobble
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

View File

@@ -4,7 +4,7 @@ Types:Instant
A:SP$ Effect | ReplacementEffects$ ReplaceGrave | SpellDescription$ If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step.
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 owners control at the beginning of the next end step.
SVar:DBDelayTrigger:DB$ DelayedTrigger | RememberObjects$ ReplacedCard | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ Return it to the battlefield under its owner's control at the beginning of the next end step.
SVar:TrigReturn:DB$ ChangeZone | Defined$ DelayTriggerRemembered | Destination$ Battlefield
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.)

View File

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

View File

@@ -4,9 +4,7 @@ Types:Enchantment Aura Curse
K:Enchant player
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.

View File

@@ -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.

View File

@@ -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