...
 
Commits (139)
......@@ -1199,7 +1199,7 @@ public class AiAttackController {
// - our creature will die for sure (chump attack)
// - our attack will not do anything special (no attack/combat effect to proc)
// - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less
if (attackerWillDie || (avoidAttackingIntoBlock && (uselessAttack || noContributionToAttack))) {
if (attackerWillDie || (avoidAttackingIntoBlock && uselessAttack && noContributionToAttack)) {
canKillAllDangerous = false;
}
}
......@@ -1248,8 +1248,9 @@ public class AiAttackController {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = all out attacking");
return true;
case 4: // expecting to at least trade with something
if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) {
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
return true;
......
......@@ -1842,6 +1842,15 @@ public class ComputerUtilCard {
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
if (sa != null && sa.isEvoke()) {
if (card.hasSVar("NeedsToPlayEvoked")) {
needsToPlayName = "NeedsToPlayEvoked";
}
if (card.hasSVar("NeedsToPlayEvokedVar")) {
needsToPlayVarName = "NeedsToPlayEvokedVar";
}
}
if (card.hasSVar(needsToPlayName)) {
final String needsToPlay = card.getSVar(needsToPlayName);
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
......
......@@ -91,7 +91,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
// AI does not know how to sideboard
return null;
}
......
......@@ -501,6 +501,49 @@ public class SpecialCardAi {
}
}
// Gideon Blackblade
public static class GideonBlackblade {
public static boolean consider(final Player ai, final SpellAbility sa) {
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
if (!otb.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
}
return true;
}
public static SpellAbility chooseSpellAbility(final Player ai, final SpellAbility sa, final List<SpellAbility> spells) {
// TODO: generalize and improve this so that it acts in a more reasonable way and can potentially be used for other cards too
List<SpellAbility> best = Lists.newArrayList();
List<SpellAbility> possible = Lists.newArrayList();
Card tgtCard = sa.getTargetCard();
if (tgtCard != null) {
for (SpellAbility sp : spells) {
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(ai, sp)) {
best.add(sp); // these SAs are prioritized since the AI sees a reason to play them now
}
final List<String> keywords = sp.hasParam("KW") ? Arrays.asList(sp.getParam("KW").split(" & "))
: Lists.<String>newArrayList();
for (String kw : keywords) {
if (!tgtCard.hasKeyword(kw)) {
if ("Indestructible".equals(kw) && ai.getOpponents().getCreaturesInPlay().isEmpty()) {
continue; // nothing to damage or kill the creature with
}
possible.add(sp); // these SAs at least don't duplicate a keyword on the card
break;
}
}
}
}
if (!best.isEmpty()) {
return Aggregates.random(best);
} else if (!possible.isEmpty()) {
return Aggregates.random(possible);
} else {
return Aggregates.random(spells); // if worst comes to worst, it's a PW +1 ability, so do at least something
}
}
}
// Guilty Conscience
public static class GuiltyConscience {
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
......
......@@ -35,6 +35,7 @@ public enum SpellApiToAi {
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeX, AlwaysPlayAi.class)
.put(ApiType.ChangeZone, ChangeZoneAi.class)
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
.put(ApiType.Charm, CharmAi.class)
......
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.ai.*;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
......@@ -35,6 +21,9 @@ import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
public class ChooseGenericEffectAi extends SpellAbilityAi {
......@@ -50,6 +39,8 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
return true;
}
}
} else if ("GideonBlackblade".equals(aiLogic)) {
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
}
return false;
}
......@@ -95,7 +86,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
return spells.get(0);
} else if ("Random".equals(logic)) {
return Aggregates.random(spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
} else if ("GideonBlackblade".equals(logic)) {
return SpecialCardAi.GideonBlackblade.chooseSpellAbility(player, sa, spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility sp) {
......
......@@ -201,14 +201,20 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
return true;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
// remove P1P1 counters from opposing creatures
CardCollection oppP1P1List = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
CardPredicates.hasCounter(CounterType.P1P1));
if (!oppP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
return true;
}
......@@ -363,6 +369,11 @@ public class CountersRemoveAi extends SpellAbilityAi {
if (targetCard.getController().isOpponentOf(player)) {
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
} else {
if (targetCard.hasKeyword(Keyword.UNDYING) && type == CounterType.P1P1
&& targetCard.getCounters(CounterType.P1P1) >= max) {
return max;
}
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
}
} else if (target instanceof Player) {
......
......@@ -79,7 +79,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Specific details of ordering cards are handled by PlayerControllerAi#orderMoveToZoneList
return mandatory || canPlayAI(ai, sa);
return canPlayAI(ai, sa) || mandatory;
}
/* (non-Javadoc)
......
......@@ -439,6 +439,7 @@ public class GameAction {
copied.clearDevoured();
copied.clearDelved();
copied.clearConvoked();
copied.clearExploited();
}
// rule 504.6: reveal a face-down card leaving the stack
......
......@@ -11,6 +11,7 @@ public class GameRules {
private int gamesToWinMatch = 2;
private boolean playForAnte = false;
private boolean matchAnteRarity = false;
private boolean sideboardForAI = false;
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
// it's a preference, not rule... but I could hardly find a better place for it
......@@ -65,6 +66,14 @@ public class GameRules {
matchAnteRarity = matchRarity;
}
public boolean getSideboardForAI() {
return sideboardForAI;
}
public void setSideboardForAI(final boolean sideboard) {
sideboardForAI = sideboard;
}
public int getGamesToWinMatch() {
return gamesToWinMatch;
}
......
......@@ -4,12 +4,14 @@ import com.google.common.collect.*;
import forge.LobbyPlayer;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckFormat;
import forge.deck.DeckSection;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.event.GameEventAnteCardsSelected;
import forge.game.event.GameEventGameFinished;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.RegisteredPlayer;
import forge.game.trigger.Trigger;
import forge.game.zone.PlayerZone;
......@@ -208,18 +210,39 @@ public class Match {
Multimap<Player, PaperCard> rAICards = HashMultimap.create();
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
final FCollectionView<Player> players = game.getPlayers();
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
boolean isFirstGame = game.getMatch().getPlayedGames().isEmpty();
boolean canSideBoard = !isFirstGame && rules.getGameType().isSideboardingAllowed();
// Only allow this if feature flag is on AND for certain match types
boolean sideboardForAIs = rules.getSideboardForAI() &&
rules.getGameType().getDeckFormat().equals(DeckFormat.Constructed);
PlayerController sideboardProxy = null;
if (canSideBoard && sideboardForAIs) {
for (int i = 0; i < playersConditions.size(); i++) {
final Player player = players.get(i);
final RegisteredPlayer psc = playersConditions.get(i);
if (!player.getController().isAI()) {
sideboardProxy = player.getController();
break;
}
}
}
final FCollectionView<Player> players = game.getPlayers();
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
for (int i = 0; i < playersConditions.size(); i++) {
final Player player = players.get(i);
final RegisteredPlayer psc = playersConditions.get(i);
if (canSideBoard) {
PlayerController person = player.getController();
if (sideboardProxy != null && person.isAI()) {
person = sideboardProxy;
}
String forPlayer = " for " + player.getName();
Deck toChange = psc.getDeck();
List<PaperCard> newMain = player.getController().sideboard(toChange, rules.getGameType());
List<PaperCard> newMain = person.sideboard(toChange, rules.getGameType(), forPlayer);
if (null != newMain) {
CardPool allCards = new CardPool();
allCards.addAll(toChange.get(DeckSection.Main));
......
......@@ -27,11 +27,13 @@ public enum ApiType {
BecomeMonarch (BecomeMonarchEffect.class),
BecomesBlocked (BecomesBlockedEffect.class),
BidLife (BidLifeEffect.class),
Block (BlockEffect.class),
Bond (BondEffect.class),
Branch (BranchEffect.class),
ChangeCombatants (ChangeCombatantsEffect.class),
ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),
ChangeX (ChangeXEffect.class),
ChangeZone (ChangeZoneEffect.class),
ChangeZoneAll (ChangeZoneAllEffect.class),
Charm (CharmEffect.class),
......
package forge.game.ability.effects;
import com.google.common.collect.Maps;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.Game;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import java.util.*;
public class BlockEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
final Combat combat = game.getPhaseHandler().getCombat();
List<Card> attackers = new ArrayList<Card>();
if (sa.hasParam("DefinedAttacker")) {
for (final Card attacker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa)) {
if (combat.isAttacking(attacker))
attackers.add(attacker);
}
}
List<Card> blockers = new ArrayList<Card>();
if (sa.hasParam("DefinedBlocker")) {
for (final Card blocker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedBlocker"), sa)) {
if (blocker.isCreature() && blocker.isInZone(ZoneType.Battlefield))
blockers.add(blocker);
}
}
if (attackers.size() == 0 || blockers.size() == 0) return;
for (final Card attacker : attackers) {
final boolean wasBlocked = combat.isBlocked(attacker);
for (final Card blocker : blockers) {
if (combat.isBlocking(blocker, attacker)) continue;
// If the attacker was blocked, this covers adding the blocker to the damage assignment
combat.addBlocker(attacker, blocker);
combat.orderAttackersForDamageAssignment(blocker);
blocker.addBlockedThisTurn(attacker);
attacker.addBlockedByThisTurn(blocker);
Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Attacker", attacker);
runParams.put("Blocker", blocker);
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedByCreature, runParams, false);
runParams = Maps.newHashMap();
runParams.put("Blocker", blocker);
runParams.put("Attackers", attacker);
game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false);
}
attacker.getDamageHistory().setCreatureGotBlockedThisCombat(true);
if (!wasBlocked) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Attacker", attacker);
runParams.put("Blockers", blockers);
runParams.put("NumBlockers", blockers.size());
runParams.put("Defender", combat.getDefenderByAttacker(attacker));
runParams.put("DefendingPlayer", combat.getDefenderPlayerByAttacker(attacker));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
combat.orderBlockersForDamageAssignment(attacker, new CardCollection(blockers));
}
}
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
@Override
protected String getStackDescription(SpellAbility sa) {
final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder();
// end standard pre-
List<String> attackers = new ArrayList<String>();
if (sa.hasParam("DefinedAttacker")) {
for (final Card attacker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa)) {
attackers.add(attacker.toString());
}
}
List<String> blockers = new ArrayList<String>();
if (sa.hasParam("DefinedBlocker")) {
for (final Card blocker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedBlocker"), sa)) {
blockers.add(blocker.toString());
}
}
sb.append(String.join(", ", blockers)).append(" block ").append(String.join(", ", attackers));
return sb.toString();
}
}
package forge.game.ability.effects;
import java.util.List;
import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.MagicStack;
public class ChangeXEffect extends SpellAbilityEffect {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
// can't get the SpellAbilityStackInstances directly from the Stack,
// even if they are in the Triggered Objects
final List<SpellAbility> sas = getTargetSpells(sa);
final Player activator = sa.getActivatingPlayer();
final MagicStack stack = activator.getGame().getStack();
for (final SpellAbility tgtSA : sas) {
SpellAbilityStackInstance si = stack.getInstanceFromSpellAbility(tgtSA);
if (si == null) {
// If there isn't a Stack Instance, then this doesn't matter
continue;
}
// currently hard coded, no nicer way to get the xManaPaid from that Spell/Card
si.setXManaPaid(si.getXManaPaid() * 2);
}
}
}
......@@ -151,6 +151,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false);
}
if (exploit) {
card.addExploited(lKICopy);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Exploited", lKICopy);
runParams.put("Card", card);
......
......@@ -99,7 +99,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection();
// cards attached or otherwise linked to this card
private CardCollection hauntedBy, devouredCards, delvedCards, convokedCards, imprintedCards, encodedCards;
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
private CardCollection mustBlockCards, gainControlTargets, chosenCards, blockedThisTurn, blockedByThisTurn;
// if this card is attached or linked to something, what card is it currently attached to
......@@ -816,7 +816,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return Card.storableSVars;
}
public final CardCollectionView getDevoured() {
public final CardCollectionView getDevouredCards() {
return CardCollection.getView(devouredCards);
}
public final void addDevoured(final Card c) {
......@@ -830,6 +830,19 @@ public class Card extends GameEntity implements Comparable<Card> {
devouredCards = null;
}
public final CardCollectionView getExploited() {
return CardCollection.getView(exploitedCards);
}
public final void addExploited(final Card c) {
if (exploitedCards == null) {
exploitedCards = new CardCollection();
}
exploitedCards.add(c);
}
public final void clearExploited() {
exploitedCards = null;
}
public final CardCollectionView getDelved() {
return CardCollection.getView(delvedCards);
}
......@@ -5152,10 +5165,6 @@ public class Card extends GameEntity implements Comparable<Card> {
currentState.setSVar("Foil", Integer.toString(f));
}
public final CardCollectionView getDevouredCards() {
return CardCollection.getView(devouredCards);
}
public final CardCollectionView getHauntedBy() {
return CardCollection.getView(hauntedBy);
}
......
......@@ -1112,6 +1112,25 @@ public class CardFactoryUtil {
return doXMath(n, m, c);
}
if (sq[0].contains("CreatureType")) {
String[] sqparts = sq[0].split(" ", 2);
final String[] rest = sqparts[1].split(",");
final CardCollectionView cardsInZones = sqparts[0].length() > 12
? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12)))
: game.getCardsIn(ZoneType.Battlefield);
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c, null);
final Set<String> creatTypes = Sets.newHashSet();
for (Card card : cards) {
Iterables.addAll(creatTypes, card.getType().getCreatureTypes());
}
int n = creatTypes.contains("AllCreatureTypes") ? CardType.getAllCreatureTypes().size() : creatTypes.size();
return doXMath(n, m, c);
}
if (sq[0].contains("Hellbent")) {
return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c);
}
......@@ -1151,7 +1170,7 @@ public class CardFactoryUtil {
if (sq[0].startsWith("Devoured")) {
final String validDevoured = l[0].split(" ")[1];
CardCollection cl = CardLists.getValidCards(c.getDevoured(), validDevoured.split(","), cc, c, null);
CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), cc, c, null);
return doXMath(cl.size(), m, c);
}
......
......@@ -1394,6 +1394,10 @@ public class CardProperty {
if (!source.getConvoked().contains(card)) {
return false;
}
} else if (property.startsWith("exploited")) {
if (!source.getExploited().contains(card)) {
return false;
}
} else if (property.startsWith("unequalPT")) {
if (card.getNetPower() == card.getNetToughness()) {
return false;
......
......@@ -449,7 +449,7 @@ public class Combat {
for (Card attacker : band.getAttackers()) {
if (blockers.size() <= 1) {
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blockers));
orderBlockersForDamageAssignment(attacker, new CardCollection(blockers));
}
else { // process it a bit later
blockersNeedManualOrdering.add(Pair.of(attacker, new CardCollection(blockers))); // we know there's a list
......@@ -459,27 +459,37 @@ public class Combat {
// brought this out of iteration on bands to avoid concurrency problems
for (Pair<Card, CardCollection> pair : blockersNeedManualOrdering) {
// Damage Ordering needs to take cards like Melee into account, is that happening?
CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlockers(pair.getLeft(), pair.getRight()); // we know there's a list
blockersOrderedForDamageAssignment.put(pair.getLeft(), orderedBlockers);
// Display the chosen order of blockers in the log
// TODO: this is best done via a combat panel update
StringBuilder sb = new StringBuilder();
sb.append(playerWhoAttacks.getName());
sb.append(" has ordered blockers for ");
sb.append(pair.getLeft());
sb.append(": ");
for (int i = 0; i < orderedBlockers.size(); i++) {
sb.append(orderedBlockers.get(i));
if (i != orderedBlockers.size() - 1) {
sb.append(", ");
}
}
playerWhoAttacks.getGame().getGameLog().add(GameLogEntryType.COMBAT, sb.toString());
orderBlockersForDamageAssignment(pair.getLeft(), pair.getRight());
}
}
/** If there are multiple blockers, the Attacker declares the Assignment Order */
public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role
if (blockers.size() <= 1) {
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blockers));
return;
}
// Damage Ordering needs to take cards like Melee into account, is that happening?
CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlockers(attacker, blockers); // we know there's a list
blockersOrderedForDamageAssignment.put(attacker, orderedBlockers);
// Display the chosen order of blockers in the log
// TODO: this is best done via a combat panel update
StringBuilder sb = new StringBuilder();
sb.append(playerWhoAttacks.getName());
sb.append(" has ordered blockers for ");
sb.append(attacker);
sb.append(": ");
for (int i = 0; i < orderedBlockers.size(); i++) {
sb.append(orderedBlockers.get(i));
if (i != orderedBlockers.size() - 1) {
sb.append(", ");
}
}
playerWhoAttacks.getGame().getGameLog().add(GameLogEntryType.COMBAT, sb.toString());
}
/**
* Add a blocker to the damage assignment order of an attacker. The
* relative order of creatures already blocking the attacker may not be
......
......@@ -91,7 +91,7 @@ public abstract class PlayerController {
public abstract void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets);
public abstract void playSpellAbilityNoStack(SpellAbility effectSA, boolean mayChoseNewTargets);
public abstract List<PaperCard> sideboard(final Deck deck, GameType gameType);
public abstract List<PaperCard> sideboard(final Deck deck, GameType gameType, String message);
public abstract List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses);
public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder);
......
......@@ -1058,6 +1058,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return c.getNetPower() <= parentTarget.getNetPower();
case "LECMC" :
return c.getCMC() <= parentTarget.getCMC();
case "SharedCreatureType" :
return c.sharesCreatureTypeWith(parentTarget);
}
}
......
......@@ -241,6 +241,10 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
return xManaPaid;
}
public final void setXManaPaid(int x) {
xManaPaid = x;
}
public final boolean isSpell() {
return ability.isSpell();
}
......
......@@ -199,6 +199,16 @@ public class TriggerSpellAbilityCast extends Trigger {
}
}
if (hasParam("HasXManaCost")) {
final Cost cost = (Cost) (runParams2.get("Cost"));
if (cost.hasNoManaCost()) {
return false;
}
if (cost.getCostMana().getAmountOfX() <= 0) {
return false;
}
}
if (hasParam("Conspire")) {
if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) {
return false;
......
......@@ -234,10 +234,10 @@ public class GuiChoose {
return null;
}
public static <T extends Comparable<? super T>> List<T> sideboard(final CMatchUI matchUI, final List<T> sideboard, final List<T> deck) {
public static <T extends Comparable<? super T>> List<T> sideboard(final CMatchUI matchUI, final List<T> sideboard, final List<T> deck, final String message) {
Collections.sort(deck);
Collections.sort(sideboard);
return order("Sideboard", "Main Deck", -1, -1, sideboard, deck, null, true, matchUI);
return order("Sideboard" + message, "Main Deck", -1, -1, sideboard, deck, null, true, matchUI);
}
public static <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
......
......@@ -10,6 +10,7 @@ import forge.screens.home.VHomeUI;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.util.Localizer;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
......@@ -23,14 +24,14 @@ import javax.swing.*;
public enum VSubmenuGauntletLoad implements IVSubmenu<CSubmenuGauntletLoad> {
/** */
SINGLETON_INSTANCE;
final Localizer localizer = Localizer.getInstance();
// Fields used with interface IVDoc
private DragCell parentCell;
private final DragTab tab = new DragTab("Quick Gauntlets");
private final DragTab tab = new DragTab(localizer.getMessage("lblQuickGauntlets"));
// Other fields
private final FLabel lblTitle = new FLabel.Builder()
.text("Load a gauntlet").fontAlign(SwingConstants.CENTER)
.text(localizer.getMessage("lblLoadaGauntlet")).fontAlign(SwingConstants.CENTER)
.opaque(true).fontSize(16).build();
private final QuickGauntletLister gauntletList = new QuickGauntletLister();
......@@ -38,7 +39,7 @@ public enum VSubmenuGauntletLoad implements IVSubmenu<CSubmenuGauntletLoad> {
private final FScrollPane scrLoad = new FScrollPane(gauntletList, false);
private final FLabel lblDesc = new FLabel.Builder().text(
"Load a previous gauntlet (uses the deck with which it was started).")
localizer.getMessage("lblLoadaPreviousGauntlet"))
.build();
private final StartButton btnStart = new StartButton();
......@@ -60,7 +61,7 @@ public enum VSubmenuGauntletLoad implements IVSubmenu<CSubmenuGauntletLoad> {
*/
@Override
public String getMenuTitle() {
return "Load Gauntlet";
return localizer.getMessage("lblLoadGauntlet");
}
/* (non-Javadoc)
......
......@@ -9,6 +9,7 @@ import forge.match.LocalLobby;
import forge.net.event.UpdateLobbyPlayerEvent;
import forge.screens.home.*;
import forge.toolbox.FLabel;
import forge.util.Localizer;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
......@@ -16,9 +17,9 @@ import java.awt.*;
public enum VSubmenuPuzzleCreate implements IVSubmenu<CSubmenuPuzzleCreate> {
SINGLETON_INSTANCE;
final Localizer localizer = Localizer.getInstance();
private DragCell parentCell;
private final DragTab tab = new DragTab("Puzzle Mode: Create");
private final DragTab tab = new DragTab(localizer.getMessage("lblPuzzleModeCreate"));
private final GameLobby lobby = new LocalLobby();
private final VLobby vLobby = new VLobby(lobby);
......@@ -68,7 +69,7 @@ public enum VSubmenuPuzzleCreate implements IVSubmenu<CSubmenuPuzzleCreate> {
@Override
public String getMenuTitle() {
return "Create";
return localizer.getMessage(("lblCreate"));
}
@Override
......@@ -111,7 +112,7 @@ public enum VSubmenuPuzzleCreate implements IVSubmenu<CSubmenuPuzzleCreate> {
container.removeAll();
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
vLobby.getLblTitle().setText("Puzzle Mode: Create");
vLobby.getLblTitle().setText(localizer.getMessage("lblPuzzleModeCreate"));
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
container.add(lblInfo, "h 30px!, gap 0 0 0 5px, al center");
......
......@@ -10,6 +10,7 @@ import forge.net.event.UpdateLobbyPlayerEvent;
import forge.screens.home.*;
import forge.toolbox.FList;
import forge.toolbox.FScrollPane;
import forge.util.Localizer;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
......@@ -25,7 +26,8 @@ public enum VSubmenuPuzzleSolve implements IVSubmenu<CSubmenuPuzzleSolve> {
private final StartButton btnStart = new StartButton();
private DragCell parentCell;
private final DragTab tab = new DragTab("Puzzle Mode: Solve");
final Localizer localizer = Localizer.getInstance();
private final DragTab tab = new DragTab(localizer.getMessage("lblPuzzleModeSolve"));
private final GameLobby lobby = new LocalLobby();
private final VLobby vLobby = new VLobby(lobby);
......@@ -52,7 +54,8 @@ public enum VSubmenuPuzzleSolve implements IVSubmenu<CSubmenuPuzzleSolve> {
@Override
public String getMenuTitle() {
return "Solve";
final Localizer localizer = Localizer.getInstance();
return localizer.getMessage("lblSolve");
}
@Override
......@@ -103,7 +106,8 @@ public enum VSubmenuPuzzleSolve implements IVSubmenu<CSubmenuPuzzleSolve> {
container.removeAll();
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
vLobby.getLblTitle().setText("Puzzle Mode: Solve");
final Localizer localizer = Localizer.getInstance();
vLobby.getLblTitle().setText(localizer.getMessage("lblPuzzleModeSolve"));
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
puzzleList.setModel(model);
container.add(puzzleListPane, "w 80%, h 80%, gap 0 0 0px 0px, span 2, al center");
......
......@@ -44,7 +44,7 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
private final DragTab tab = new DragTab(localizer.getMessage("lblQuestData"));
private final FLabel lblTitle = new FLabel.Builder()
.text("Load Quest Data").fontAlign(SwingConstants.CENTER)
.text(localizer.getMessage("lblLoadQuestData")).fontAlign(SwingConstants.CENTER)
.opaque(true).fontSize(16).build();
private final FLabel lblTitleNew = new FLabel.Builder().text(localizer.getMessage("lblStartanewQuest")).opaque(true).fontSize(16).build();
......
......@@ -70,7 +70,7 @@ public enum VSubmenuQuestDraft implements IVSubmenu<CSubmenuQuestDraft>, IQuestT
private final JPanel pnlStats = new JPanel();
private final FLabel lblPastResults = new FLabel.Builder()
.text("Past Results:")
.text(localizer.getMessage("lblPastResults") +":")
.fontSize(19).build();
private final FLabel lblFirst = new FLabel.Builder()
.fontSize(15).build();
......
......@@ -107,6 +107,7 @@ public enum CSubmenuPreferences implements ICDoc {
lstControls.add(Pair.of(view.getCbRandomArtInPools(), FPref.UI_RANDOM_ART_IN_POOLS));
lstControls.add(Pair.of(view.getCbEnforceDeckLegality(), FPref.ENFORCE_DECK_LEGALITY));
lstControls.add(Pair.of(view.getCbPerformanceMode(), FPref.PERFORMANCE_MODE));
lstControls.add(Pair.of(view.getCbSideboardForAI(), FPref.MATCH_SIDEBOARD_FOR_AI));
lstControls.add(Pair.of(view.getCbFilteredHands(), FPref.FILTERED_HANDS));
lstControls.add(Pair.of(view.getCbCloneImgSource(), FPref.UI_CLONE_MODE_SOURCE));
lstControls.add(Pair.of(view.getCbRemoveSmall(), FPref.DECKGEN_NOSMALL));
......@@ -147,7 +148,7 @@ public enum CSubmenuPreferences implements ICDoc {
for(final Pair<JCheckBox, FPref> kv : lstControls) {
kv.getKey().addItemListener(new ItemListener() {
kv.getKey().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent arg0) {
if (updating) { return; }
......
......@@ -75,6 +75,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final JCheckBox cbLoadHistoricFormats = new OptionsCheckBox(localizer.getMessage("cbLoadHistoricFormats"));
private final JCheckBox cbWorkshopSyntax = new OptionsCheckBox(localizer.getMessage("cbWorkshopSyntax"));
private final JCheckBox cbEnforceDeckLegality = new OptionsCheckBox(localizer.getMessage("cbEnforceDeckLegality"));
private final JCheckBox cbSideboardForAI = new OptionsCheckBox(localizer.getMessage("cbSideboardForAI"));
private final JCheckBox cbPerformanceMode = new OptionsCheckBox(localizer.getMessage("cbPerformanceMode"));
private final JCheckBox cbFilteredHands = new OptionsCheckBox(localizer.getMessage("cbFilteredHands"));
private final JCheckBox cbImageFetcher = new OptionsCheckBox(localizer.getMessage("cbImageFetcher"));
......@@ -195,6 +196,9 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbPerformanceMode, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlPerformanceMode")), descriptionConstraints);
pnlPrefs.add(cbSideboardForAI, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSideboardForAI")), descriptionConstraints);
pnlPrefs.add(cbFilteredHands, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlFilteredHands")), descriptionConstraints);
......@@ -680,6 +684,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
return cbPerformanceMode;
}
public JCheckBox getCbSideboardForAI() {
return cbSideboardForAI;
}
/** @return {@link javax.swing.JCheckBox} */
public JCheckBox getCbFilteredHands() {
return cbFilteredHands;
......
......@@ -1022,8 +1022,8 @@ public final class CMatchUI
}
@Override
public List<PaperCard> sideboard(final CardPool sideboard, final CardPool main) {
return GuiChoose.sideboard(this, sideboard.toFlatList(), main.toFlatList());
public List<PaperCard> sideboard(final CardPool sideboard, final CardPool main, final String message) {
return GuiChoose.sideboard(this, sideboard.toFlatList(), main.toFlatList(), message);
}
@Override
......
......@@ -112,7 +112,7 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
return null; // refused to side
}
......
......@@ -23,8 +23,8 @@ public class FSideboardDialog extends FDialog {
private final SideboardTabs tabs;
private final Callback<List<PaperCard>> callback;
public FSideboardDialog(CardPool sideboard, CardPool main, final Callback<List<PaperCard>> callback0) {
super("Update main deck from sideboard", 1);
public FSideboardDialog(CardPool sideboard, CardPool main, final Callback<List<PaperCard>> callback0, String message) {
super("Update main deck from sideboard" + message, 1);
callback = callback0;
tabs = add(new SideboardTabs(sideboard, main));
......
......@@ -481,11 +481,11 @@ public class MatchController extends AbstractGuiGame {
}
@Override
public List<PaperCard> sideboard(final CardPool sideboard, final CardPool main) {
public List<PaperCard> sideboard(final CardPool sideboard, final CardPool main, final String message) {
return new WaitCallback<List<PaperCard>>() {
@Override
public void run() {
final FSideboardDialog sideboardDialog = new FSideboardDialog(sideboard, main, this);
final FSideboardDialog sideboardDialog = new FSideboardDialog(sideboard, main, this, message);
sideboardDialog.show();
}
}.invokeAndWait();
......
......@@ -121,6 +121,10 @@ public class SettingsPage extends TabPage<SettingsScreen> {
"Performance Mode",
"Disables additional static abilities checks to speed up the game engine. (Warning: breaks some 'as if had flash' scenarios when casting cards owned by opponents)."),
1);
lstSettings.addItem(new BooleanSetting(FPref.MATCH_SIDEBOARD_FOR_AI,
"Human Sideboard for AI",
"Allows users to sideboard with the AIs deck and sideboard in constructed game formats."),
1);
lstSettings.addItem(new BooleanSetting(FPref.FILTERED_HANDS,
"Filtered Hands",
"Generates two starting hands and keeps the one with the closest to average land count for the deck. (Requires restart)"),
......
......@@ -80,4 +80,4 @@ Guilds of Ravnica, 3/6/GRN, GRN
Ultimate Masters, 3/6/M19, UMA
Ravnica Allegiance, 3/6/RNA, RNA
War of the Spark, 3/6/WAR, WAR
Modern Horizons, 3/6/WAR, MH1
\ No newline at end of file
Modern Horizons, 3/6/MH1, MH1
\ No newline at end of file
......@@ -1442,4 +1442,19 @@ Guildpact Informant
Jace's Projection
Jace's Ruse
Simic Guildgate|WAR
Tezzeret, Master of the Bridge
\ No newline at end of file
Tezzeret, Master of the Bridge
[MH1 Lands]
10 Snow-Covered Plains|MH1
10 Snow-Covered Island|MH1
10 Snow-Covered Swamp|MH1
10 Snow-Covered Mountain|MH1
10 Snow-Covered Forest|MH1
[MH1 Secret Cards]
Flusterstorm
Snow-Covered Plains
Snow-Covered Island
Snow-Covered Swamp
Snow-Covered Mountain
Snow-Covered Forest
\ No newline at end of file
......@@ -5,5 +5,6 @@ PT:4/4
K:Evoke:1 U U
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigBounce | TriggerDescription$ When CARDNAME enters the battlefield, return target nonland permanent to its owner's hand.
SVar:TrigBounce:DB$ChangeZone | ValidTgts$ Permanent.nonLand | TgtPrompt$ Choose target non-Land permanent. | Origin$ Battlefield | Destination$ Hand
SVar:NeedsToPlayEvoked:Card.nonLand+OppCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/aethersnipe.jpg
Oracle:When Aethersnipe enters the battlefield, return target nonland permanent to its owner's hand.\nEvoke {1}{U}{U} (You may cast this spell for its evoke cost. If you do, it's sacrificed when it enters the battlefield.)
......@@ -3,5 +3,6 @@ ManaCost:2 R
Types:Creature Zombie Minotaur Warrior
PT:2/2
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ First Strike | Condition$ PlayerTurn | Description$ As long as it's your turn, CARDNAME has first strike.
A:AB$ Pump | Cost$ 1 Sac<1/Creature.Other/another creature> | NumAtt$ +2 | SpellDescription$ CARDNAME gets +2/+0 until end of turn.
A:AB$ Pump | Cost$ 1 Sac<1/Creature.Other/another creature> | NumAtt$ +2 | AILogic$ Aristocrat | SpellDescription$ CARDNAME gets +2/+0 until end of turn.
SVar:AIPreference:SacCost$Creature.Other
Oracle:As long as it's your turn, Ahn-Crop Invader has first strike.\n{1}, Sacrifice another creature: Ahn-Crop Invader gets +2/+0 until end of turn.
Name:Balduvian Warlord
ManaCost:3 R
Types:Creature Human Barbarian
PT:3/2
A:AB$ RemoveFromCombat | Cost$ T | ActivationPhases$ Declare Blockers | ValidTgts$ Creature.blocking | Defined$ Targeted | UnblockCreaturesBlockedOnlyBy$ Targeted | SubAbility$ ChooseAttacker | SpellDescription$ Remove target blocking creature from combat. Creatures it was blocking that hadn’t become blocked by another creature this combat become unblocked.
SVar:ChooseAttacker:DB$ ChooseCard | Defined$ You | Choices$ Creature.attacking | ChoiceTitle$ Choose an attacker to block | RememberChosen$ True | | Mandatory$ True | SubAbility$ Block | SpellDescriptions$ None | StackDescription$ None
SVar:Block:DB$ Block | DefinedAttacker$ Remembered | DefinedBlocker$ ParentTarget | SpellDescription$ Then it blocks an attacking creature of your choice. Activate this ability only during the declare blockers step. | StackDescription$ SpellDescription | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:{T}: Remove target blocking creature from combat. Creatures it was blocking that hadn’t become blocked by another creature this combat become unblocked, then it blocks an attacking creature of your choice. Activate this ability only during the declare blockers step.
\ No newline at end of file
......@@ -6,6 +6,7 @@ K:Evoke:1 G
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ When CARDNAME enters the battlefield, target creature gets +3/+3 until end of turn.
SVar:TrigPump:DB$Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3
SVar:PlayMain1:TRUE
SVar:NeedsToPlayEvoked:Creature.YouCtrl
K:Flash
SVar:Picture:http://www.wizards.com/global/images/magic/general/briarhorn.jpg
Oracle:Flash\nWhen Briarhorn enters the battlefield, target creature gets +3/+3 until end of turn.\nEvoke {1}{G} (You may cast this spell for its evoke cost. If you do, it's sacrificed when it enters the battlefield.)
......@@ -2,9 +2,9 @@ Name:Casualties of War
ManaCost:2 B B G G
Types:Sorcery
A:SP$ Charm | Cost$ 2 B B G G | MinCharmNum$ 1 | CharmNum$ 5 | Choices$ DestroyArtifact,DestroyCreature,DestroyEnchantment,DestroyLand,DestroyPlaneswalker
SVar:DestroyArtifact:DB$ Destroy | ValidTgts$ Artifact | Tgtprompt$ Select target artifact | Spelldescription$ Destroy target artifact
SVar:DestroyCreature:DB$ Destroy | ValidTgts$ Creature | Tgtprompt$ Select target creature | Spelldescription$ Destroy target creature.
SVar:DestroyEnchantment:DB$ Destroy | ValidTgts$ Enchantment | Tgtprompt$ Select target Enchantment | Spelldescription$ Destroy target Enchantment.
SVar:DestroyLand:DB$ Destroy | ValidTgts$ Land | Tgtprompt$ Select target land | Spelldescription$ Destroy target land.
SVar:DestroyPlaneswalker:DB$ Destroy | ValidTgts$ Planeswalker | Tgtprompt$ Select target planeswalker | Spelldescription$ Destroy target planeswalker.
SVar:DestroyArtifact:DB$ Destroy | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact
SVar:DestroyCreature:DB$ Destroy | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature.
SVar:DestroyEnchantment:DB$ Destroy | ValidTgts$ Enchantment | TgtPrompt$ Select target Enchantment | SpellDescription$ Destroy target Enchantment.
SVar:DestroyLand:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Destroy target land.
SVar:DestroyPlaneswalker:DB$ Destroy | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | SpellDescription$ Destroy target planeswalker.
Oracle:Choose one or more —\n• Destroy target artifact.\n• Destroy target creature.\n• Destroy target enchantment.\n• Destroy target land.\n• Destroy target planeswalker.
......@@ -5,7 +5,8 @@ T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ TrigLife | OptionalDeci
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLife | TriggerZones$ Command | Secondary$ True | OptionalDecider$ You | TriggerDescription$ When you planeswalk to CARDNAME or at the beginning of your upkeep, you may exchange life totals with target player.
SVar:TrigLife:DB$ ExchangeLife | Optional$ True | ValidTgts$ Player | TgtPrompt$ Select target player to exchange life totals with
T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, exchange control of two target permanents that share a card type.
SVar:RolledChaos:DB$ ExchangeControl | ValidTgts$ Permanent | TgtPrompt$ Select target permanent card | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Land | TargetUnique$ True
SVar:RolledChaos:DB$ Pump | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SubAbility$ DBExchange
SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent card | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Land,Tribal | TargetUnique$ True
AI:RemoveDeck:All
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/cliffside_market.jpg
......
......@@ -10,4 +10,6 @@ SVar:DBChoose:DB$ ChooseCard | Choices$ Card.IsRemembered | ChoiceZone$ Graveyar
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Graveyard | Destination$ Hand
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenPlayer$ True | ClearChosenCard$ True | SubAbility$ DBExile
SVar:DBExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | StackDescription$ None
SVar:NeedsToPlayVar:Z GE3
SVar:Z:Count$ValidGraveyard Card.YouOwn
Oracle:Choose up to four target cards in your graveyard. If you control a Bolas planeswalker, return those cards to your hand. Otherwise, an opponent chooses two of them. Leave the chosen cards in your graveyard and put the rest into your hand.\nExile Deliver Unto Evil.
......@@ -4,7 +4,7 @@ Types:Legendary Planeswalker Domri
Loyalty:5
A:AB$ Mana | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Produced$ Combo R G | Amount$ 1 | AddsKeywords$ Riot | AddsKeywordsType$ Creature | SpellDescription$ Add {R} or {G}. If that mana is spent on a creature spell, it gains riot. (It enters the battlefield with your choice of a +1/+1 counter or haste.)
A:AB$ Dig | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ForceRevealToController$ True | DigNum$ 4 | ChangeNum$ 2 | Optional$ True | ChangeValid$ Creature | RestRandomOrder$ True | SpellDescription$ Look at the top four cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order.
A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Domri, Chaos Bringer | Image$ emblem_domri_chaos_bringer | Triggers$ EffPhase | SVars$ EmblemTrigToken | Duration$ Permanent | SpellDescription$ You get an emblem with “At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.”
A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Domri, Chaos Bringer | Image$ emblem_domri_chaos_bringer | Triggers$ EffPhase | SVars$ EmblemTrigToken | Duration$ Permanent | SpellDescription$ You get an emblem with "At the beginning of each end step, create a 4/4 red and green Beast creature token with trample."
SVar:EffPhase:Mode$ Phase | Phase$ End of Turn | Execute$ EmblemTrigToken | TriggerDescription$ At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.
SVar:EmblemTrigToken:DB$ Token | TokenOwner$ You | TokenAmount$ 1 | TokenScript$ rg_4_4_beast_trample | LegacyImage$ rg 4 4 beast trample rna
Oracle:+1: Add {R} or {G}. If that mana is spent on a creature spell, it gains riot. (It enters the battlefield with your choice of a +1/+1 counter or haste.)\n−3: Look at the top four cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order.\n−8: You get an emblem with “At the beginning of each end step, create a 4/4 red and green Beast creature token with trample.”
Name:Enter the God-Eternals
ManaCost:2 U U B
Types:Sorcery
A:SP$ DealDamage | Cost$ 2 U U B | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SubAbility$ DBGainLife | StackDescription$ CARDNAME deals 4 damage to target creature.
A:SP$ DealDamage | Cost$ 2 U U B | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 4 damage to target creature and you gain life equal to the damage dealt this way. Target player puts the top four cards of their library into their graveyard. Amass 4. | StackDescription$ CARDNAME deals 4 damage to target creature.
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X | SubAbility$ DBMill | StackDescription$ You gain life equal to the damage dealt this way.
SVar:X:Count$TotalDamageDoneByThisTurn
SVar:DBMill:DB$ Mill | NumCards$ 4 | ValidTgts$ Player | TgtPrompt$ Choose target player | SubAbility$ DBAmass | StackDescription$ Target player puts the top four cards of their library into their graveyard.
......
......@@ -2,7 +2,7 @@ Name:Epochrasite
ManaCost:2
Types:Artifact Creature Construct
PT:1/1
K:etbCounter:P1P1:3:ValidCard$ Card.Self+wasNotCastFromHand:CARDNAME enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand.
K:etbCounter:P1P1:3:ValidLKI$ Card.Self+wasNotCastFromHand:CARDNAME enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand.
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile it with three time counters on it and it gains suspend.
SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile | SubAbility$ DBPutCounter | RememberChanged$ True
SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ TIME | CounterNum$ 3 | SubAbility$ GiveSuspend
......
Name:False Orders
ManaCost:R
Types:Instant
Text:Cast CARDNAME only during the declare blockers step.
A:SP$ RemoveFromCombat | Cost$ R | ActivationPhases$ Declare Blockers | ValidTgts$ Creature.DefendingPlayerCtrl | TgtPrompt$ Select target creature defending player controls | Defined$ Targeted | UnblockCreaturesBlockedOnlyBy$ Targeted | SubAbility$ ChooseAttacker | SpellDescription$ Remove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. | StackDescription$ SpellDescription
SVar:ChooseAttacker:DB$ ChooseCard | Defined$ You | Choices$ Creature.attacking | RememberChosen$ True | MinAmount$ 0 | ChoiceTitle$ Choose an attacker to block | SubAbility$ Block
SVar:Block:DB$ Block | DefinedAttacker$ Remembered | DefinedBlocker$ ParentTarget | SpellDescription$ You may have it block an attacking creature of your choice.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:Cast this spell only during the declare blockers step.\nRemove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice.
......@@ -6,5 +6,6 @@ K:Trample
K:Evoke:4 R
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy target land.
SVar:TrigDestroy:DB$Destroy | ValidTgts$ Land | TgtPrompt$ Select a land.
SVar:NeedsToPlayEvoked:Land.OppCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/faultgrinder.jpg
Oracle:Trample\nWhen Faultgrinder enters the battlefield, destroy target land.\nEvoke {4}{R} (You may cast this spell for its evoke cost. If you do, it's sacrificed when it enters the battlefield.)
Name:Finale of Devastation
ManaCost:X G G
Types:Sorcery
A:SP$ ChangeZone | Cost$ X G G | ChangeType$ Creature.YouCtrl+cmcLEX | Hidden$ True | Origin$ Library | OriginChoice$ True | OriginAlternative$ Graveyard | AlternativeMessage$ Would you like to search your library with this ability? If you do, your library will be shuffled. | Destination$ Battlefield | ShuffleNonMandatory$ True | SubAbility$ DBPump | SpellDescription$ Search your library and/or graveyard for a creature card with converted mana cost X or less and put it onto the battlefield. If you search your library this way, shuffle it. If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn.
A:SP$ ChangeZone | Cost$ X G G | ChangeType$ Creature.YouCtrl+cmcLEX | Hidden$ True | Origin$ Library | OriginChoice$ True | OriginAlternative$ Graveyard | AlternativeMessage$ Would you like to search your library with this ability? If you do, your library will be shuffled. | Destination$ Battlefield | ShuffleNonMandatory$ True | SubAbility$ DBPump | References$ X | SpellDescription$ Search your library and/or graveyard for a creature card with converted mana cost X or less and put it onto the battlefield. If you search your library this way, shuffle it. If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn.
SVar:DBPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Haste | NumAtt$ X | NumDef$ X | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
SVar:X:Count$xPaid
Oracle:Search your library and/or graveyard for a creature card with converted mana cost X or less and put it onto the battlefield. If you search your library this way, shuffle it. If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn.
......@@ -4,7 +4,7 @@ Types:Legendary Planeswalker Gideon
Loyalty:4
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | Condition$ PlayerTurn | SetPower$ 4 | SetToughness$ 4 | AddType$ Creature & Human & Soldier | AddKeyword$ Indestructible | Description$ As long as it's your turn, CARDNAME is a 4/4 Human Soldier creature with indestructible that's still a planeswalker.
R:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.Self | PlayerTurn$ True | Description$ Prevent all damage that would be dealt to CARDNAME during your turn.
A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select to one target creature you control | Choices$ DBVigilance,DBLifelink,DBIndestructible | Defined$ You | ConditionDefined$ Targeted | ConditionPresent$ Card | ConditionCompare$ GE1 | SpellDescription$ Up to one other target creature you control gains your choice of vigilance, lifelink, or indestructible until end of turn. | StackDescription$ SpellDescription
A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select up to one target creature you control | Choices$ DBVigilance,DBLifelink,DBIndestructible | Defined$ You | ConditionDefined$ Targeted | ConditionPresent$ Card | ConditionCompare$ GE1 | AILogic$ GideonBlackblade | SpellDescription$ Up to one other target creature you control gains your choice of vigilance, lifelink, or indestructible until end of turn. | StackDescription$ SpellDescription