Merge branch 'Card-Forge:master' into master

This commit is contained in:
TabletopGeneral
2023-01-08 11:47:30 -05:00
committed by GitHub
173 changed files with 1773 additions and 1316 deletions

View File

@@ -1063,6 +1063,10 @@ public class ComputerUtil {
return true; return true;
} }
if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) {
return true;
}
if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) { if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
// Planning to choose Haste for Riot, so do this in Main 1 // Planning to choose Haste for Riot, so do this in Main 1
return true; return true;
@@ -1070,6 +1074,7 @@ public class ComputerUtil {
// if we have non-persistent mana in our pool, would be good to try to use it and not waste it // if we have non-persistent mana in our pool, would be good to try to use it and not waste it
if (ai.getManaPool().willManaBeLostAtEndOfPhase()) { if (ai.getManaPool().willManaBeLostAtEndOfPhase()) {
// TODO should check if some will be kept and skip those
boolean canUseToPayCost = false; boolean canUseToPayCost = false;
for (byte color : ManaAtom.MANATYPES) { for (byte color : ManaAtom.MANATYPES) {
// tries to reuse any amount of colorless if cost only has generic // tries to reuse any amount of colorless if cost only has generic
@@ -1089,10 +1094,6 @@ public class ComputerUtil {
return true; return true;
} }
if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) {
return true;
}
//cast equipments in Main1 when there are creatures to equip and no other unequipped equipment //cast equipments in Main1 when there are creatures to equip and no other unequipped equipment
if (card.isEquipment()) { if (card.isEquipment()) {
boolean playNow = false; boolean playNow = false;

View File

@@ -93,6 +93,7 @@ public class ComputerUtilMana {
ability.setActivatingPlayer(card.getController(), true); ability.setActivatingPlayer(card.getController(), true);
if (ability.isManaAbility()) { if (ability.isManaAbility()) {
score += ability.calculateScoreForManaAbility(); score += ability.calculateScoreForManaAbility();
// TODO check TriggersWhenSpent
} }
else if (!ability.isTrigger() && ability.isPossible()) { else if (!ability.isTrigger() && ability.isPossible()) {
score += 13; //add 13 for any non-mana activated abilities score += 13; //add 13 for any non-mana activated abilities
@@ -393,9 +394,9 @@ public class ComputerUtilMana {
String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment); String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment);
//String originalProduced = manaProduced; //String originalProduced = manaProduced;
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromPlayer(ai); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(hostCard);
repParams.put(AbilityKey.Mana, manaProduced); repParams.put(AbilityKey.Mana, manaProduced);
repParams.put(AbilityKey.Affected, hostCard); repParams.put(AbilityKey.Activator, ai);
repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility
// TODO Damping Sphere might replace later? // TODO Damping Sphere might replace later?
@@ -1614,9 +1615,9 @@ public class ComputerUtilMana {
// setup produce mana replacement effects // setup produce mana replacement effects
String origin = mp.getOrigProduced(); String origin = mp.getOrigProduced();
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromPlayer(ai); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(sourceCard);
repParams.put(AbilityKey.Mana, origin); repParams.put(AbilityKey.Mana, origin);
repParams.put(AbilityKey.Affected, sourceCard); repParams.put(AbilityKey.Activator, ai);
repParams.put(AbilityKey.AbilityMana, m); // RootAbility repParams.put(AbilityKey.AbilityMana, m); // RootAbility
List<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); List<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);

View File

@@ -7,6 +7,7 @@ import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.google.common.collect.*; import com.google.common.collect.*;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import forge.StaticData; import forge.StaticData;
@@ -52,27 +53,24 @@ public abstract class GameState {
ZONES.put(ZoneType.Sideboard, "sideboard"); ZONES.put(ZoneType.Sideboard, "sideboard");
} }
private int humanLife = -1; static class PlayerState {
private int computerLife = -1; private int life = -1;
private String humanCounters = ""; private String counters = "";
private String computerCounters = ""; private String manaPool = "";
private String humanManaPool = ""; private String persistentMana = "";
private String computerManaPool = ""; private int landsPlayed = 0;
private String humanPersistentMana = ""; private int landsPlayedLastTurn = 0;
private String computerPersistentMana = ""; private String precast = null;
private int humanLandsPlayed = 0; private String putOnStack = null;
private int computerLandsPlayed = 0; private final Map<ZoneType, String> cardTexts = new EnumMap<>(ZoneType.class);
private int humanLandsPlayedLastTurn = 0; }
private int computerLandsPlayedLastTurn = 0; private final List<PlayerState> playerStates = new ArrayList<>();
private boolean puzzleCreatorState = false; private boolean puzzleCreatorState = false;
private final Map<ZoneType, String> humanCardTexts = new EnumMap<>(ZoneType.class);
private final Map<ZoneType, String> aiCardTexts = new EnumMap<>(ZoneType.class);
private final Map<Integer, Card> idToCard = new HashMap<>(); private final Map<Integer, Card> idToCard = new HashMap<>();
private final Map<Card, Integer> cardToAttachId = new HashMap<>(); private final Map<Card, Integer> cardToAttachId = new HashMap<>();
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>(); private final Map<Card, Player> cardToEnchantPlayerId = new HashMap<>();
private final Map<Card, Integer> markedDamage = new HashMap<>(); private final Map<Card, Integer> markedDamage = new HashMap<>();
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>(); private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>(); private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
@@ -98,12 +96,6 @@ public abstract class GameState {
private String tAdvancePhase = "NONE"; private String tAdvancePhase = "NONE";
private String precastHuman = null;
private String precastAI = null;
private String putOnStackHuman = null;
private String putOnStackAI = null;
private int turn = 1; private int turn = 1;
private boolean removeSummoningSickness = false; private boolean removeSummoningSickness = false;
@@ -134,32 +126,27 @@ public abstract class GameState {
sb.append("[state]\n"); sb.append("[state]\n");
} }
sb.append(TextUtil.concatNoSpace("humanlife=", String.valueOf(humanLife), "\n"));
sb.append(TextUtil.concatNoSpace("ailife=", String.valueOf(computerLife), "\n"));
sb.append(TextUtil.concatNoSpace("humanlandsplayed=", String.valueOf(humanLandsPlayed), "\n"));
sb.append(TextUtil.concatNoSpace("ailandsplayed=", String.valueOf(computerLandsPlayed), "\n"));
sb.append(TextUtil.concatNoSpace("humanlandsplayedlastturn=", String.valueOf(humanLandsPlayedLastTurn), "\n"));
sb.append(TextUtil.concatNoSpace("ailandsplayedlastturn=", String.valueOf(computerLandsPlayedLastTurn), "\n"));
sb.append(TextUtil.concatNoSpace("turn=", String.valueOf(turn), "\n")); sb.append(TextUtil.concatNoSpace("turn=", String.valueOf(turn), "\n"));
if (!humanCounters.isEmpty()) {
sb.append(TextUtil.concatNoSpace("humancounters=", humanCounters, "\n"));
}
if (!computerCounters.isEmpty()) {
sb.append(TextUtil.concatNoSpace("aicounters=", computerCounters, "\n"));
}
if (!humanManaPool.isEmpty()) {
sb.append(TextUtil.concatNoSpace("humanmanapool=", humanManaPool, "\n"));
}
if (!computerManaPool.isEmpty()) {
sb.append(TextUtil.concatNoSpace("aimanapool=", computerManaPool, "\n"));
}
sb.append(TextUtil.concatNoSpace("activeplayer=", tChangePlayer, "\n")); sb.append(TextUtil.concatNoSpace("activeplayer=", tChangePlayer, "\n"));
sb.append(TextUtil.concatNoSpace("activephase=", tChangePhase, "\n")); sb.append(TextUtil.concatNoSpace("activephase=", tChangePhase, "\n"));
appendCards(humanCardTexts, "human", sb);
appendCards(aiCardTexts, "ai", sb); int playerIndex = 0;
for (PlayerState p : playerStates) {
String prefix = "p" + playerIndex++;
sb.append(TextUtil.concatNoSpace(prefix + "life=", String.valueOf(p.life), "\n"));
sb.append(TextUtil.concatNoSpace(prefix + "landsplayed=", String.valueOf(p.landsPlayed), "\n"));
sb.append(TextUtil.concatNoSpace(prefix + "landsplayedlastturn=", String.valueOf(p.landsPlayedLastTurn), "\n"));
if (!p.counters.isEmpty()) {
sb.append(TextUtil.concatNoSpace(prefix + "counters=", p.counters, "\n"));
}
if (!p.manaPool.isEmpty()) {
sb.append(TextUtil.concatNoSpace(prefix + "manapool=", p.manaPool, "\n"));
}
if (!p.persistentMana.isEmpty()) {
sb.append(TextUtil.concatNoSpace(prefix + "persistentmana=", p.persistentMana, "\n"));
}
appendCards(p.cardTexts, prefix, sb);
}
return sb.toString(); return sb.toString();
} }
@@ -169,33 +156,21 @@ public abstract class GameState {
} }
} }
public void initFromGame(Game game) throws Exception { public void initFromGame(Game game) {
FCollectionView<Player> players = game.getPlayers(); playerStates.clear();
// Can only serialize a two player game with one AI and one human. for (Player player : game.getPlayers()) {
if (players.size() != 2) { PlayerState p = new PlayerState();
throw new Exception("Game not supported"); p.life = player.getLife();
p.landsPlayed = player.getLandsPlayedThisTurn();
p.landsPlayedLastTurn = player.getLandsPlayedLastTurn();
p.counters = countersToString(player.getCounters());
p.manaPool = processManaPool(player.getManaPool());
playerStates.add(p);
} }
final Player human = game.getPlayers().get(0);
final Player ai = game.getPlayers().get(1);
if (!human.getController().isGuiPlayer() || !ai.getController().isAI()) {
throw new Exception("Game not supported");
}
humanLife = human.getLife();
computerLife = ai.getLife();
humanLandsPlayed = human.getLandsPlayedThisTurn();
computerLandsPlayed = ai.getLandsPlayedThisTurn();
humanLandsPlayedLastTurn = human.getLandsPlayedLastTurn();
computerLandsPlayedLastTurn = ai.getLandsPlayedLastTurn();
humanCounters = countersToString(human.getCounters());
computerCounters = countersToString(ai.getCounters());
humanManaPool = processManaPool(human.getManaPool());
computerManaPool = processManaPool(ai.getManaPool());
tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human"; tChangePlayer = "p" + game.getPlayers().indexOf(game.getPhaseHandler().getPlayerTurn());
tChangePhase = game.getPhaseHandler().getPhase().toString(); tChangePhase = game.getPhaseHandler().getPhase().toString();
turn = game.getPhaseHandler().getTurn(); turn = game.getPhaseHandler().getTurn();
aiCardTexts.clear();
humanCardTexts.clear();
// Mark the cards that need their ID remembered for various reasons // Mark the cards that need their ID remembered for various reasons
cardsReferencedByID.clear(); cardsReferencedByID.clear();
@@ -238,8 +213,9 @@ public abstract class GameState {
for (ZoneType zone : ZONES.keySet()) { for (ZoneType zone : ZONES.keySet()) {
// Init texts to empty, so that restoring will clear the state // Init texts to empty, so that restoring will clear the state
// if the zone had no cards in it (e.g. empty hand). // if the zone had no cards in it (e.g. empty hand).
aiCardTexts.put(zone, ""); for (PlayerState p : playerStates) {
humanCardTexts.put(zone, ""); p.cardTexts.put(zone, "");
}
for (Card card : game.getCardsIncludePhasingIn(zone)) { for (Card card : game.getCardsIncludePhasingIn(zone)) {
if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) { if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) {
puzzleCreatorState = true; puzzleCreatorState = true;
@@ -247,18 +223,35 @@ public abstract class GameState {
if (card instanceof DetachedCardEffect) { if (card instanceof DetachedCardEffect) {
continue; continue;
} }
addCard(zone, card.getController() == ai ? aiCardTexts : humanCardTexts, card); int playerIndex = game.getPlayers().indexOf(card.getController());
addCard(zone, playerStates.get(playerIndex).cardTexts, card);
} }
} }
} }
private String getPlayerString(Player p) {
return "P" + p.getGame().getPlayers().indexOf(p);
}
private Player parsePlayerString(Game game, String str) {
if (str.equalsIgnoreCase("HUMAN")) {
return game.getPlayers().get(0);
} else if (str.equalsIgnoreCase("AI")) {
return game.getPlayers().get(1);
} else if (str.startsWith("P") && Character.isDigit(str.charAt(1))) {
return game.getPlayers().get(Integer.parseInt(String.valueOf(str.charAt(1))));
} else {
return game.getPlayers().get(0);
}
}
private void addCard(ZoneType zoneType, Map<ZoneType, String> cardTexts, Card c) { private void addCard(ZoneType zoneType, Map<ZoneType, String> cardTexts, Card c) {
StringBuilder newText = new StringBuilder(cardTexts.get(zoneType)); StringBuilder newText = new StringBuilder(cardTexts.get(zoneType));
if (newText.length() > 0) { if (newText.length() > 0) {
newText.append(";"); newText.append(";");
} }
if (c.isToken()) { if (c.isToken()) {
newText.append("t:").append(new TokenInfo(c).toString()); newText.append("t:").append(new TokenInfo(c));
} else { } else {
if (c.getPaperCard() == null) { if (c.getPaperCard() == null) {
return; return;
@@ -281,8 +274,7 @@ public abstract class GameState {
if (zoneType == ZoneType.Battlefield) { if (zoneType == ZoneType.Battlefield) {
if (c.getOwner() != c.getController()) { if (c.getOwner() != c.getController()) {
// TODO: Handle more than 2-player games. newText.append("|Owner:").append(getPlayerString(c.getOwner()));
newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human"));
} }
if (c.isTapped()) { if (c.isTapped()) {
newText.append("|Tapped"); newText.append("|Tapped");
@@ -298,7 +290,7 @@ public abstract class GameState {
} }
if (c.isPhasedOut()) { if (c.isPhasedOut()) {
newText.append("|PhasedOut:"); newText.append("|PhasedOut:");
newText.append(c.getPhasedOut().isAI() ? "AI" : "HUMAN"); newText.append(getPlayerString(c.getPhasedOut()));
} }
if (c.isFaceDown()) { if (c.isFaceDown()) {
newText.append("|FaceDown"); newText.append("|FaceDown");
@@ -319,10 +311,8 @@ public abstract class GameState {
} }
if (c.getPlayerAttachedTo() != null) { if (c.getPlayerAttachedTo() != null) {
// TODO: improve this for game states with more than two players
newText.append("|EnchantingPlayer:"); newText.append("|EnchantingPlayer:");
Player p = c.getPlayerAttachedTo(); newText.append(getPlayerString(c.getPlayerAttachedTo()));
newText.append(p.getController().isAI() ? "AI" : "HUMAN");
} else if (c.isAttachedToEntity()) { } else if (c.isAttachedToEntity()) {
newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId()); newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId());
} }
@@ -348,11 +338,8 @@ public abstract class GameState {
} }
List<String> chosenCardIds = Lists.newArrayList(); List<String> chosenCardIds = Lists.newArrayList();
for (Object obj : c.getChosenCards()) { for (Card obj : c.getChosenCards()) {
if (obj instanceof Card) { chosenCardIds.add(String.valueOf(obj.getId()));
int id = ((Card)obj).getId();
chosenCardIds.add(String.valueOf(id));
}
} }
if (!chosenCardIds.isEmpty()) { if (!chosenCardIds.isEmpty()) {
newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ",")); newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ","));
@@ -465,16 +452,36 @@ public abstract class GameState {
public void parse(InputStream in) throws Exception { public void parse(InputStream in) throws Exception {
final BufferedReader br = new BufferedReader(new InputStreamReader(in)); final BufferedReader br = new BufferedReader(new InputStreamReader(in));
parse(br.lines());
String line;
while ((line = br.readLine()) != null) {
parseLine(line);
}
} }
public void parse(List<String> lines) { public void parse(List<String> lines) {
for (String line : lines) { parse(lines.stream());
parseLine(line); }
public void parse(Stream<String> lines) {
playerStates.clear();
lines.forEach(this::parseLine);
}
private PlayerState getPlayerState(int index) {
while (index >= playerStates.size()) {
playerStates.add(new PlayerState());
}
return playerStates.get(index);
}
private PlayerState getPlayerState(String key) {
if (key.startsWith("human")) {
return getPlayerState(0);
} else if (key.startsWith("ai")) {
return getPlayerState(1);
} else if (key.startsWith("p") && Character.isDigit(key.charAt(1))) {
return getPlayerState(Integer.parseInt(String.valueOf(key.charAt(1))));
} else {
System.err.println("Unknown player state key: " + key);
return new PlayerState();
} }
} }
@@ -495,142 +502,56 @@ public abstract class GameState {
return; return;
} }
boolean isHuman = categoryName.startsWith("human");
if (categoryName.equals("turn")) { if (categoryName.equals("turn")) {
turn = Integer.parseInt(categoryValue); turn = Integer.parseInt(categoryValue);
} } else if (categoryName.equals("removesummoningsickness")) {
else if (categoryName.equals("removesummoningsickness")) {
removeSummoningSickness = categoryValue.equalsIgnoreCase("true"); removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
} } else if (categoryName.endsWith("life")) {
getPlayerState(categoryName).life = Integer.parseInt(categoryValue);
else if (categoryName.endsWith("life")) { } else if (categoryName.endsWith("counters")) {
if (isHuman) getPlayerState(categoryName).counters = categoryValue;
humanLife = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("landsplayed")) {
else getPlayerState(categoryName).landsPlayed = Integer.parseInt(categoryValue);
computerLife = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("landsplayedlastturn")) {
} getPlayerState(categoryName).landsPlayedLastTurn = Integer.parseInt(categoryValue);
} else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
else if (categoryName.endsWith("counters")) { getPlayerState(categoryName).cardTexts.put(ZoneType.Battlefield, categoryValue);
if (isHuman) } else if (categoryName.endsWith("hand")) {
humanCounters = categoryValue; getPlayerState(categoryName).cardTexts.put(ZoneType.Hand, categoryValue);
else } else if (categoryName.endsWith("graveyard")) {
computerCounters = categoryValue; getPlayerState(categoryName).cardTexts.put(ZoneType.Graveyard, categoryValue);
} } else if (categoryName.endsWith("library")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Library, categoryValue);
else if (categoryName.endsWith("landsplayed")) { } else if (categoryName.endsWith("exile")) {
if (isHuman) getPlayerState(categoryName).cardTexts.put(ZoneType.Exile, categoryValue);
humanLandsPlayed = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("command")) {
else getPlayerState(categoryName).cardTexts.put(ZoneType.Command, categoryValue);
computerLandsPlayed = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("sideboard")) {
} getPlayerState(categoryName).cardTexts.put(ZoneType.Sideboard, categoryValue);
} else if (categoryName.startsWith("ability")) {
else if (categoryName.endsWith("landsplayedlastturn")) {
if (isHuman)
humanLandsPlayedLastTurn = Integer.parseInt(categoryValue);
else
computerLandsPlayedLastTurn = Integer.parseInt(categoryValue);
}
else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
if (isHuman)
humanCardTexts.put(ZoneType.Battlefield, categoryValue);
else
aiCardTexts.put(ZoneType.Battlefield, categoryValue);
}
else if (categoryName.endsWith("hand")) {
if (isHuman)
humanCardTexts.put(ZoneType.Hand, categoryValue);
else
aiCardTexts.put(ZoneType.Hand, categoryValue);
}
else if (categoryName.endsWith("graveyard")) {
if (isHuman)
humanCardTexts.put(ZoneType.Graveyard, categoryValue);
else
aiCardTexts.put(ZoneType.Graveyard, categoryValue);
}
else if (categoryName.endsWith("library")) {
if (isHuman)
humanCardTexts.put(ZoneType.Library, categoryValue);
else
aiCardTexts.put(ZoneType.Library, categoryValue);
}
else if (categoryName.endsWith("exile")) {
if (isHuman)
humanCardTexts.put(ZoneType.Exile, categoryValue);
else
aiCardTexts.put(ZoneType.Exile, categoryValue);
}
else if (categoryName.endsWith("command")) {
if (isHuman)
humanCardTexts.put(ZoneType.Command, categoryValue);
else
aiCardTexts.put(ZoneType.Command, categoryValue);
}
else if (categoryName.endsWith("sideboard")) {
if (isHuman)
humanCardTexts.put(ZoneType.Sideboard, categoryValue);
else
aiCardTexts.put(ZoneType.Sideboard, categoryValue);
}
else if (categoryName.startsWith("ability")) {
abilityString.put(categoryName.substring("ability".length()), categoryValue); abilityString.put(categoryName.substring("ability".length()), categoryValue);
} } else if (categoryName.endsWith("precast")) {
getPlayerState(categoryName).precast = categoryValue;
else if (categoryName.endsWith("precast")) { } else if (categoryName.endsWith("putonstack")) {
if (isHuman) getPlayerState(categoryName).putOnStack = categoryValue;
precastHuman = categoryValue; } else if (categoryName.endsWith("manapool")) {
else getPlayerState(categoryName).manaPool = categoryValue;
precastAI = categoryValue; } else if (categoryName.endsWith("persistentmana")) {
} getPlayerState(categoryName).persistentMana = categoryValue;
} else {
else if (categoryName.endsWith("putonstack")) { System.err.println("Unknown key: " + categoryName);
if (isHuman)
putOnStackHuman = categoryValue;
else
putOnStackAI = categoryValue;
}
else if (categoryName.endsWith("manapool")) {
if (isHuman)
humanManaPool = categoryValue;
else
computerManaPool = categoryValue;
}
else if (categoryName.endsWith("persistentmana")) {
if (isHuman)
humanPersistentMana = categoryValue;
else
computerPersistentMana = categoryValue;
}
else {
System.out.println("Unknown key: " + categoryName);
} }
} }
public void applyToGame(final Game game) { public void applyToGame(final Game game) {
game.getAction().invoke(new Runnable() { game.getAction().invoke(() -> applyGameOnThread(game));
@Override
public void run() {
applyGameOnThread(game);
}
});
} }
protected void applyGameOnThread(final Game game) { protected void applyGameOnThread(final Game game) {
final Player human = game.getPlayers().get(0); if (game.getPlayers().size() != playerStates.size()) {
final Player ai = game.getPlayers().get(1); throw new RuntimeException("Non-matching number of players, (" +
game.getPlayers().size() + " vs. " + playerStates.size() + ")");
}
idToCard.clear(); idToCard.clear();
cardToAttachId.clear(); cardToAttachId.clear();
@@ -647,32 +568,21 @@ public abstract class GameState {
cardToScript.clear(); cardToScript.clear();
cardAttackMap.clear(); cardAttackMap.clear();
Player newPlayerTurn = tChangePlayer.equalsIgnoreCase("human") ? human : tChangePlayer.equalsIgnoreCase("ai") ? ai : null; int playerTurn = playerStates.indexOf(getPlayerState(tChangePlayer));
Player newPlayerTurn = game.getPlayers().get(playerTurn);
PhaseType newPhase = tChangePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase); PhaseType newPhase = tChangePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase);
PhaseType advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase); PhaseType advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase);
// Set stack to resolving so things won't trigger/effects be checked right away // Set stack to resolving so things won't trigger/effects be checked right away
game.getStack().setResolving(true); game.getStack().setResolving(true);
updateManaPool(human, humanManaPool, true, false);
updateManaPool(ai, computerManaPool, true, false);
updateManaPool(human, humanPersistentMana, false, true);
updateManaPool(ai, computerPersistentMana, false, true);
if (!humanCounters.isEmpty()) {
applyCountersToGameEntity(human, humanCounters);
}
if (!computerCounters.isEmpty()) {
applyCountersToGameEntity(ai, computerCounters);
}
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn, turn); game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn, turn);
game.getTriggerHandler().setSuppressAllTriggers(true); game.getTriggerHandler().setSuppressAllTriggers(true);
setupPlayerState(humanLife, humanCardTexts, human, humanLandsPlayed, humanLandsPlayedLastTurn); for (int i = 0; i < playerStates.size(); i++) {
setupPlayerState(computerLife, aiCardTexts, ai, computerLandsPlayed, computerLandsPlayedLastTurn); setupPlayerState(game.getPlayers().get(i), playerStates.get(i));
}
handleCardAttachments(); handleCardAttachments();
handleChosenEntities(); handleChosenEntities();
handleRememberedEntities(); handleRememberedEntities();
@@ -712,10 +622,11 @@ public abstract class GameState {
// Set negative or zero life after state effects if need be, important for some puzzles that rely on // Set negative or zero life after state effects if need be, important for some puzzles that rely on
// pre-setting negative life (e.g. PS_NEO4). // pre-setting negative life (e.g. PS_NEO4).
if (humanLife <= 0) { for (int i = 0; i < playerStates.size(); i++) {
human.setLife(humanLife, null); int life = playerStates.get(i).life;
} else if (computerLife <= 0) { if (life <= 0) {
ai.setLife(computerLife, null); game.getPlayers().get(i).setLife(life, null);
}
} }
} }
@@ -746,12 +657,7 @@ public abstract class GameState {
produced.put("PersistentMana", "True"); produced.put("PersistentMana", "True");
} }
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced); final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
game.getAction().invoke(new Runnable() { game.getAction().invoke(() -> abMana.produceMana(null));
@Override
public void run() {
abMana.produceMana(null);
}
});
} }
} }
@@ -832,8 +738,7 @@ public abstract class GameState {
} }
private int parseTargetInScript(final String tgtDef) { private int parseTargetInScript(final String tgtDef) {
int tgtID = TARGET_NONE; int tgtID;
if (tgtDef.equalsIgnoreCase("human")) { if (tgtDef.equalsIgnoreCase("human")) {
tgtID = TARGET_HUMAN; tgtID = TARGET_HUMAN;
} else if (tgtDef.equalsIgnoreCase("ai")) { } else if (tgtDef.equalsIgnoreCase("ai")) {
@@ -972,37 +877,23 @@ public abstract class GameState {
} }
private void handlePrecastSpells(final Game game) { private void handlePrecastSpells(final Game game) {
Player human = game.getPlayers().get(0); for (int i = 0; i < playerStates.size(); i++) {
Player ai = game.getPlayers().get(1); if (playerStates.get(i).precast != null) {
String[] spellList = TextUtil.split(playerStates.get(i).precast, ';');
if (precastHuman != null) { for (String spell : spellList) {
String[] spellList = TextUtil.split(precastHuman, ';'); precastSpellFromCard(spell, game.getPlayers().get(i), game);
for (String spell : spellList) { }
precastSpellFromCard(spell, human, game);
}
}
if (precastAI != null) {
String[] spellList = TextUtil.split(precastAI, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, ai, game);
} }
} }
} }
private void handleAddSAsToStack(final Game game) { private void handleAddSAsToStack(final Game game) {
Player human = game.getPlayers().get(0); for (int i = 0; i < playerStates.size(); i++) {
Player ai = game.getPlayers().get(1); if (playerStates.get(i).putOnStack != null) {
String[] spellList = TextUtil.split(playerStates.get(i).putOnStack, ';');
if (putOnStackHuman != null) { for (String spell : spellList) {
String[] spellList = TextUtil.split(putOnStackHuman, ';'); precastSpellFromCard(spell, game.getPlayers().get(i), game, true);
for (String spell : spellList) { }
precastSpellFromCard(spell, human, game, true);
}
}
if (putOnStackAI != null) {
String[] spellList = TextUtil.split(putOnStackAI, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, ai, game, true);
} }
} }
} }
@@ -1138,14 +1029,9 @@ public abstract class GameState {
} }
} }
// Enchant players by ID // Enchant players
for (Entry<Card, Integer> entry : cardToEnchantPlayerId.entrySet()) { for (Entry<Card, Player> entry : cardToEnchantPlayerId.entrySet()) {
// TODO: improve this for game states with more than two players entry.getKey().attachToEntity(entry.getValue(), null);
Card attacher = entry.getKey();
Game game = attacher.getGame();
Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0);
attacher.attachToEntity(attachedTo, null);
} }
} }
@@ -1184,7 +1070,7 @@ public abstract class GameState {
top.removeCloneState(top.getMutatedTimestamp()); top.removeCloneState(top.getMutatedTimestamp());
} }
final Long ts = game.getNextTimestamp(); final long ts = game.getNextTimestamp();
top.setMutatedTimestamp(ts); top.setMutatedTimestamp(ts);
if (top.getCurrentStateName() != CardStateName.FaceDown) { if (top.getCurrentStateName() != CardStateName.FaceDown) {
final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(top, null/*FIXME*/); final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(top, null/*FIXME*/);
@@ -1207,7 +1093,7 @@ public abstract class GameState {
} }
} }
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p, final int landsPlayed, final int landsPlayedLastTurn) { private void setupPlayerState(final Player p, final PlayerState state) {
// Lock check static as we setup player state // Lock check static as we setup player state
// Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up
@@ -1219,14 +1105,14 @@ public abstract class GameState {
p.setCommanders(Lists.newArrayList()); p.setCommanders(Lists.newArrayList());
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<>(ZoneType.class); Map<ZoneType, CardCollectionView> playerCards = new EnumMap<>(ZoneType.class);
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) { for (Entry<ZoneType, String> kv : state.cardTexts.entrySet()) {
String value = kv.getValue(); String value = kv.getValue();
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p)); playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
} }
if (life >= 0) p.setLife(life, null); if (state.life >= 0) p.setLife(state.life, null);
p.setLandsPlayedThisTurn(landsPlayed); p.setLandsPlayedThisTurn(state.landsPlayed);
p.setLandsPlayedLastTurn(landsPlayedLastTurn); p.setLandsPlayedLastTurn(state.landsPlayedLastTurn);
p.clearPaidForSA(); p.clearPaidForSA();
@@ -1275,6 +1161,13 @@ public abstract class GameState {
for (Card cmd : p.getCommanders()) { for (Card cmd : p.getCommanders()) {
p.getZone(ZoneType.Command).add(Player.createCommanderEffect(p.getGame(), cmd)); p.getZone(ZoneType.Command).add(Player.createCommanderEffect(p.getGame(), cmd));
} }
updateManaPool(p, state.manaPool, true, false);
updateManaPool(p, state.persistentMana, false, true);
if (!state.counters.isEmpty()) {
applyCountersToGameEntity(p, state.counters);
}
} }
/** /**
@@ -1330,9 +1223,7 @@ public abstract class GameState {
c.setMonstrous(true); c.setMonstrous(true);
} else if (info.startsWith("PhasedOut")) { } else if (info.startsWith("PhasedOut")) {
String tgt = info.substring(info.indexOf(':') + 1); String tgt = info.substring(info.indexOf(':') + 1);
Player human = player.getGame().getPlayers().get(0); c.setPhasedOut(parsePlayerString(player.getGame(), tgt));
Player ai = player.getGame().getPlayers().get(1);
c.setPhasedOut(tgt.equalsIgnoreCase("AI") ? ai : human);
} else if (info.startsWith("Counters:")) { } else if (info.startsWith("Counters:")) {
applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1)); applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("SummonSick")) { } else if (info.startsWith("SummonSick")) {
@@ -1377,16 +1268,12 @@ public abstract class GameState {
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1)); int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
cardToAttachId.put(c, id); cardToAttachId.put(c, id);
} else if (info.startsWith("EnchantingPlayer:")) { } else if (info.startsWith("EnchantingPlayer:")) {
// TODO: improve this for game states with more than two players
String tgt = info.substring(info.indexOf(':') + 1); String tgt = info.substring(info.indexOf(':') + 1);
cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN); cardToEnchantPlayerId.put(c, parsePlayerString(player.getGame(), tgt));
} else if (info.startsWith("Owner:")) { } else if (info.startsWith("Owner:")) {
// TODO: improve this for game states with more than two players
Player human = player.getGame().getPlayers().get(0);
Player ai = player.getGame().getPlayers().get(1);
String owner = info.substring(info.indexOf(':') + 1); String owner = info.substring(info.indexOf(':') + 1);
Player controller = c.getController(); Player controller = c.getController();
c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human); c.setOwner(parsePlayerString(player.getGame(), owner));
c.setController(controller, c.getGame().getNextTimestamp()); c.setController(controller, c.getGame().getNextTimestamp());
} else if (info.startsWith("Ability:")) { } else if (info.startsWith("Ability:")) {
String abString = info.substring(info.indexOf(':') + 1).toLowerCase(); String abString = info.substring(info.indexOf(':') + 1).toLowerCase();

View File

@@ -115,8 +115,7 @@ public class EffectAi extends SpellAbilityAi {
} }
randomReturn = true; randomReturn = true;
} else if (logic.equals("ChainVeil")) { } else if (logic.equals("ChainVeil")) {
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) || ai.getPlaneswalkersInPlay().isEmpty()) {
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
return false; return false;
} }
randomReturn = true; randomReturn = true;

View File

@@ -266,6 +266,7 @@ public class ManaEffectAi extends SpellAbilityAi {
ManaPool mp = ai.getManaPool(); ManaPool mp = ai.getManaPool();
Mana test = null; Mana test = null;
if (mp.isEmpty()) { if (mp.isEmpty()) {
// TODO use color from ability
test = new Mana((byte) ManaAtom.COLORLESS, source, null); test = new Mana((byte) ManaAtom.COLORLESS, source, null);
mp.addMana(test, false); mp.addMana(test, false);
} }

View File

@@ -101,7 +101,7 @@ public class ManifestAi extends SpellAbilityAi {
repParams.put(AbilityKey.Origin, card.getZone().getZoneType()); repParams.put(AbilityKey.Origin, card.getZone().getZoneType());
repParams.put(AbilityKey.Destination, ZoneType.Battlefield); repParams.put(AbilityKey.Destination, ZoneType.Battlefield);
repParams.put(AbilityKey.Source, sa.getHostCard()); repParams.put(AbilityKey.Source, sa.getHostCard());
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other); List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.CantHappen);
if (!list.isEmpty()) { if (!list.isEmpty()) {
return false; return false;
} }

View File

@@ -423,7 +423,7 @@ public class PumpAi extends PumpAiBase {
} }
if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
if (ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too if (!ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too
return false; return false;
} }
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);

View File

@@ -76,21 +76,23 @@ public class GameCopier {
for (RegisteredPlayer p : origPlayers) { for (RegisteredPlayer p : origPlayers) {
newPlayers.add(clonePlayer(p)); newPlayers.add(clonePlayer(p));
} }
GameRules currentRules = origGame.getRules(); GameRules currentRules = origGame.getRules();
Match newMatch = new Match(currentRules, newPlayers, origGame.getView().getTitle()); Match newMatch = new Match(currentRules, newPlayers, origGame.getView().getTitle());
Game newGame = new Game(newPlayers, currentRules, newMatch); Game newGame = new Game(newPlayers, currentRules, newMatch);
for (int i = 0; i < origGame.getPlayers().size(); i++) { for (int i = 0; i < origGame.getPlayers().size(); i++) {
Player origPlayer = origGame.getPlayers().get(i); Player origPlayer = origGame.getPlayers().get(i);
Player newPlayer = newGame.getPlayers().get(i); Player newPlayer = newGame.getPlayers().get(i);
newPlayer.setLife(origPlayer.getLife(), null); newPlayer.setLife(origPlayer.getLife(), null);
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++)
newPlayer.addSpellCastThisTurn();
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn()); newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn()); newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn()); newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn());
newPlayer.setLifeStartedThisTurnWith(origPlayer.getLifeStartedThisTurnWith());
newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn());
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setBlessing(origPlayer.hasBlessing()); newPlayer.setBlessing(origPlayer.hasBlessing());
newPlayer.setRevolt(origPlayer.hasRevolt()); newPlayer.setRevolt(origPlayer.hasRevolt());
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched()); newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
@@ -350,6 +352,7 @@ public class GameCopier {
newCard.setPTBoost(c.getPTBoostTable()); newCard.setPTBoost(c.getPTBoostTable());
// TODO copy by map // TODO copy by map
newCard.setDamage(c.getDamage()); newCard.setDamage(c.getDamage());
newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn());
newCard.setChangedCardColors(c.getChangedCardColorsTable()); newCard.setChangedCardColors(c.getChangedCardColorsTable());
newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable()); newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable());

View File

@@ -198,13 +198,10 @@ public class GameSimulator {
final SpellAbility playingSa = sa; final SpellAbility playingSa = sa;
simGame.copyLastState(); simGame.copyLastState();
boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() { boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, () -> {
@Override if (interceptor != null) {
public void run() { interceptor.announceX(playingSa);
if (interceptor != null) { interceptor.chooseTargets(playingSa, GameSimulator.this);
interceptor.announceX(playingSa);
interceptor.chooseTargets(playingSa, GameSimulator.this);
}
} }
}); });
if (!success) { if (!success) {

View File

@@ -1,5 +1,6 @@
package forge.ai.simulation; package forge.ai.simulation;
import forge.ai.ComputerUtilAbility;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@@ -16,7 +17,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class SpellAbilityChoicesIterator { public class SpellAbilityChoicesIterator {
private SimulationController controller; private final SimulationController controller;
private Iterator<int[]> modeIterator; private Iterator<int[]> modeIterator;
private int[] selectedModes; private int[] selectedModes;
@@ -34,11 +35,13 @@ public class SpellAbilityChoicesIterator {
Card selectedChoice; Card selectedChoice;
Score bestScoreForChoice = new Score(Integer.MIN_VALUE); Score bestScoreForChoice = new Score(Integer.MIN_VALUE);
} }
private ArrayList<ChoicePoint> choicePoints = new ArrayList<>(); private final ArrayList<ChoicePoint> choicePoints = new ArrayList<>();
private int incrementedCpIndex = 0; private int incrementedCpIndex = 0;
private int cpIndex = -1; private int cpIndex = -1;
private int evalDepth; private int evalDepth;
// Maps from filtered mode indexes to original ones.
private List<Integer> modesMap;
public SpellAbilityChoicesIterator(SimulationController controller) { public SpellAbilityChoicesIterator(SimulationController controller) {
this.controller = controller; this.controller = controller;
@@ -46,17 +49,28 @@ public class SpellAbilityChoicesIterator {
public List<AbilitySub> chooseModesForAbility(List<AbilitySub> choices, int min, int num, boolean allowRepeat) { public List<AbilitySub> chooseModesForAbility(List<AbilitySub> choices, int min, int num, boolean allowRepeat) {
if (modeIterator == null) { if (modeIterator == null) {
// TODO: Need to skip modes that are invalid (e.g. targets don't exist)! // Skip modes that don't have legal targets.
modesMap = new ArrayList<>();
int origIndex = -1;
for (AbilitySub sub : choices) {
origIndex++;
if (!ComputerUtilAbility.isFullyTargetable(sub)) {
continue;
}
modesMap.add(origIndex);
}
// TODO: Do we need to do something special to support cards that have extra costs // TODO: Do we need to do something special to support cards that have extra costs
// when choosing more modes, like Blessed Alliance? // when choosing more modes, like Blessed Alliance?
if (!allowRepeat) { if (modesMap.isEmpty()) {
modeIterator = CombinatoricsUtils.combinationsIterator(choices.size(), num); return null;
} else if (!allowRepeat) {
modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num);
} else { } else {
// Note: When allowRepeat is true, it does result in many possibilities being tried. // Note: When allowRepeat is true, it does result in many possibilities being tried.
// We should ideally prune some of those at a higher level. // We should ideally prune some of those at a higher level.
modeIterator = new AllowRepeatModesIterator(choices.size(), min, num); modeIterator = new AllowRepeatModesIterator(modesMap.size(), min, num);
} }
selectedModes = modeIterator.next(); selectedModes = remapModes(modeIterator.next());
advancedToNextMode = true; advancedToNextMode = true;
} }
// Note: If modeIterator already existed, selectedModes would have been updated in advance(). // Note: If modeIterator already existed, selectedModes would have been updated in advance().
@@ -78,6 +92,13 @@ public class SpellAbilityChoicesIterator {
return result; return result;
} }
private int[] remapModes(int[] modes) {
for (int i = 0; i < modes.length; i++) {
modes[i] = modesMap.get(modes[i]);
}
return modes;
}
public Card chooseCard(CardCollection fetchList) { public Card chooseCard(CardCollection fetchList) {
cpIndex++; cpIndex++;
if (cpIndex >= choicePoints.size()) { if (cpIndex >= choicePoints.size()) {
@@ -86,8 +107,7 @@ public class SpellAbilityChoicesIterator {
ChoicePoint cp = choicePoints.get(cpIndex); ChoicePoint cp = choicePoints.get(cpIndex);
// Prune duplicates. // Prune duplicates.
HashSet<String> uniqueCards = new HashSet<>(); HashSet<String> uniqueCards = new HashSet<>();
for (int i = 0; i < fetchList.size(); i++) { for (Card card : fetchList) {
Card card = fetchList.get(i);
if (uniqueCards.add(card.getName()) && uniqueCards.size() == cp.nextChoice + 1) { if (uniqueCards.add(card.getName()) && uniqueCards.size() == cp.nextChoice + 1) {
cp.selectedChoice = card; cp.selectedChoice = card;
} }
@@ -137,14 +157,9 @@ public class SpellAbilityChoicesIterator {
evalDepth++; evalDepth++;
pushTarget = false; pushTarget = false;
} }
return;
} }
} }
public int[] getSelectModes() {
return selectedModes;
}
public boolean advance(Score lastScore) { public boolean advance(Score lastScore) {
cpIndex = -1; cpIndex = -1;
for (ChoicePoint cp : choicePoints) { for (ChoicePoint cp : choicePoints) {
@@ -195,7 +210,7 @@ public class SpellAbilityChoicesIterator {
doneEvaluating(bestScoreForMode); doneEvaluating(bestScoreForMode);
bestScoreForMode = new Score(Integer.MIN_VALUE); bestScoreForMode = new Score(Integer.MIN_VALUE);
if (modeIterator.hasNext()) { if (modeIterator.hasNext()) {
selectedModes = modeIterator.next(); selectedModes = remapModes(modeIterator.next());
advancedToNextMode = true; advancedToNextMode = true;
return true; return true;
} }
@@ -232,8 +247,8 @@ public class SpellAbilityChoicesIterator {
} }
private static class AllowRepeatModesIterator implements Iterator<int[]> { private static class AllowRepeatModesIterator implements Iterator<int[]> {
private int numChoices; private final int numChoices;
private int max; private final int max;
private int[] indexes; private int[] indexes;
public AllowRepeatModesIterator(int numChoices, int min, int max) { public AllowRepeatModesIterator(int numChoices, int min, int max) {

View File

@@ -39,6 +39,7 @@ public class SpellAbilityPicker {
private SpellAbilityChoicesIterator interceptor; private SpellAbilityChoicesIterator interceptor;
private Plan plan; private Plan plan;
private int numSimulations;
public SpellAbilityPicker(Game game, Player player) { public SpellAbilityPicker(Game game, Player player) {
this.game = game; this.game = game;
@@ -66,19 +67,7 @@ public class SpellAbilityPicker {
private List<SpellAbility> getCandidateSpellsAndAbilities() { private List<SpellAbility> getCandidateSpellsAndAbilities() {
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player); List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); HashMap<String, Card> landsDeDupe = new HashMap<>();
if (landsToPlay != null) {
HashMap<String, Card> landsDeDupe = new HashMap<>();
for (Card land : landsToPlay) {
Card previousLand = landsDeDupe.get(land.getName());
// Skip identical lands.
if (previousLand != null && previousLand.getZone() == land.getZone() && previousLand.getOwner() == land.getOwner()) {
continue;
}
landsDeDupe.put(land.getName(), land);
all.add(new LandAbility(land));
}
}
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player); List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
int writeIndex = 0; int writeIndex = 0;
for (int i = 0; i < candidateSAs.size(); i++) { for (int i = 0; i < candidateSAs.size(); i++) {
@@ -86,6 +75,16 @@ public class SpellAbilityPicker {
if (sa.isManaAbility()) { if (sa.isManaAbility()) {
continue; continue;
} }
// Skip identical lands.
if (sa instanceof LandAbility) {
Card land = sa.getHostCard();
Card previousLand = landsDeDupe.get(sa.getHostCard().getName());
if (previousLand != null && previousLand.getZone() == land.getZone() &&
previousLand.getOwner() == land.getOwner()) {
continue;
}
landsDeDupe.put(land.getName(), land);
}
sa.setActivatingPlayer(player, true); sa.setActivatingPlayer(player, true);
AiPlayDecision opinion = canPlayAndPayForSim(sa); AiPlayDecision opinion = canPlayAndPayForSim(sa);
@@ -95,7 +94,7 @@ public class SpellAbilityPicker {
if (opinion != AiPlayDecision.WillPlay) if (opinion != AiPlayDecision.WillPlay)
continue; continue;
candidateSAs.set(writeIndex, sa); candidateSAs.set(writeIndex, sa);
writeIndex++; writeIndex++;
} }
candidateSAs.subList(writeIndex, candidateSAs.size()).clear(); candidateSAs.subList(writeIndex, candidateSAs.size()).clear();
@@ -353,7 +352,9 @@ public class SpellAbilityPicker {
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
return AiPlayDecision.CantAfford; return AiPlayDecision.CantAfford;
} }
if (!ComputerUtilAbility.isFullyTargetable(sa)) {
return AiPlayDecision.TargetingFailed;
}
if (shouldWaitForLater(sa)) { if (shouldWaitForLater(sa)) {
return AiPlayDecision.AnotherTime; return AiPlayDecision.AnotherTime;
} }
@@ -375,10 +376,13 @@ public class SpellAbilityPicker {
final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller); final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller);
Score lastScore; Score lastScore;
do { do {
// TODO: MyRandom should be an instance on the game object, so that we could do
// simulations in parallel without messing up global state.
MyRandom.setRandom(new Random(randomSeedToUse)); MyRandom.setRandom(new Random(randomSeedToUse));
GameSimulator simulator = new GameSimulator(controller, game, player, phase); GameSimulator simulator = new GameSimulator(controller, game, player, phase);
simulator.setInterceptor(choicesIterator); simulator.setInterceptor(choicesIterator);
lastScore = simulator.simulateSpellAbility(sa); lastScore = simulator.simulateSpellAbility(sa);
numSimulations++;
if (lastScore.value > bestScore.value) { if (lastScore.value > bestScore.value) {
bestScore = lastScore; bestScore = lastScore;
} }
@@ -462,4 +466,8 @@ public class SpellAbilityPicker {
} }
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude); return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude);
} }
public int getNumSimulations() {
return numSimulations;
}
} }

View File

@@ -240,6 +240,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
// cannot still decide, if this "name|set" format is needed anymore // cannot still decide, if this "name|set" format is needed anymore
// return String.format("%s|%s", name, cardSet); // return String.format("%s|%s", name, cardSet);
} }
public String getCardName() {
return name;
}
/* /*
* This (utility) method transform a collectorNumber String into a key string for sorting. * This (utility) method transform a collectorNumber String into a key string for sorting.

View File

@@ -159,7 +159,7 @@ public class Localizer {
e.printStackTrace(); e.printStackTrace();
} }
System.out.println("Language '" + resourceBundle.toString() + "' loaded successfully."); System.out.println("Language '" + resourceBundle.getBaseBundleName() + "' loaded successfully.");
notifyObservers(); notifyObservers();

View File

@@ -164,8 +164,8 @@ public class GameAction {
} }
if (!found) { if (!found) {
c.clearControllers(); c.clearControllers();
if (c.removeChangedState()) { if (cause != null) {
c.updateStateForView(); unanimateOnAbortedChange(cause, c);
} }
return c; return c;
} }
@@ -365,7 +365,18 @@ public class GameAction {
copied.getOwner().removeInboundToken(copied); copied.getOwner().removeInboundToken(copied);
if (repres == ReplacementResult.Prevented) { if (repres == ReplacementResult.Prevented) {
if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard)) { c.clearEtbCounters();
c.clearControllers();
if (cause != null) {
unanimateOnAbortedChange(cause, c);
if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) {
c.setBackSide(false);
c.changeToState(CardStateName.Original);
}
unattachCardLeavingBattlefield(c);
}
if (c.isInZone(ZoneType.Stack) && !zoneTo.is(ZoneType.Graveyard)) {
return moveToGraveyard(c, cause, params); return moveToGraveyard(c, cause, params);
} }
@@ -373,10 +384,8 @@ public class GameAction {
copied.clearDelved(); copied.clearDelved();
copied.clearConvoked(); copied.clearConvoked();
copied.clearExploited(); copied.clearExploited();
} } else if (toBattlefield && !c.isInPlay()) {
// was replaced with another Zone Change
// was replaced with another Zone Change
if (toBattlefield && !c.isInPlay()) {
if (c.removeChangedState()) { if (c.removeChangedState()) {
c.updateStateForView(); c.updateStateForView();
} }
@@ -570,6 +579,12 @@ public class GameAction {
c.setZone(zoneTo); c.setZone(zoneTo);
} }
if (fromBattlefield) {
// order here is important so it doesn't unattach cards that might have returned from UntilHostLeavesPlay
unattachCardLeavingBattlefield(copied);
c.runLeavesPlayCommands();
}
// do ETB counters after zone add // do ETB counters after zone add
if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) { if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) {
game.getTriggerHandler().registerActiveTrigger(copied, false); game.getTriggerHandler().registerActiveTrigger(copied, false);
@@ -697,7 +712,6 @@ public class GameAction {
copied.setState(CardStateName.Original, true); copied.setState(CardStateName.Original, true);
} }
unattachCardLeavingBattlefield(copied);
} else if (toBattlefield) { } else if (toBattlefield) {
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {
copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p);
@@ -973,9 +987,15 @@ public class GameAction {
if (c.isInZone(ZoneType.Stack)) { if (c.isInZone(ZoneType.Stack)) {
c.getGame().getStack().remove(c); c.getGame().getStack().remove(c);
} }
final Zone z = c.getZone();
// in some corner cases there's no zone yet (copied spell that failed targeting) // in some corner cases there's no zone yet (copied spell that failed targeting)
if (c.getZone() != null) { if (z != null) {
c.getZone().remove(c); z.remove(c);
if (z.is(ZoneType.Battlefield)) {
c.runLeavesPlayCommands();
}
} }
// CR 603.6c other players LTB triggers should work // CR 603.6c other players LTB triggers should work
@@ -1036,20 +1056,13 @@ public class GameAction {
} }
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
for (Player p : game.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false);
}
final int tiz = c.getTurnInZone();
oldBattlefield.remove(c); oldBattlefield.remove(c);
newBattlefield.add(c); newBattlefield.add(c);
c.setSickness(true);
if (game.getPhaseHandler().inCombat()) { if (game.getPhaseHandler().inCombat()) {
game.getCombat().removeFromCombat(c); game.getCombat().removeFromCombat(c);
} }
c.setTurnInZone(tiz);
c.setCameUnderControlSinceLastUpkeep(true); c.setCameUnderControlSinceLastUpkeep(true);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
@@ -1057,9 +1070,6 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
for (Player p : game.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true);
}
c.runChangeControllerCommands(); c.runChangeControllerCommands();
} }
@@ -2560,4 +2570,17 @@ public class GameAction {
} }
return false; return false;
} }
private static void unanimateOnAbortedChange(final SpellAbility cause, final Card c) {
if (cause.hasParam("AnimateSubAbility")) {
long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp"));
c.removeChangedCardKeywords(unanimateTimestamp, 0);
c.removeChangedCardTypes(unanimateTimestamp, 0);
c.removeChangedName(unanimateTimestamp, 0);
c.removeNewPT(unanimateTimestamp, 0);
if (c.removeChangedCardTraits(unanimateTimestamp, 0)) {
c.updateStateForView();
}
}
}
} }

View File

@@ -895,7 +895,7 @@ public final class GameActionUtil {
} }
CardCollection completeList = new CardCollection(); CardCollection completeList = new CardCollection();
PlayerCollection players = new PlayerCollection(game.getPlayers()); PlayerCollection players = new PlayerCollection(game.getPlayers());
// CR 613.7k use APNAP // CR 613.7m use APNAP
int indexAP = players.indexOf(game.getPhaseHandler().getPlayerTurn()); int indexAP = players.indexOf(game.getPhaseHandler().getPlayerTurn());
if (indexAP != -1) { if (indexAP != -1) {
Collections.rotate(players, - indexAP); Collections.rotate(players, - indexAP);

View File

@@ -340,6 +340,13 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params); addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params);
} }
public List<Pair<Integer, Boolean>> getDamageReceivedThisTurn() {
return damageReceivedThisTurn;
}
public void setDamageReceivedThisTurn(List<Pair<Integer, Boolean>> dmg) {
damageReceivedThisTurn.addAll(dmg);
}
public void receiveDamage(Pair<Integer, Boolean> dmg) { public void receiveDamage(Pair<Integer, Boolean> dmg) {
damageReceivedThisTurn.add(dmg); damageReceivedThisTurn.add(dmg);
} }

View File

@@ -511,12 +511,8 @@ public class AbilityUtils {
} else if (hType.equals("Other")) { } else if (hType.equals("Other")) {
players.addAll(player.getAllOtherPlayers()); players.addAll(player.getAllOtherPlayers());
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} else if (hType.equals("Remembered")) { } else if (hType.startsWith("Remembered")) {
for (final Object o : card.getRemembered()) { addPlayer(card.getRemembered(), hType, players);
if (o instanceof Player) {
players.add((Player) o);
}
}
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} else if (hType.equals("NonActive")) { } else if (hType.equals("NonActive")) {
players.addAll(game.getPlayers()); players.addAll(game.getPlayers());

View File

@@ -400,6 +400,23 @@ public abstract class SpellAbilityEffect {
addedTrigger.setIntrinsic(true); addedTrigger.setIntrinsic(true);
} }
protected static void addExileOnCastOrMoveTrigger(final Card card, final String zone) {
String trig = "Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True";
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
//Any on Destination will cause the effect to remove itself when cancelling to play the card
String trig2 = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | Destination$ Hand,Library,Graveyard,Battlefield,Command,Sideboard | TriggerZones$ Command | Static$ True";
String effect2 = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
final Trigger parsedTrigger2 = TriggerHandler.parseTrigger(trig2, card, true);
parsedTrigger2.setOverridingAbility(AbilityFactory.getAbility(effect2, card));
final Trigger addedTrigger2 = card.addTrigger(parsedTrigger2);
addedTrigger2.setIntrinsic(true);
}
protected static void addExileOnCounteredTrigger(final Card card) { protected static void addExileOnCounteredTrigger(final Card card) {
String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True";
String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile";
@@ -434,7 +451,7 @@ public abstract class SpellAbilityEffect {
protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) { protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = card.getGame(); final Game game = card.getGame();
final Card eff = createEffect(sa, sa.getActivatingPlayer(), host.getName() + "'s Effect", host.getImageKey()); final Card eff = createEffect(sa, sa.getActivatingPlayer(), host + "'s Effect", host.getImageKey());
addLeaveBattlefieldReplacement(eff, zone); addLeaveBattlefieldReplacement(eff, zone);

View File

@@ -136,10 +136,10 @@ public class AnimateAllEffect extends AnimateEffectBase {
CardCollectionView list; CardCollectionView list;
if (!sa.usesTargeting() && !sa.hasParam("Defined")) { if (sa.usesTargeting() || sa.hasParam("Defined")) {
list = game.getCardsIn(ZoneType.Battlefield);
} else {
list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield); list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield);
} else {
list = game.getCardsIn(ZoneType.Battlefield);
} }
list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa); list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa);
@@ -155,18 +155,18 @@ public class AnimateAllEffect extends AnimateEffectBase {
game.fireEvent(new GameEventCardStatsChanged(c)); game.fireEvent(new GameEventCardStatsChanged(c));
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = -5861759814760561373L;
@Override
public void run() {
doUnanimate(c, timestamp);
game.fireEvent(new GameEventCardStatsChanged(c));
}
};
if (!permanent) { if (!permanent) {
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = -5861759814760561373L;
@Override
public void run() {
doUnanimate(c, timestamp);
game.fireEvent(new GameEventCardStatsChanged(c));
}
};
addUntilCommand(sa, unanimate); addUntilCommand(sa, unanimate);
} }
} }

View File

@@ -62,7 +62,7 @@ public class BalanceEffect extends SpellAbilityEffect {
discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)); discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance));
} else { // Battlefield } else { // Battlefield
for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue; if (null == card) continue;
game.getAction().sacrifice(card, sa, true, table, params); game.getAction().sacrifice(card, sa, true, table, params);
} }
} }

View File

@@ -3,8 +3,6 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.game.Game; import forge.game.Game;
@@ -14,6 +12,7 @@ import forge.game.card.Card;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.util.Lang;
public class BecomesBlockedEffect extends SpellAbilityEffect { public class BecomesBlockedEffect extends SpellAbilityEffect {
@@ -21,9 +20,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<Card> tgtCards = getTargetCards(sa); sb.append(Lang.joinHomogenous(getTargetCards(sa)));
sb.append(StringUtils.join(tgtCards, ", "));
sb.append(" becomes blocked."); sb.append(" becomes blocked.");
return sb.toString(); return sb.toString();

View File

@@ -1,10 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
@@ -19,6 +16,7 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.util.CardTranslation; import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
@@ -28,10 +26,9 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<Card> tgtCards = getTargetCards(sa);
// should update when adding effects for defined blocker // should update when adding effects for defined blocker
sb.append("Reselect the defender of "); sb.append("Reselect the defender of ");
sb.append(StringUtils.join(tgtCards, ", ")); sb.append(Lang.joinHomogenous(getTargetCards(sa)));
return sb.toString(); return sb.toString();
} }

View File

@@ -68,7 +68,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
if (origin.contains(ZoneType.Library) && sa.hasParam("Search") && !sa.getActivatingPlayer().canSearchLibraryWith(sa, p)) { if (origin.contains(ZoneType.Library) && sa.hasParam("Search") && !sa.getActivatingPlayer().canSearchLibraryWith(sa, p)) {
cards.removeAll(p.getCardsIn(ZoneType.Library)); cards.removeAll(p.getCardsIn(ZoneType.Library));
} }
} }
if (origin.contains(ZoneType.Library) && sa.hasParam("Search")) { if (origin.contains(ZoneType.Library) && sa.hasParam("Search")) {
// Search library using changezoneall effect need a param "Search" // Search library using changezoneall effect need a param "Search"
@@ -95,8 +94,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
if (!libCards.isEmpty()) { if (!libCards.isEmpty()) {
sa.getActivatingPlayer().getController().reveal(libCards, ZoneType.Library, libCards.get(0).getOwner()); sa.getActivatingPlayer().getController().reveal(libCards, ZoneType.Library, libCards.get(0).getOwner());
} }
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sa.getActivatingPlayer());
runParams.put(AbilityKey.Player, sa.getActivatingPlayer());
runParams.put(AbilityKey.Target, tgtPlayers); runParams.put(AbilityKey.Target, tgtPlayers);
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
} }
@@ -172,9 +170,11 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
// need LKI before Animate does apply // need LKI before Animate does apply
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
source.addRemembered(c); source.addRemembered(c);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); AbilityUtils.resolve(animate);
source.removeRemembered(c); source.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
} }
if (sa.hasParam("Tapped")) { if (sa.hasParam("Tapped")) {
c.setTapped(true); c.setTapped(true);

View File

@@ -133,10 +133,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
sb.append(" face down"); sb.append(" face down");
} }
sb.append(".");
} else if (destination.equals("Ante")) { } else if (destination.equals("Ante")) {
sb.append("Add the top card of your library to the ante"); sb.append("Add the top card of your library to the ante.");
} }
sb.append(".");
} else if (origin.equals("Library")) { } else if (origin.equals("Library")) {
final boolean originAlt = sa.hasParam("OriginAlternative"); final boolean originAlt = sa.hasParam("OriginAlternative");
sb.append(chooserNames).append(" search").append(choosers.size() > 1 ? " " : "es "); sb.append(chooserNames).append(" search").append(choosers.size() > 1 ? " " : "es ");
@@ -575,6 +575,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa); movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa);
} else { } else {
if (destination.equals(ZoneType.Battlefield)) { if (destination.equals(ZoneType.Battlefield)) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (sa.isReplacementAbility()) {
ReplacementEffect re = sa.getReplacementEffect();
moveParams.put(AbilityKey.ReplacementEffect, re);
if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) {
moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
}
}
if (sa.hasParam("Tapped") || sa.isNinjutsu()) { if (sa.hasParam("Tapped") || sa.isNinjutsu()) {
gameCard.setTapped(true); gameCard.setTapped(true);
} }
@@ -583,6 +594,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Transformed")) { if (sa.hasParam("Transformed")) {
if (gameCard.isDoubleFaced()) { if (gameCard.isDoubleFaced()) {
// need LKI before Animate does apply
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard));
}
gameCard.changeCardState("Transform", null, sa); gameCard.changeCardState("Transform", null, sa);
} else { } else {
// If it can't Transform, don't change zones. // If it can't Transform, don't change zones.
@@ -650,26 +665,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (sa.isReplacementAbility()) {
ReplacementEffect re = sa.getReplacementEffect();
moveParams.put(AbilityKey.ReplacementEffect, re);
if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) {
moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
}
}
if (sa.hasAdditionalAbility("AnimateSubAbility")) { if (sa.hasAdditionalAbility("AnimateSubAbility")) {
// need LKI before Animate does apply // need LKI before Animate does apply
if (!moveParams.containsKey(AbilityKey.CardLKI)) { if (!moveParams.containsKey(AbilityKey.CardLKI)) {
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard)); moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard));
} }
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
hostCard.addRemembered(gameCard); hostCard.addRemembered(gameCard);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); AbilityUtils.resolve(animate);
hostCard.removeRemembered(gameCard); hostCard.removeRemembered(gameCard);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
} }
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
@@ -776,7 +782,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("TrackDiscarded")) { if (sa.hasParam("TrackDiscarded")) {
movedCard.setMadnessWithoutCast(true); movedCard.setDiscarded(true);
} }
} }
} }
@@ -1109,7 +1115,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
fetchList = (CardCollection)AbilityUtils.filterListByType(fetchList, sa.getParam("ChangeType"), sa); fetchList = (CardCollection)AbilityUtils.filterListByType(fetchList, sa.getParam("ChangeType"), sa);
} }
if (sa.hasParam("NoShuffle")) { if (sa.hasParam("NoShuffle") || "False".equals(sa.getParam("Shuffle"))) {
shuffleMandatory = false; shuffleMandatory = false;
} }
@@ -1249,7 +1255,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
CardLists.shuffle(chosenCards); CardLists.shuffle(chosenCards);
} }
// do not shuffle the library once we have placed a fetched card on top. // do not shuffle the library once we have placed a fetched card on top.
if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && !"False".equals(sa.getParam("Shuffle"))) { if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && shuffleMandatory) {
player.shuffle(sa); player.shuffle(sa);
} }
@@ -1310,9 +1316,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need LKI before Animate does apply // need LKI before Animate does apply
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
source.addRemembered(c); source.addRemembered(c);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); AbilityUtils.resolve(animate);
source.removeRemembered(c); source.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
} }
if (sa.hasParam("GainControl")) { if (sa.hasParam("GainControl")) {
final String g = sa.getParam("GainControl"); final String g = sa.getParam("GainControl");
@@ -1331,6 +1339,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Transformed")) { if (sa.hasParam("Transformed")) {
if (c.isDoubleFaced()) { if (c.isDoubleFaced()) {
// need LKI before Animate does apply
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
}
c.changeCardState("Transform", null, sa); c.changeCardState("Transform", null, sa);
} else { } else {
// If it can't Transform, don't change zones. // If it can't Transform, don't change zones.
@@ -1390,7 +1402,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard.setTimestamp(ts); movedCard.setTimestamp(ts);
if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) { if (sa.hasParam("AttachAfter") && movedCard.isAttachment() && movedCard.isInPlay()) {
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa); CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa);
if (list.isEmpty()) { if (list.isEmpty()) {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa); list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa);

View File

@@ -25,19 +25,15 @@ public class CharmEffect extends SpellAbilityEffect {
List<String> restriction = null; List<String> restriction = null;
if (sa.hasParam("ChoiceRestriction")) { if (sa.hasParam("ChoiceRestriction")) {
String rest = sa.getParam("ChoiceRestriction"); restriction = source.getChosenModes(sa, sa.getParam("ChoiceRestriction"));
if (rest.equals("ThisGame")) {
restriction = source.getChosenModesGame(sa);
} else if (rest.equals("ThisTurn")) {
restriction = source.getChosenModesTurn(sa);
}
} }
List<AbilitySub> choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices")); List<AbilitySub> choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
List<AbilitySub> toRemove = Lists.newArrayList(); List<AbilitySub> toRemove = Lists.newArrayList();
for (AbilitySub ch : choices) { for (AbilitySub ch : choices) {
// 603.3c If one of the modes would be illegal, that mode can't be chosen. // 603.3c If one of the modes would be illegal, that mode can't be chosen.
if ((ch.usesTargeting() && ch.isTrigger() && ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) || if ((ch.usesTargeting() && ch.isTrigger() && ch.getMinTargets() > 0 &&
ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) ||
(restriction != null && restriction.contains(ch.getDescription()))) { (restriction != null && restriction.contains(ch.getDescription()))) {
toRemove.add(ch); toRemove.add(ch);
} }
@@ -97,6 +93,8 @@ public class CharmEffect extends SpellAbilityEffect {
sb.append(" that hasn't been chosen"); sb.append(" that hasn't been chosen");
} else if (rest.equals("ThisTurn")) { } else if (rest.equals("ThisTurn")) {
sb.append(" that hasn't been chosen this turn"); sb.append(" that hasn't been chosen this turn");
} else if (rest.equals("YourLastCombat")) {
sb.append(" that wasn't chosen during your last combat");
} }
} }

View File

@@ -20,7 +20,7 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
List<Player> tgtPlayers = getTargetPlayers(sa); List<Player> tgtPlayers = getTargetPlayers(sa);
return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn"); return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(), "controls", Lang.joinHomogenous(tgtPlayers), "during their next turn");
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")

View File

@@ -28,8 +28,9 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final Player pl = !sa.hasParam("DefinedPlayer") ? sa.getActivatingPlayer() : final Player pl = sa.hasParam("DefinedPlayer") ?
AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst(); AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst()
: sa.getActivatingPlayer();
sb.append(pl.getName()); sb.append(pl.getName());
if (sa.hasParam("CounterType")) { if (sa.hasParam("CounterType")) {

View File

@@ -18,6 +18,7 @@ import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
public class CountersRemoveEffect extends SpellAbilityEffect { public class CountersRemoveEffect extends SpellAbilityEffect {
@@ -53,13 +54,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
} }
sb.append(" from"); sb.append(" from");
for (final Card c : getTargetCards(sa)) { sb.append(Lang.joinHomogenous(getTargetCards(sa)));
sb.append(" ").append(c);
}
for (final Player tgtPlayer : getTargetPlayers(sa)) { sb.append(Lang.joinHomogenous(getTargetPlayers(sa)));
sb.append(" ").append(tgtPlayer);
}
sb.append("."); sb.append(".");

View File

@@ -9,6 +9,7 @@ import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
public class DamageEachEffect extends DamageBaseEffect { public class DamageEachEffect extends DamageBaseEffect {
@@ -38,9 +39,7 @@ public class DamageEachEffect extends DamageBaseEffect {
sb.append(sa.getParam("StackDescription")); sb.append(sa.getParam("StackDescription"));
} else { } else {
sb.append("Each ").append(desc).append(" deals ").append(dmg).append(" to "); sb.append("Each ").append(desc).append(" deals ").append(dmg).append(" to ");
for (final Player p : getTargetPlayers(sa)) { Lang.joinHomogenous(getTargetPlayers(sa));
sb.append(p);
}
if (sa.hasParam("DefinedCards")) { if (sa.hasParam("DefinedCards")) {
if (sa.getParam("DefinedCards").equals("Self")) { if (sa.getParam("DefinedCards").equals("Self")) {
sb.append(" itself"); sb.append(" itself");

View File

@@ -417,13 +417,13 @@ public class DigEffect extends SpellAbilityEffect {
} }
if (sa.hasAdditionalAbility("AnimateSubAbility")) { if (sa.hasAdditionalAbility("AnimateSubAbility")) {
// need LKI before Animate does apply // need LKI before Animate does apply
if (!moveParams.containsKey(AbilityKey.CardLKI)) { moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
}
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
host.addRemembered(c); host.addRemembered(c);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); AbilityUtils.resolve(animate);
host.removeRemembered(c); host.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
} }
c = game.getAction().moveTo(zone, c, sa, moveParams); c = game.getAction().moveTo(zone, c, sa, moveParams);
if (destZone1.equals(ZoneType.Battlefield)) { if (destZone1.equals(ZoneType.Battlefield)) {

View File

@@ -77,10 +77,20 @@ public class DigMultipleEffect extends SpellAbilityEffect {
if (validMap.isEmpty()) { if (validMap.isEmpty()) {
chooser.getController().notifyOfValue(sa, null, Localizer.getInstance().getMessage("lblNoValidCards")); chooser.getController().notifyOfValue(sa, null, Localizer.getInstance().getMessage("lblNoValidCards"));
} else { } else {
CardCollection chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional); CardCollection chosen;
//ensure choosing something when possible and not optional
while (true) {
chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa,
Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional);
if (!chosen.isEmpty()) { if (!chosen.isEmpty()) {
game.getAction().reveal(chosen, chooser, true, Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName())); game.getAction().reveal(chosen, chooser, true,
Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName()));
break;
}
if (chooseOptional) break;
chooser.getController().notifyOfValue(sa, null,
Localizer.getInstance().getMessage("lblMustChoose"));
} }
if (sa.hasParam("ChooseAmount") || sa.hasParam("ChosenZone")) { if (sa.hasParam("ChooseAmount") || sa.hasParam("ChosenZone")) {
@@ -108,21 +118,23 @@ public class DigMultipleEffect extends SpellAbilityEffect {
final ZoneType origin = c.getZone().getZoneType(); final ZoneType origin = c.getZone().getZoneType();
final PlayerZone zone = c.getOwner().getZone(destZone1); final PlayerZone zone = c.getOwner().getZone(destZone1);
if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { if (!sa.hasParam("ChangeLater")) {
if (libraryPosition == -1 || libraryPosition > zone.size()) { if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) {
libraryPosition = zone.size(); if (libraryPosition == -1 || libraryPosition > zone.size()) {
} libraryPosition = zone.size();
c = game.getAction().moveTo(zone, c, libraryPosition, sa);
} else {
if (destZone1.equals(ZoneType.Battlefield)) {
if (sa.hasParam("Tapped")) {
c.setTapped(true);
} }
c = game.getAction().moveTo(zone, c, libraryPosition, sa);
} else {
if (destZone1.equals(ZoneType.Battlefield)) {
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
}
c = game.getAction().moveTo(zone, c, sa);
}
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
} }
c = game.getAction().moveTo(zone, c, sa);
}
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
} }
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
@@ -142,38 +154,45 @@ public class DigMultipleEffect extends SpellAbilityEffect {
} }
// now, move the rest to destZone2 // now, move the rest to destZone2
if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck if (!sa.hasParam("ChangeLater")) {
|| destZone2 == ZoneType.Graveyard) { if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck
CardCollection afterOrder = rest; || destZone2 == ZoneType.SchemeDeck || destZone2 == ZoneType.Graveyard) {
if (sa.hasParam("RestRandomOrder")) { CardCollection afterOrder = rest;
CardLists.shuffle(afterOrder); if (sa.hasParam("RestRandomOrder")) {
} CardLists.shuffle(afterOrder);
if (libraryPosition2 != -1) {
// Closest to top
Collections.reverse(afterOrder);
}
for (final Card c : afterOrder) {
final ZoneType origin = c.getZone().getZoneType();
Card m;
if (destZone2 == ZoneType.Library) {
m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
} else {
m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa);
} }
if (m != null && !origin.equals(m.getZone().getZoneType())) { if (libraryPosition2 != -1) {
table.put(origin, m.getZone().getZoneType(), m); // Closest to top
Collections.reverse(afterOrder);
}
for (final Card c : afterOrder) {
final ZoneType origin = c.getZone().getZoneType();
Card m;
if (destZone2 == ZoneType.Library) {
m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
} else {
m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa);
}
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
}
} else {
// just move them randomly
for (int i = 0; i < rest.size(); i++) {
Card c = rest.get(i);
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone toZone = c.getOwner().getZone(destZone2);
c = game.getAction().moveTo(toZone, c, sa);
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
} }
} }
} else { }
// just move them randomly if (sa.hasParam("ImprintRest")) {
for (int i = 0; i < rest.size(); i++) { for (Card c : rest) {
Card c = rest.get(i); host.addImprintedCard(c);
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone toZone = c.getOwner().getZone(destZone2);
c = game.getAction().moveTo(toZone, c, sa);
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
} }
} }
} }

View File

@@ -227,6 +227,8 @@ public class EffectEffect extends SpellAbilityEffect {
addForgetOnCastTrigger(eff); addForgetOnCastTrigger(eff);
} else if (sa.hasParam("ExileOnMoved")) { } else if (sa.hasParam("ExileOnMoved")) {
addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved")); addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved"));
} else if (sa.hasParam("ExileOnCast")) {
addExileOnCastOrMoveTrigger(eff, sa.getParam("ExileOnCast"));
} }
if (sa.hasParam("ForgetOnPhasedIn")) { if (sa.hasParam("ForgetOnPhasedIn")) {
addForgetOnPhasedInTrigger(eff); addForgetOnPhasedInTrigger(eff);

View File

@@ -1,9 +1,5 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
@@ -12,6 +8,7 @@ import forge.game.card.CardCollection;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Lang;
public class RemoveFromCombatEffect extends SpellAbilityEffect { public class RemoveFromCombatEffect extends SpellAbilityEffect {
@@ -19,10 +16,8 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<Card> tgtCards = getTargetCards(sa);
sb.append("Remove "); sb.append("Remove ");
sb.append(StringUtils.join(tgtCards, ", ")); sb.append(Lang.joinHomogenous(getTargetCards(sa)));
sb.append(" from combat."); sb.append(" from combat.");
return sb.toString(); return sb.toString();

View File

@@ -226,7 +226,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private boolean tributed = false; private boolean tributed = false;
private boolean embalmed = false; private boolean embalmed = false;
private boolean eternalized = false; private boolean eternalized = false;
private boolean madnessWithoutCast = false; private boolean discarded = false;
private boolean flipped = false; private boolean flipped = false;
private boolean facedown = false; private boolean facedown = false;
@@ -324,6 +324,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length]; private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length];
private int planeswalkerAbilityActivated; private int planeswalkerAbilityActivated;
private boolean planeswalkerActivationLimitUsed;
private final ActivationTable numberTurnActivations = new ActivationTable(); private final ActivationTable numberTurnActivations = new ActivationTable();
private final ActivationTable numberGameActivations = new ActivationTable(); private final ActivationTable numberGameActivations = new ActivationTable();
@@ -331,9 +332,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private final Map<SpellAbility, List<String>> chosenModesTurn = Maps.newHashMap(); private final Map<SpellAbility, List<String>> chosenModesTurn = Maps.newHashMap();
private final Map<SpellAbility, List<String>> chosenModesGame = Maps.newHashMap(); private final Map<SpellAbility, List<String>> chosenModesGame = Maps.newHashMap();
private final Map<SpellAbility, List<String>> chosenModesYourCombat = Maps.newHashMap();
private final Map<SpellAbility, List<String>> chosenModesYourLastCombat = Maps.newHashMap();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create(); private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create(); private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesYourCombatStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesYourLastCombatStatic = HashBasedTable.create();
private CombatLki combatLKI; private CombatLki combatLKI;
@@ -3263,7 +3268,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final boolean isFlipped() { public final boolean isFlipped() {
return flipped; return flipped;
} }
public final void setFlipped(boolean value) { public final void setFlipped(boolean value) {
flipped = value; flipped = value;
} }
@@ -3271,7 +3275,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void setCanCounter(final boolean b) { public final void setCanCounter(final boolean b) {
canCounter = b; canCounter = b;
} }
public final boolean getCanCounter() { public final boolean getCanCounter() {
return canCounter; return canCounter;
} }
@@ -5458,13 +5461,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
damageHistory = history; damageHistory = history;
} }
public List<Pair<Integer, Boolean>> getDamageReceivedThisTurn() {
return damageReceivedThisTurn;
}
public void setDamageReceivedThisTurn(List<Pair<Integer, Boolean>> dmg) {
damageReceivedThisTurn.addAll(dmg);
}
public final boolean hasDealtDamageToOpponentThisTurn() { public final boolean hasDealtDamageToOpponentThisTurn() {
return getDamageHistory().getDamageDoneThisTurn(null, true, null, "Player.Opponent", this, getController(), null) > 0; return getDamageHistory().getDamageDoneThisTurn(null, true, null, "Player.Opponent", this, getController(), null) > 0;
} }
@@ -5805,8 +5801,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
return getCastSA().isMadness(); return getCastSA().isMadness();
} }
public boolean getMadnessWithoutCast() { return madnessWithoutCast; } public boolean wasDiscarded() { return discarded; }
public void setMadnessWithoutCast(boolean state) { madnessWithoutCast = state; } public void setDiscarded(boolean state) { discarded = state; }
public final boolean isMonstrous() { public final boolean isMonstrous() {
return monstrous; return monstrous;
@@ -6345,6 +6341,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return isInZone(ZoneType.Battlefield); return isInZone(ZoneType.Battlefield);
} }
public void onEndOfCombat(final Player active) {
if (this.getController().equals(active)) {
chosenModesYourLastCombat.clear();
chosenModesYourLastCombatStatic.clear();
chosenModesYourLastCombat.putAll(chosenModesYourCombat);
chosenModesYourLastCombatStatic.putAll(chosenModesYourCombatStatic);
chosenModesYourCombat.clear();
chosenModesYourCombatStatic.clear();
updateAbilityTextForView();
}
}
public void onCleanupPhase(final Player turn) { public void onCleanupPhase(final Player turn) {
if (!this.hasKeyword("Damage isn't removed from CARDNAME during cleanup steps.")) { if (!this.hasKeyword("Damage isn't removed from CARDNAME during cleanup steps.")) {
setDamage(0); setDamage(0);
@@ -7037,7 +7045,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
numberAbilityResolved.clear(); numberAbilityResolved.clear();
} }
public List<String> getChosenModesTurn(SpellAbility ability) { public List<String> getChosenModes(SpellAbility ability, String type) {
SpellAbility original = null; SpellAbility original = null;
SpellAbility root = ability.getRootAbility(); SpellAbility root = ability.getRootAbility();
@@ -7051,32 +7059,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
} }
if (ability.getGrantorStatic() != null) { if (type.equals("ThisTurn")) {
return chosenModesTurnStatic.get(original, ability.getGrantorStatic()); if (ability.getGrantorStatic() != null) {
} return chosenModesTurnStatic.get(original, ability.getGrantorStatic());
return chosenModesTurn.get(original);
}
public List<String> getChosenModesGame(SpellAbility ability) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
// because trigger spell abilities are copied, try to get original one
if (root.isTrigger()) {
original = root.getTrigger().getOverridingAbility();
} else {
original = ability.getOriginalAbility();
if (original == null) {
original = ability;
} }
return chosenModesTurn.get(original);
} else if (type.equals("ThisGame")) {
if (ability.getGrantorStatic() != null) {
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
}
return chosenModesGame.get(original);
} else if (type.equals("YourLastCombat")) {
if (ability.getGrantorStatic() != null) {
return chosenModesYourLastCombatStatic.get(original, ability.getGrantorStatic());
}
return chosenModesYourLastCombat.get(original);
} }
return null;
if (ability.getGrantorStatic() != null) {
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
}
return chosenModesGame.get(original);
} }
public void addChosenModes(SpellAbility ability, String mode) { public void addChosenModes(SpellAbility ability, String mode, boolean yourCombat) {
SpellAbility original = null; SpellAbility original = null;
SpellAbility root = ability.getRootAbility(); SpellAbility root = ability.getRootAbility();
@@ -7103,6 +7105,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
chosenModesGameStatic.put(original, ability.getGrantorStatic(), result); chosenModesGameStatic.put(original, ability.getGrantorStatic(), result);
} }
result.add(mode); result.add(mode);
if (yourCombat) {
result = chosenModesYourCombatStatic.get(original, ability.getGrantorStatic());
if (result == null) {
result = Lists.newArrayList();
chosenModesYourCombatStatic.put(original, ability.getGrantorStatic(), result);
}
}
} else { } else {
List<String> result = chosenModesTurn.get(original); List<String> result = chosenModesTurn.get(original);
if (result == null) { if (result == null) {
@@ -7117,6 +7126,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
chosenModesGame.put(original, result); chosenModesGame.put(original, result);
} }
result.add(mode); result.add(mode);
if (yourCombat) {
result = chosenModesYourCombat.get(original);
if (result == null) {
result = Lists.newArrayList();
chosenModesYourCombat.put(original, result);
}
result.add(mode);
}
} }
} }
@@ -7130,11 +7148,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public void addPlaneswalkerAbilityActivated() { public void addPlaneswalkerAbilityActivated() {
planeswalkerAbilityActivated++; // track if increased limit was used for activation because if there are also additional ones they can count on top
if (++planeswalkerAbilityActivated == 2 && StaticAbilityNumLoyaltyAct.limitIncrease(this)) {
planeswalkerActivationLimitUsed = true;
}
}
public boolean planeswalkerActivationLimitUsed() {
return planeswalkerActivationLimitUsed;
} }
public void resetActivationsPerTurn() { public void resetActivationsPerTurn() {
planeswalkerAbilityActivated = 0; planeswalkerAbilityActivated = 0;
planeswalkerActivationLimitUsed = false;
numberTurnActivations.clear(); numberTurnActivations.clear();
} }

View File

@@ -3855,7 +3855,7 @@ public class CardFactoryUtil {
SpellAbility saExile = AbilityFactory.getAbility(abExile, card); SpellAbility saExile = AbilityFactory.getAbility(abExile, card);
String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnCast$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card);
StringBuilder sbPlay = new StringBuilder(); StringBuilder sbPlay = new StringBuilder();

View File

@@ -984,8 +984,7 @@ public class CardProperty {
return false; return false;
} }
List<Card> cards = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Hand, "Card", source, spellAbility); if (!card.wasDiscarded()) {
if (!cards.contains(card) && !card.getMadnessWithoutCast()) {
return false; return false;
} }
} else if (property.startsWith("ControlledByPlayerInTheDirection")) { } else if (property.startsWith("ControlledByPlayerInTheDirection")) {

View File

@@ -357,6 +357,9 @@ public class PhaseHandler implements java.io.Serializable {
case COMBAT_END: case COMBAT_END:
// End Combat always happens // End Combat always happens
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
c.onEndOfCombat(playerTurn);
}
game.getEndOfCombat().executeAt(); game.getEndOfCombat().executeAt();
//SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc()); //SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc());

View File

@@ -1444,6 +1444,9 @@ public class Player extends GameEntity implements Comparable<Player> {
newCard = game.getAction().moveToGraveyard(c, sa, params); newCard = game.getAction().moveToGraveyard(c, sa, params);
// Play the Discard sound // Play the Discard sound
} }
newCard.setDiscarded(true);
if (table != null) { if (table != null) {
table.put(origin, newCard.getZone().getZoneType(), newCard); table.put(origin, newCard.getZone().getZoneType(), newCard);
} }

View File

@@ -6,6 +6,7 @@ package forge.game.replacement;
* *
*/ */
public enum ReplacementLayer { public enum ReplacementLayer {
CantHappen, // 614.17
Control, // 616.1b Control, // 616.1b
Copy, // 616.1c Copy, // 616.1c
Transform, // 616.1d Transform, // 616.1d

View File

@@ -664,9 +664,9 @@ public class AbilityManaPart implements java.io.Serializable {
// check for produce mana replacement effects - they mess this up, so just use the mana ability // check for produce mana replacement effects - they mess this up, so just use the mana ability
final Card source = am.getHostCard(); final Card source = am.getHostCard();
final Player activator = am.getActivatingPlayer(); final Player activator = am.getActivatingPlayer();
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromPlayer(activator); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(source);
repParams.put(AbilityKey.Mana, getOrigProduced()); repParams.put(AbilityKey.Mana, getOrigProduced());
repParams.put(AbilityKey.Affected, source); repParams.put(AbilityKey.Activator, activator);
repParams.put(AbilityKey.AbilityMana, am.getRootAbility()); repParams.put(AbilityKey.AbilityMana, am.getRootAbility());
if (!source.getGame().getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other).isEmpty()) { if (!source.getGame().getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other).isEmpty()) {

View File

@@ -1254,10 +1254,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false; return false;
} }
final TargetRestrictions tr = getTargetRestrictions(); final SpellAbility rootAbility = this.getRootAbility();
// 115.5. A spell or ability on the stack is an illegal target for itself.
// (This covers the spell case.)
if (rootAbility.isSpell() && rootAbility.getHostCard() == entity) {
return false;
}
// Restriction related to this ability // Restriction related to this ability
if (usesTargeting()) { if (usesTargeting()) {
final TargetRestrictions tr = getTargetRestrictions();
if (tr.isUniqueTargets() && getUniqueTargets().contains(entity)) if (tr.isUniqueTargets() && getUniqueTargets().contains(entity))
return false; return false;

View File

@@ -475,12 +475,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
} }
if (sa.isPwAbility()) { if (sa.isPwAbility()) {
final int initialLimit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 1 : 0;
final int limit = StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) + initialLimit;
int numActivates = c.getPlaneswalkerAbilityActivated(); int numActivates = c.getPlaneswalkerAbilityActivated();
if (numActivates > limit) { int limit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 2 : 1;
return false;
if (numActivates >= limit) {
// increased limit only counts if it's been used already
limit += StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) - (limit == 1 || c.planeswalkerActivationLimitUsed() ? 0 : 1);
if (numActivates >= limit) {
return false;
}
} }
} }

View File

@@ -563,6 +563,11 @@ public final class StaticAbilityContinuous {
p.setMaxHandSize(max); p.setMaxHandSize(max);
} }
} }
if (params.containsKey("RaiseMaxHandSize")) {
String rmhs = params.get("RaiseMaxHandSize");
int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb);
p.setMaxHandSize(p.getMaxHandSize() + rmax);
}
if (params.containsKey("AdjustLandPlays")) { if (params.containsKey("AdjustLandPlays")) {
String mhs = params.get("AdjustLandPlays"); String mhs = params.get("AdjustLandPlays");
@@ -573,6 +578,7 @@ public final class StaticAbilityContinuous {
p.addMaxLandPlays(se.getTimestamp(), add); p.addMaxLandPlays(se.getTimestamp(), add);
} }
} }
if (params.containsKey("ControlOpponentsSearchingLibrary")) { if (params.containsKey("ControlOpponentsSearchingLibrary")) {
Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), stAb), null); Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), stAb), null);
p.addControlledWhileSearching(se.getTimestamp(), cntl); p.addControlledWhileSearching(se.getTimestamp(), cntl);
@@ -592,12 +598,6 @@ public final class StaticAbilityContinuous {
p.addAdditionalOptionalVote(se.getTimestamp(), add); p.addAdditionalOptionalVote(se.getTimestamp(), add);
} }
if (params.containsKey("RaiseMaxHandSize")) {
String rmhs = params.get("RaiseMaxHandSize");
int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb);
p.setMaxHandSize(p.getMaxHandSize() + rmax);
}
if (params.containsKey("ManaConversion")) { if (params.containsKey("ManaConversion")) {
AbilityUtils.applyManaColorConversion(p.getManaPool(), params); AbilityUtils.applyManaColorConversion(p.getManaPool(), params);
} }

View File

@@ -100,8 +100,8 @@ public class StaticAbilityPanharmonicon {
} }
} else if (trigMode.equals(TriggerType.ChangesZoneAll)) { } else if (trigMode.equals(TriggerType.ChangesZoneAll)) {
// Check if the cards have a trigger at all // Check if the cards have a trigger at all
final String origin = stAb.getParamOrDefault("Origin", null); final String origin = stAb.getParam("Origin");
final String destination = stAb.getParamOrDefault("Destination", null); final String destination = stAb.getParam("Destination");
final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards);
if (table.filterCards(origin == null ? null : ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), card, stAb).isEmpty()) { if (table.filterCards(origin == null ? null : ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), card, stAb).isEmpty()) {

View File

@@ -1,12 +1,10 @@
package forge.game.trigger; package forge.game.trigger;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Localizer; import forge.util.Localizer;
@@ -79,11 +77,11 @@ public class TriggerDamageDoneOnce extends Trigger {
return result; return result;
} }
public Set<Card> getDamageSources(Map<Card, Integer> damageMap) { public CardCollection getDamageSources(Map<Card, Integer> damageMap) {
if (!hasParam("ValidSource")) { if (!hasParam("ValidSource")) {
return Sets.newHashSet(damageMap.keySet()); return new CardCollection(damageMap.keySet());
} }
Set<Card> result = Sets.newHashSet(); CardCollection result = new CardCollection();
for (Card c : damageMap.keySet()) { for (Card c : damageMap.keySet()) {
if (matchesValid(c, getParam("ValidSource").split(","))) { if (matchesValid(c, getParam("ValidSource").split(","))) {
result.add(c); result.add(c);

View File

@@ -288,7 +288,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (sp.getApi() == ApiType.Charm && sp.hasParam("ChoiceRestriction")) { if (sp.getApi() == ApiType.Charm && sp.hasParam("ChoiceRestriction")) {
// Remember the Choice here for later handling // Remember the Choice here for later handling
source.addChosenModes(sp, sp.getSubAbility().getDescription()); source.addChosenModes(sp, sp.getSubAbility().getDescription(), game.getPhaseHandler().inCombat());
} }
//cancel auto-pass for all opponents of activating player //cancel auto-pass for all opponents of activating player
@@ -857,7 +857,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
final Trigger trig = sa.getTrigger(); final Trigger trig = sa.getTrigger();
final Card newHost = game.getCardState(host); final Card newHost = game.getCardState(host);
if (host.isAura() && newHost.isInZone(ZoneType.Graveyard) && trig.getMode() == TriggerType.ChangesZone && if (host.isAura() && newHost.isInZone(ZoneType.Graveyard) && trig.getMode() == TriggerType.ChangesZone &&
trig.getParam("Destination").equals("Graveyard") && trig.getParam("ValidCard").equals("Card.EnchantedBy")) { "Graveyard".equals(trig.getParam("Destination")) && "Card.EnchantedBy".equals(trig.getParam("ValidCard"))) {
sa.setHostCard(newHost); sa.setHostCard(newHost);
} }
} }

View File

@@ -20,6 +20,7 @@ package forge.game.zone;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -63,7 +64,7 @@ public class PlayerZone extends Zone {
boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) || boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) ||
c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE) || c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE) ||
c.hasKeyword(Keyword.DISTURB); c.hasKeyword(Keyword.DISTURB);
boolean exileCastable = (c.isAdventureCard() || c.isForetold()) && c.isInZone(ZoneType.Exile); boolean exileCastable = c.isForetold() || isOnAdventure(c);
for (final SpellAbility sa : c.getSpellAbilities()) { for (final SpellAbility sa : c.getSpellAbilities()) {
final ZoneType restrictZone = sa.getRestrictions().getZone(); final ZoneType restrictZone = sa.getRestrictions().getZone();
@@ -92,6 +93,15 @@ public class PlayerZone extends Zone {
return false; return false;
} }
} }
private boolean isOnAdventure(Card c) {
if (!c.isAdventureCard())
return false;
if (c.getExiledWith() == null)
return false;
if (!CardStateName.Adventure.equals(c.getExiledWith().getCurrentStateName()))
return false;
return true;
}
private final Player player; private final Player player;

View File

@@ -65,16 +65,6 @@ public class PlayerZoneBattlefield extends PlayerZone {
} }
} }
/** {@inheritDoc} */
@Override
public final void remove(final Card c) {
super.remove(c);
if (trigger) {
c.runLeavesPlayCommands();
}
}
public final void setTriggers(final boolean b) { public final void setTriggers(final boolean b) {
trigger = b; trigger = b;
} }

View File

@@ -35,6 +35,7 @@ public class MessageUtil {
String choser = StringUtils.capitalize(mayBeYou(player, target)); String choser = StringUtils.capitalize(mayBeYou(player, target));
switch(sa.getApi()) { switch(sa.getApi()) {
case ChooseDirection: case ChooseDirection:
case DigMultiple:
return value; return value;
case ChooseColor: case ChooseColor:
return sa.hasParam("Random") return sa.hasParam("Random")

View File

@@ -840,9 +840,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
try { try {
if (StringUtils.isBlank(savedState)) { if (StringUtils.isBlank(savedState)) {
return new ArrayList<>(); return new ArrayList<>();
} else {
return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER));
} }
final String[] parts = savedState.split(";", -1);
return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER));
} catch (final Exception ex) { } catch (final Exception ex) {
System.err.println(ex + " [savedState=" + savedState + "]"); System.err.println(ex + " [savedState=" + savedState + "]");
return new ArrayList<>(); return new ArrayList<>();

View File

@@ -189,7 +189,7 @@ public class FDeckViewer extends FDialog {
sectionCards = new TreeMap<>(); sectionCards = new TreeMap<>();
deckList.append(nl); deckList.append(nl);
for (final Entry<PaperCard, Integer> ev : cp) { for (final Entry<PaperCard, Integer> ev : cp) {
cardName = ev.getKey().toString(); cardName = ev.getKey().getCardName();
if (sectionCards.containsKey(cardName)) { if (sectionCards.containsKey(cardName)) {
sectionCards.put(cardName, (int)sectionCards.get(cardName) + ev.getValue()); sectionCards.put(cardName, (int)sectionCards.get(cardName) + ev.getValue());
} }

View File

@@ -9,7 +9,7 @@ import forge.item.PaperCard;
import forge.itemmanager.ItemManager; import forge.itemmanager.ItemManager;
import forge.screens.home.quest.DialogChooseSets; import forge.screens.home.quest.DialogChooseSets;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
@@ -26,7 +26,9 @@ public class CardSetFilter extends CardFormatFilter {
public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, Collection<String> limitedSets0, boolean allowReprints0){ public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, Collection<String> limitedSets0, boolean allowReprints0){
this(itemManager0, sets0, allowReprints0); this(itemManager0, sets0, allowReprints0);
this.limitedSets.addAll(limitedSets0); if (limitedSets0 != null) {
this.limitedSets.addAll(limitedSets0);
}
} }
@Override @Override
@@ -60,7 +62,7 @@ public class CardSetFilter extends CardFormatFilter {
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, this.limitedSets, final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, this.limitedSets,
true, this.allowReprints); true, this.allowReprints);
final CardSetFilter itemFilter = this; final CardSetFilter itemFilter = this;
dialog.setOkCallback(new Runnable() { dialog.setOkCallback(new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@@ -1,7 +1,5 @@
package forge.screens.home; package forge.screens.home;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays; import java.util.Arrays;
import java.util.Vector; import java.util.Vector;
@@ -10,8 +8,6 @@ import javax.swing.SwingUtilities;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.deck.DeckProxy; import forge.deck.DeckProxy;
import forge.deck.DeckType;
import forge.deckchooser.FDeckChooser;
import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref; import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel; import forge.model.FModel;
@@ -22,12 +18,10 @@ public class CLobby {
private final VLobby view; private final VLobby view;
public CLobby(final VLobby view) { public CLobby(final VLobby view) {
this.view = view; this.view = view;
this.view.setForCommander(true);
} }
private void addDecks(final Iterable<DeckProxy> commanderDecks, FList<Object> deckList, String... initialItems) { private void addDecks(final Iterable<DeckProxy> commanderDecks, FList<Object> deckList, String... initialItems) {
Vector<Object> listData = new Vector<>(); Vector<Object> listData = new Vector<>(Arrays.asList(initialItems));
listData.addAll(Arrays.asList(initialItems));
listData.add("Generate"); listData.add("Generate");
if (!Iterables.isEmpty(commanderDecks)) { if (!Iterables.isEmpty(commanderDecks)) {
listData.add("Random"); listData.add("Random");
@@ -46,89 +40,38 @@ public class CLobby {
} }
public void update() { public void update() {
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(() -> {
@Override public final void run() { final Iterable<DeckProxy> schemeDecks = DeckProxy.getAllSchemeDecks();
final Iterable<DeckProxy> schemeDecks = DeckProxy.getAllSchemeDecks(); final Iterable<DeckProxy> planarDecks = DeckProxy.getAllPlanarDecks();
final Iterable<DeckProxy> planarDecks = DeckProxy.getAllPlanarDecks();
for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { for (int i = 0; i < VLobby.MAX_PLAYERS; i++) {
addDecks(schemeDecks, view.getSchemeDeckLists().get(i), addDecks(schemeDecks, view.getSchemeDeckLists().get(i),
"Use deck's scheme section (random if unavailable)"); "Use deck's scheme section (random if unavailable)");
addDecks(planarDecks, view.getPlanarDeckLists().get(i), addDecks(planarDecks, view.getPlanarDeckLists().get(i),
"Use deck's planes section (random if unavailable)"); "Use deck's planes section (random if unavailable)");
view.updateVanguardList(i); view.updateVanguardList(i);
}
// General updates when switching back to this view
view.getBtnStart().requestFocusInWindow();
} }
// General updates when switching back to this view
view.getBtnStart().requestFocusInWindow();
}); });
} }
public void initialize() { public void initialize() {
for (int iSlot = 0; iSlot < VLobby.MAX_PLAYERS; iSlot++) {
final FDeckChooser fdc = view.getDeckChooser(iSlot);
fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot));
fdc.populate();
/*fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() {
@Override public final void deckTypeSelected(final DecksComboBoxEvent ev) {
view.focusOnAvatar();
}
});*/
final FDeckChooser fdccom = view.getCommanderDeckChooser(iSlot);
fdccom.initialize(FPref.COMMANDER_DECK_STATES[iSlot], defaultDeckTypeForCommanderSlot(iSlot));
fdccom.populate();
final FDeckChooser fdobcom = view.getOathbreakerDeckChooser(iSlot);
fdobcom.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], defaultDeckTypeForOathbreakerSlot(iSlot));
fdobcom.populate();
final FDeckChooser fdtlcom = view.getTinyLeaderDeckChooser(iSlot);
fdtlcom.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], defaultDeckTypeForTinyLeaderSlot(iSlot));
fdtlcom.populate();
final FDeckChooser fdbcom = view.getBrawlDeckChooser(iSlot);
fdbcom.initialize(FPref.BRAWL_DECK_STATES[iSlot], defaultDeckTypeForBrawlSlot(iSlot));
fdbcom.populate();
}
final ForgePreferences prefs = FModel.getPreferences(); final ForgePreferences prefs = FModel.getPreferences();
// Checkbox event handling // Checkbox event handling
view.getCbSingletons().addActionListener(new ActionListener() { view.getCbSingletons().addActionListener(arg0 -> {
@Override prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected()));
public void actionPerformed(final ActionEvent arg0) { prefs.save();
prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected()));
prefs.save();
}
}); });
view.getCbArtifacts().addActionListener(new ActionListener() { view.getCbArtifacts().addActionListener(arg0 -> {
@Override prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected()));
public void actionPerformed(final ActionEvent arg0) { prefs.save();
prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected()));
prefs.save();
}
}); });
// Pre-select checkboxes // Pre-select checkboxes
view.getCbSingletons().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_SINGLETONS)); view.getCbSingletons().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_SINGLETONS));
view.getCbArtifacts().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_ARTIFACTS)); view.getCbArtifacts().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_ARTIFACTS));
} }
private static DeckType defaultDeckTypeForSlot(final int iSlot) {
return iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK;
}
private static DeckType defaultDeckTypeForCommanderSlot(final int iSlot) {
return iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
}
private static DeckType defaultDeckTypeForOathbreakerSlot(final int iSlot) {
return iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
}
private static DeckType defaultDeckTypeForTinyLeaderSlot(final int iSlot) {
return iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
}
private static DeckType defaultDeckTypeForBrawlSlot(final int iSlot) {
return iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK;
}
} }

View File

@@ -1,5 +1,6 @@
package forge.screens.home; package forge.screens.home;
import forge.deckchooser.FDeckChooser;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
@@ -103,6 +104,8 @@ public class PlayerPanel extends FPanel {
private boolean allowNetworking; private boolean allowNetworking;
private FDeckChooser deckChooser;
private final VLobby lobby; private final VLobby lobby;
public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) { public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) {
super(); super();
@@ -463,10 +466,6 @@ public class PlayerPanel extends FPanel {
radioAiUseSimulation.setSelected(useSimulation); radioAiUseSimulation.setSelected(useSimulation);
} }
public boolean isLocal() {
return type == LobbySlotType.LOCAL;
}
public boolean isArchenemy() { public boolean isArchenemy() {
return aeTeamComboBox.getSelectedIndex() == 0; return aeTeamComboBox.getSelectedIndex() == 0;
} }
@@ -540,9 +539,6 @@ public class PlayerPanel extends FPanel {
} }
}; };
/**
* @param index
*/
private void addHandlersToVariantsControls() { private void addHandlersToVariantsControls() {
// Archenemy buttons // Archenemy buttons
scmDeckSelectorBtn.setCommand(new Runnable() { scmDeckSelectorBtn.setCommand(new Runnable() {
@@ -621,9 +617,6 @@ public class PlayerPanel extends FPanel {
}); });
} }
/**
* @param index
*/
private void createPlayerTypeOptions() { private void createPlayerTypeOptions() {
radioHuman = new FRadioButton(localizer.getMessage("lblHuman")); radioHuman = new FRadioButton(localizer.getMessage("lblHuman"));
radioAi = new FRadioButton(localizer.getMessage("lblAI")); radioAi = new FRadioButton(localizer.getMessage("lblAI"));
@@ -674,9 +667,6 @@ public class PlayerPanel extends FPanel {
}); });
} }
/**
* @param index
*/
private void addHandlersDeckSelector() { private void addHandlersDeckSelector() {
deckBtn.setCommand(new Runnable() { deckBtn.setCommand(new Runnable() {
@Override @Override
@@ -688,10 +678,6 @@ public class PlayerPanel extends FPanel {
}); });
} }
/**
* @param index
* @return
*/
private FLabel createNameRandomizer() { private FLabel createNameRandomizer() {
final FLabel newNameBtn = new FLabel.Builder().tooltip(localizer.getMessage("lblGetaNewRandomName")).iconInBackground(false) final FLabel newNameBtn = new FLabel.Builder().tooltip(localizer.getMessage("lblGetaNewRandomName")).iconInBackground(false)
.icon(FSkin.getIcon(FSkinProp.ICO_EDIT)).hoverable(true).opaque(false) .icon(FSkin.getIcon(FSkinProp.ICO_EDIT)).hoverable(true).opaque(false)
@@ -717,10 +703,6 @@ public class PlayerPanel extends FPanel {
return newNameBtn; return newNameBtn;
} }
/**
* @param index
* @return
*/
private void createNameEditor() { private void createNameEditor() {
String name; String name;
if (index == 0) { if (index == 0) {
@@ -898,4 +880,12 @@ public class PlayerPanel extends FPanel {
public void setMayRemove(final boolean mayRemove) { public void setMayRemove(final boolean mayRemove) {
this.mayRemove = mayRemove; this.mayRemove = mayRemove;
} }
FDeckChooser getDeckChooser() {
return deckChooser;
}
void setDeckChooser(final FDeckChooser deckChooser) {
this.deckChooser = deckChooser;
}
} }

View File

@@ -4,11 +4,11 @@ import java.awt.Font;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Vector; import java.util.Vector;
import javax.swing.*; import javax.swing.*;
@@ -36,7 +36,6 @@ import forge.gamemodes.match.LobbySlotType;
import forge.gamemodes.net.event.UpdateLobbyPlayerEvent; import forge.gamemodes.net.event.UpdateLobbyPlayerEvent;
import forge.gui.CardDetailPanel; import forge.gui.CardDetailPanel;
import forge.gui.GuiBase; import forge.gui.GuiBase;
import forge.gui.UiCommand;
import forge.gui.interfaces.ILobbyView; import forge.gui.interfaces.ILobbyView;
import forge.gui.util.SOptionPane; import forge.gui.util.SOptionPane;
import forge.interfaces.IPlayerChangeListener; import forge.interfaces.IPlayerChangeListener;
@@ -105,22 +104,18 @@ public class VLobby implements ILobbyView {
private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3")); private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3"));
private final FScrollPanel playersScroll = new FScrollPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"), true); private final FScrollPanel playersScroll = new FScrollPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"), true);
private final List<PlayerPanel> playerPanels = new ArrayList<>(MAX_PLAYERS); private final List<PlayerPanel> playerPanels = new ArrayList<>(MAX_PLAYERS);
// Cache deck choosers so switching settings doesn't re-generate random decks.
private final Map<FPref, FDeckChooser> cachedDeckChoosers = new HashMap<>();
private final FLabel addPlayerBtn = new FLabel.ButtonBuilder().fontSize(14).text(localizer.getMessage("lblAddAPlayer")).build(); private final FLabel addPlayerBtn = new FLabel.ButtonBuilder().fontSize(14).text(localizer.getMessage("lblAddAPlayer")).build();
// Deck frame elements // Deck frame elements
private final JPanel decksFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3")); private final JPanel decksFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"));
private final List<FDeckChooser> deckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final FCheckBox cbSingletons = new FCheckBox(localizer.getMessage("cbSingletons")); private final FCheckBox cbSingletons = new FCheckBox(localizer.getMessage("cbSingletons"));
private final FCheckBox cbArtifacts = new FCheckBox(localizer.getMessage("cbRemoveArtifacts")); private final FCheckBox cbArtifacts = new FCheckBox(localizer.getMessage("cbRemoveArtifacts"));
private final Deck[] decks = new Deck[MAX_PLAYERS]; private final Deck[] decks = new Deck[MAX_PLAYERS];
// Variants // Variants
private final List<FDeckChooser> commanderDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FDeckChooser> oathbreakerDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FDeckChooser> tinyLeadersDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FDeckChooser> brawlDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FList<Object>> schemeDeckLists = new ArrayList<>(); private final List<FList<Object>> schemeDeckLists = new ArrayList<>();
private final List<FPanel> schemeDeckPanels = new ArrayList<>(MAX_PLAYERS); private final List<FPanel> schemeDeckPanels = new ArrayList<>(MAX_PLAYERS);
@@ -131,22 +126,11 @@ public class VLobby implements ILobbyView {
private final List<FPanel> vgdPanels = new ArrayList<>(MAX_PLAYERS); private final List<FPanel> vgdPanels = new ArrayList<>(MAX_PLAYERS);
private final List<CardDetailPanel> vgdAvatarDetails = new ArrayList<>(); private final List<CardDetailPanel> vgdAvatarDetails = new ArrayList<>();
private final List<PaperCard> vgdAllAvatars = new ArrayList<>(); private final List<PaperCard> vgdAllAvatars = new ArrayList<>();
private final List<PaperCard> vgdAllAiAvatars = new ArrayList<>();
private final List<PaperCard> nonRandomHumanAvatars = new ArrayList<>(); private final List<PaperCard> nonRandomHumanAvatars = new ArrayList<>();
private final List<PaperCard> nonRandomAiAvatars = new ArrayList<>(); private final List<PaperCard> nonRandomAiAvatars = new ArrayList<>();
private final Vector<Object> humanListData = new Vector<>(); private final Vector<Object> humanListData = new Vector<>();
private final Vector<Object> aiListData = new Vector<>(); private final Vector<Object> aiListData = new Vector<>();
public boolean isForCommander() {
return isForCommander;
}
public void setForCommander(boolean forCommander) {
isForCommander = forCommander;
}
private boolean isForCommander = false;
// CTR // CTR
public VLobby(final GameLobby lobby) { public VLobby(final GameLobby lobby) {
this.lobby = lobby; this.lobby = lobby;
@@ -178,12 +162,7 @@ public class VLobby implements ILobbyView {
if (lobby.hasControl()) { if (lobby.hasControl()) {
addPlayerBtn.setFocusable(true); addPlayerBtn.setFocusable(true);
addPlayerBtn.setCommand(new Runnable() { addPlayerBtn.setCommand(lobby::addSlot);
@Override
public final void run() {
lobby.addSlot();
}
});
playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx");
} }
@@ -207,7 +186,7 @@ public class VLobby implements ILobbyView {
// Start button event handling // Start button event handling
btnStart.addActionListener(new ActionListener() { btnStart.addActionListener(new ActionListener() {
@Override @Override
public final void actionPerformed(final ActionEvent arg0) { public void actionPerformed(final ActionEvent arg0) {
Runnable startGame = lobby.startGame(); Runnable startGame = lobby.startGame();
if (startGame != null) { if (startGame != null) {
if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) { if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) {
@@ -226,17 +205,8 @@ public class VLobby implements ILobbyView {
} }
public void updateDeckPanel() { public void updateDeckPanel() {
for (int iPlayer = 0; iPlayer < activePlayersNum; iPlayer++) { for (final PlayerPanel playerPanel : playerPanels) {
final FDeckChooser fdc = getDeckChooser(iPlayer); playerPanel.getDeckChooser().restoreSavedState();
fdc.restoreSavedState();
final FDeckChooser fdcom = getCommanderDeckChooser(iPlayer);
fdcom.restoreSavedState();
final FDeckChooser fdob = getOathbreakerDeckChooser(iPlayer);
fdob.restoreSavedState();
final FDeckChooser fdtl = getTinyLeaderDeckChooser(iPlayer);
fdtl.restoreSavedState();
final FDeckChooser fdbr = getBrawlDeckChooser(iPlayer);
fdbr.restoreSavedState();
} }
} }
@@ -244,8 +214,12 @@ public class VLobby implements ILobbyView {
getPlayerPanelWithFocus().focusOnAvatar(); getPlayerPanelWithFocus().focusOnAvatar();
} }
private PlayerPanel getPlayerPanel(int slot) {
return playerPanels.get(slot);
}
@Override @Override
public void update(final int slot, final LobbySlotType type){ public void update(final int slot, final LobbySlotType type) {
final FDeckChooser deckChooser = getDeckChooser(slot); final FDeckChooser deckChooser = getDeckChooser(slot);
deckChooser.setIsAi(type==LobbySlotType.AI); deckChooser.setIsAi(type==LobbySlotType.AI);
DeckType selectedDeckType = deckChooser.getSelectedDeckType(); DeckType selectedDeckType = deckChooser.getSelectedDeckType();
@@ -259,28 +233,13 @@ public class VLobby implements ILobbyView {
case COLOR_DECK: case COLOR_DECK:
case STANDARD_COLOR_DECK: case STANDARD_COLOR_DECK:
case MODERN_COLOR_DECK: case MODERN_COLOR_DECK:
case RANDOM_CARDGEN_COMMANDER_DECK:
case RANDOM_COMMANDER_DECK:
deckChooser.refreshDeckListForAI(); deckChooser.refreshDeckListForAI();
break; break;
default: default:
break; break;
} }
updateCommanderStyleDeckChooser(getCommanderDeckChooser(slot), type);
updateCommanderStyleDeckChooser(getOathbreakerDeckChooser(slot), type);
updateCommanderStyleDeckChooser(getTinyLeaderDeckChooser(slot), type);
updateCommanderStyleDeckChooser(getBrawlDeckChooser(slot), type);
}
private void updateCommanderStyleDeckChooser(final FDeckChooser deckChooser, final LobbySlotType type) {
deckChooser.setIsAi(type==LobbySlotType.AI);
DeckType selectedDeckType = deckChooser.getSelectedDeckType();
switch (selectedDeckType){
case RANDOM_CARDGEN_COMMANDER_DECK:
case RANDOM_COMMANDER_DECK:
deckChooser.refreshDeckListForAI();
break;
default:
break;
}
} }
@Override @Override
@@ -308,11 +267,6 @@ public class VLobby implements ILobbyView {
if (i < activePlayersNum) { if (i < activePlayersNum) {
// visible panels // visible panels
final LobbySlot slot = lobby.getSlot(i); final LobbySlot slot = lobby.getSlot(i);
final FDeckChooser deckChooser = getDeckChooser(i);
final FDeckChooser commanderDeckChooser = getCommanderDeckChooser(i);
final FDeckChooser oathbreakerDeckChooser = getOathbreakerDeckChooser(i);
final FDeckChooser tinyLeaderDeckChooser = getTinyLeaderDeckChooser(i);
final FDeckChooser brawlDeckChooser = getBrawlDeckChooser(i);
final PlayerPanel panel; final PlayerPanel panel;
final boolean isNewPanel; final boolean isNewPanel;
if (hasPanel) { if (hasPanel) {
@@ -326,15 +280,6 @@ public class VLobby implements ILobbyView {
constraints += ", gaptop 5px"; constraints += ", gaptop 5px";
} }
playersScroll.add(panel, constraints); playersScroll.add(panel, constraints);
deckChooser.restoreSavedState();
commanderDeckChooser.restoreSavedState();
oathbreakerDeckChooser.restoreSavedState();
tinyLeaderDeckChooser.restoreSavedState();
brawlDeckChooser.restoreSavedState();
if (i == 0) {
slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED));
changePlayerFocus(0);
}
isNewPanel = true; isNewPanel = true;
} }
@@ -353,14 +298,24 @@ public class VLobby implements ILobbyView {
panel.update(); panel.update();
final boolean isSlotAI = slot.getType() == LobbySlotType.AI; final boolean isSlotAI = slot.getType() == LobbySlotType.AI;
if (isNewPanel || fullUpdate) {
deckChooser.setIsAi(isSlotAI); final FDeckChooser deckChooser = createDeckChooser(lobby.getGameType(), i, isSlotAI);
commanderDeckChooser.setIsAi(isSlotAI); deckChooser.populate();
oathbreakerDeckChooser.setIsAi(isSlotAI); panel.setDeckChooser(deckChooser);
tinyLeaderDeckChooser.setIsAi(isSlotAI); if (i == 0) {
brawlDeckChooser.setIsAi(isSlotAI); // TODO: This seems like the wrong place to do this:
slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED));
changePlayerFocus(0);
}
} else {
panel.getDeckChooser().setIsAi(isSlotAI);
}
if (fullUpdate && (type == LobbySlotType.LOCAL || isSlotAI)) { if (fullUpdate && (type == LobbySlotType.LOCAL || isSlotAI)) {
selectDeck(i); // Deck section selection
panel.getDeckChooser().getLstDecks().getSelectCommand().run();
selectSchemeDeck(i);
selectPlanarDeck(i);
selectVanguardAvatar(i);
} }
if (isNewPanel) { if (isNewPanel) {
panel.setVisible(true); panel.setVisible(true);
@@ -373,7 +328,7 @@ public class VLobby implements ILobbyView {
if (playerWithFocus >= activePlayersNum) { if (playerWithFocus >= activePlayersNum) {
changePlayerFocus(activePlayersNum - 1); changePlayerFocus(activePlayersNum - 1);
} else { } else {
populateDeckPanel(getCurrentGameMode()); populateDeckPanel(lobby.getGameType());
} }
refreshPanels(true, true); refreshPanels(true, true);
} }
@@ -395,8 +350,7 @@ public class VLobby implements ILobbyView {
void setDevMode(final int index) { void setDevMode(final int index) {
// clear ready for everyone // clear ready for everyone
for (int i = 0; i < activePlayersNum; i++) { for (int i = 0; i < activePlayersNum; i++) {
final PlayerPanel panel = playerPanels.get(i); getPlayerPanel(i).setIsReady(false);
panel.setIsReady(false);
firePlayerChangeListener(i); firePlayerChangeListener(i);
} }
changePlayerFocus(index); changePlayerFocus(index);
@@ -430,7 +384,7 @@ public class VLobby implements ILobbyView {
} }
private UpdateLobbyPlayerEvent getSlot(final int index) { private UpdateLobbyPlayerEvent getSlot(final int index) {
final PlayerPanel panel = playerPanels.get(index); final PlayerPanel panel = getPlayerPanel(index);
return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), -1/*TODO panel.getSleeveIndex()*/, panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions()); return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), -1/*TODO panel.getSleeveIndex()*/, panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions());
} }
@@ -438,16 +392,6 @@ public class VLobby implements ILobbyView {
* These are added to a list which can be referenced to populate the deck panel appropriately. */ * These are added to a list which can be referenced to populate the deck panel appropriately. */
@SuppressWarnings("serial") @SuppressWarnings("serial")
private void buildDeckPanels(final int playerIndex) { private void buildDeckPanels(final int playerIndex) {
// Main deck
final FDeckChooser mainChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Constructed, false);
mainChooser.getLstDecks().setSelectCommand(new UiCommand() {
@Override public final void run() {
selectMainDeck(playerIndex);
}
});
mainChooser.initialize();
deckChoosers.add(mainChooser);
// Scheme deck list // Scheme deck list
buildDeckPanel(localizer.getMessage("lblSchemeDeck"), playerIndex, schemeDeckLists, schemeDeckPanels, new ListSelectionListener() { buildDeckPanel(localizer.getMessage("lblSchemeDeck"), playerIndex, schemeDeckLists, schemeDeckPanels, new ListSelectionListener() {
@Override public final void valueChanged(final ListSelectionEvent e) { @Override public final void valueChanged(final ListSelectionEvent e) {
@@ -455,42 +399,6 @@ public class VLobby implements ILobbyView {
} }
}); });
final FDeckChooser commanderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Commander, true);
commanderChooser.getLstDecks().setSelectCommand(new UiCommand() {
@Override public final void run() {
selectCommanderDeck(playerIndex);
}
});
commanderChooser.initialize();
commanderDeckChoosers.add(commanderChooser);
final FDeckChooser oathbreakerChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Oathbreaker, true);
oathbreakerChooser.getLstDecks().setSelectCommand(new UiCommand() {
@Override public final void run() {
selectOathbreakerDeck(playerIndex);
}
});
oathbreakerChooser.initialize();
oathbreakerDeckChoosers.add(oathbreakerChooser);
final FDeckChooser tinyLeaderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.TinyLeaders, true);
tinyLeaderChooser.getLstDecks().setSelectCommand(new UiCommand() {
@Override public final void run() {
selectTinyLeadersDeck(playerIndex);
}
});
tinyLeaderChooser.initialize();
tinyLeadersDeckChoosers.add(tinyLeaderChooser);
final FDeckChooser brawlChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Brawl, true);
brawlChooser.getLstDecks().setSelectCommand(new UiCommand() {
@Override public final void run() {
selectBrawlDeck(playerIndex);
}
});
brawlChooser.initialize();
brawlDeckChoosers.add(brawlChooser);
// Planar deck list // Planar deck list
buildDeckPanel(localizer.getMessage("lblPlanarDeck"), playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() { buildDeckPanel(localizer.getMessage("lblPlanarDeck"), playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() {
@Override public final void valueChanged(final ListSelectionEvent e) { @Override public final void valueChanged(final ListSelectionEvent e) {
@@ -532,26 +440,11 @@ public class VLobby implements ILobbyView {
deckPanels.add(deckPanel); deckPanels.add(deckPanel);
} }
private void selectDeck(final int playerIndex) { private FDeckChooser getDeckChooser(final int iSlot) {
// Full deck selection return getPlayerPanel(iSlot).getDeckChooser();
selectMainDeck(playerIndex);
selectCommanderDeck(playerIndex);
selectOathbreakerDeck(playerIndex);
selectTinyLeadersDeck(playerIndex);
selectBrawlDeck(playerIndex);
// Deck section selection
selectSchemeDeck(playerIndex);
selectPlanarDeck(playerIndex);
selectVanguardAvatar(playerIndex);
} }
private void selectMainDeck(final int playerIndex) { private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) {
if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) {
// These game types use specific deck panel
return;
}
final FDeckChooser mainChooser = getDeckChooser(playerIndex);
final DeckType type = mainChooser.getSelectedDeckType(); final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck(); final Deck deck = mainChooser.getDeck();
// something went wrong, clear selection to prevent error loop // something went wrong, clear selection to prevent error loop
@@ -561,75 +454,11 @@ public class VLobby implements ILobbyView {
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems(); final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setDeckSelectorButtonText(text); if (isCommanderDeck) {
fireDeckChangeListener(playerIndex, deck); getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text);
} } else {
mainChooser.saveState(); getPlayerPanel(playerIndex).setDeckSelectorButtonText(text);
} }
private void selectCommanderDeck(final int playerIndex) {
if (!hasVariant(GameType.Commander) && !hasVariant(GameType.Oathbreaker) && !hasVariant(GameType.TinyLeaders) && !hasVariant(GameType.Brawl)) {
// Only these game types use this specific deck panel
return;
}
final FDeckChooser mainChooser = getCommanderDeckChooser(playerIndex);
final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck();
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text);
fireDeckChangeListener(playerIndex, deck);
}
mainChooser.saveState();
}
private void selectOathbreakerDeck(final int playerIndex) {
if (!hasVariant(GameType.Oathbreaker)) {
// Only these game types use this specific deck panel
return;
}
final FDeckChooser mainChooser = getOathbreakerDeckChooser(playerIndex);
final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck();
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text);
fireDeckChangeListener(playerIndex, deck);
}
mainChooser.saveState();
}
private void selectTinyLeadersDeck(final int playerIndex) {
if (!hasVariant(GameType.TinyLeaders)) {
// Only these game types use this specific deck panel
return;
}
final FDeckChooser mainChooser = getTinyLeaderDeckChooser(playerIndex);
final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck();
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text);
fireDeckChangeListener(playerIndex, deck);
}
mainChooser.saveState();
}
private void selectBrawlDeck(final int playerIndex) {
if (!hasVariant(GameType.Brawl)) {
// Only these game types use this specific deck panel
return;
}
final FDeckChooser mainChooser = getBrawlDeckChooser(playerIndex);
final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck();
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text);
fireDeckChangeListener(playerIndex, deck); fireDeckChangeListener(playerIndex, deck);
} }
mainChooser.saveState(); mainChooser.saveState();
@@ -653,7 +482,7 @@ public class VLobby implements ILobbyView {
} }
} }
if (sel.equals("Random")) { if (sel.equals("Random")) {
final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex));
schemePool = randomDeck.get(DeckSection.Schemes); schemePool = randomDeck.get(DeckSection.Schemes);
} }
} else if (selected instanceof Deck) { } else if (selected instanceof Deck) {
@@ -684,7 +513,7 @@ public class VLobby implements ILobbyView {
} }
} }
if (sel.equals("Random")) { if (sel.equals("Random")) {
final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex));
planePool = randomDeck.get(DeckSection.Planes); planePool = randomDeck.get(DeckSection.Planes);
} }
} else if (selected instanceof Deck) { } else if (selected instanceof Deck) {
@@ -703,7 +532,7 @@ public class VLobby implements ILobbyView {
} }
final Object selected = vgdAvatarLists.get(playerIndex).getSelectedValue(); final Object selected = vgdAvatarLists.get(playerIndex).getSelectedValue();
final PlayerPanel pp = playerPanels.get(playerIndex); final PlayerPanel pp = getPlayerPanel(playerIndex);
final CardDetailPanel cdp = vgdAvatarDetails.get(playerIndex); final CardDetailPanel cdp = vgdAvatarDetails.get(playerIndex);
PaperCard vanguardAvatar = null; PaperCard vanguardAvatar = null;
@@ -726,7 +555,7 @@ public class VLobby implements ILobbyView {
if (sel.contains("Use deck's default avatar") && deck != null && deck.has(DeckSection.Avatar)) { if (sel.contains("Use deck's default avatar") && deck != null && deck.has(DeckSection.Avatar)) {
vanguardAvatar = deck.get(DeckSection.Avatar).get(0); vanguardAvatar = deck.get(DeckSection.Avatar).get(0);
} else { //Only other string is "Random" } else { //Only other string is "Random"
if (playerPanels.get(playerIndex).isAi()) { //AI if (isPlayerAI(playerIndex)) { //AI
vanguardAvatar = Aggregates.random(getNonRandomAiAvatars()); vanguardAvatar = Aggregates.random(getNonRandomAiAvatars());
} else { //Human } else { //Human
vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars()); vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars());
@@ -750,8 +579,8 @@ public class VLobby implements ILobbyView {
switch (forGameType) { switch (forGameType) {
case Constructed: case Constructed:
decksFrame.add(deckChoosers.get(playerWithFocus), "grow, push"); decksFrame.add(getDeckChooser(playerWithFocus), "grow, push");
if (deckChoosers.get(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) { if (getDeckChooser(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) {
final String strCheckboxConstraints = "h 30px!, gap 0 20px 0 0"; final String strCheckboxConstraints = "h 30px!, gap 0 20px 0 0";
decksFrame.add(cbSingletons, strCheckboxConstraints); decksFrame.add(cbSingletons, strCheckboxConstraints);
decksFrame.add(cbArtifacts, strCheckboxConstraints); decksFrame.add(cbArtifacts, strCheckboxConstraints);
@@ -766,16 +595,10 @@ public class VLobby implements ILobbyView {
} }
break; break;
case Commander: case Commander:
decksFrame.add(commanderDeckChoosers.get(playerWithFocus), "grow, push");
break;
case Oathbreaker: case Oathbreaker:
decksFrame.add(oathbreakerDeckChoosers.get(playerWithFocus), "grow, push");
break;
case TinyLeaders: case TinyLeaders:
decksFrame.add(tinyLeadersDeckChoosers.get(playerWithFocus), "grow, push");
break;
case Brawl: case Brawl:
decksFrame.add(brawlDeckChoosers.get(playerWithFocus), "grow, push"); decksFrame.add(getDeckChooser(playerWithFocus), "grow, push");
break; break;
case Planechase: case Planechase:
decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push"); decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push");
@@ -798,7 +621,13 @@ public class VLobby implements ILobbyView {
public LblHeader getLblTitle() { return lblTitle; } public LblHeader getLblTitle() { return lblTitle; }
public JPanel getConstructedFrame() { return constructedFrame; } public JPanel getConstructedFrame() { return constructedFrame; }
public JPanel getPanelStart() { return pnlStart; } public JPanel getPanelStart() { return pnlStart; }
public List<FDeckChooser> getDeckChoosers() { return Collections.unmodifiableList(deckChoosers); } public List<FDeckChooser> getDeckChoosers() {
List<FDeckChooser> choosers = new ArrayList<>(playerPanels.size());
for (final PlayerPanel playerPanel : playerPanels) {
choosers.add(playerPanel.getDeckChooser());
}
return choosers;
}
/** Gets the random deck checkbox for Singletons. */ /** Gets the random deck checkbox for Singletons. */
FCheckBox getCbSingletons() { return cbSingletons; } FCheckBox getCbSingletons() { return cbSingletons; }
@@ -816,29 +645,6 @@ public class VLobby implements ILobbyView {
return iPlayer == playerWithFocus; return iPlayer == playerWithFocus;
} }
public final FDeckChooser getDeckChooser(final int playernum) {
return deckChoosers.get(playernum);
}
public final FDeckChooser getCommanderDeckChooser(final int playernum) {
return commanderDeckChoosers.get(playernum);
}
public final FDeckChooser getOathbreakerDeckChooser(final int playernum) {
return oathbreakerDeckChoosers.get(playernum);
}
public final FDeckChooser getTinyLeaderDeckChooser(final int playernum) {
return tinyLeadersDeckChoosers.get(playernum);
}
public final FDeckChooser getBrawlDeckChooser(final int playernum) {
return brawlDeckChoosers.get(playernum);
}
GameType getCurrentGameMode() {
return lobby.getGameType();
}
void setCurrentGameMode(final GameType mode) { void setCurrentGameMode(final GameType mode) {
lobby.setGameType(mode); lobby.setGameType(mode);
update(true); update(true);
@@ -851,10 +657,6 @@ public class VLobby implements ILobbyView {
return true; return true;
} }
public int getNumPlayers() {
return activePlayersNum;
}
/** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */ /** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */
private void refreshPanels(final boolean refreshPlayerFrame, final boolean refreshDeckFrame) { private void refreshPanels(final boolean refreshPlayerFrame, final boolean refreshDeckFrame) {
if (refreshPlayerFrame) { if (refreshPlayerFrame) {
@@ -888,8 +690,8 @@ public class VLobby implements ILobbyView {
/** Saves avatar prefs for players one and two. */ /** Saves avatar prefs for players one and two. */
void updateAvatarPrefs() { void updateAvatarPrefs() {
final int pOneIndex = playerPanels.get(0).getAvatarIndex(); final int pOneIndex = getPlayerPanel(0).getAvatarIndex();
final int pTwoIndex = playerPanels.get(1).getAvatarIndex(); final int pTwoIndex = getPlayerPanel(1).getAvatarIndex();
prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex); prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex);
prefs.save(); prefs.save();
@@ -897,8 +699,8 @@ public class VLobby implements ILobbyView {
/** Saves sleeve prefs for players one and two. */ /** Saves sleeve prefs for players one and two. */
void updateSleevePrefs() { void updateSleevePrefs() {
final int pOneIndex = playerPanels.get(0).getSleeveIndex(); final int pOneIndex = getPlayerPanel(0).getSleeveIndex();
final int pTwoIndex = playerPanels.get(1).getSleeveIndex(); final int pTwoIndex = getPlayerPanel(1).getSleeveIndex();
prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex); prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex);
prefs.save(); prefs.save();
@@ -925,7 +727,6 @@ public class VLobby implements ILobbyView {
return usedSleeves; return usedSleeves;
} }
private static final ImmutableList<String> genderOptions = ImmutableList.of("Male", "Female", "Any"), private static final ImmutableList<String> genderOptions = ImmutableList.of("Male", "Female", "Any"),
typeOptions = ImmutableList.of("Fantasy", "Generic", "Any"); typeOptions = ImmutableList.of("Fantasy", "Generic", "Any");
final String getNewName() { final String getNewName() {
@@ -973,18 +774,57 @@ public class VLobby implements ILobbyView {
this.variant = variantType; this.variant = variantType;
setToolTipText(variantType.getDescription()); setToolTipText(variantType.getDescription());
addItemListener(new ItemListener() { addItemListener(e -> {
@Override public final void itemStateChanged(final ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) {
if (e.getStateChange() == ItemEvent.SELECTED) { lobby.applyVariant(variantType);
lobby.applyVariant(variantType); } else {
} else { lobby.removeVariant(variantType);
lobby.removeVariant(variantType);
}
} }
VLobby.this.update(false);
}); });
} }
} }
private FDeckChooser createDeckChooser(final GameType type, final int iSlot, final boolean ai) {
boolean forCommander;
DeckType deckType;
FPref prefKey;
switch (type) {
case Commander:
forCommander = true;
deckType = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
prefKey = FPref.COMMANDER_DECK_STATES[iSlot];
break;
case TinyLeaders:
forCommander = true;
deckType = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
prefKey = FPref.TINY_LEADER_DECK_STATES[iSlot];
break;
case Oathbreaker:
forCommander = true;
deckType = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
prefKey = FPref.OATHBREAKER_DECK_STATES[iSlot];
break;
case Brawl:
forCommander = true;
deckType = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK;
prefKey = FPref.BRAWL_DECK_STATES[iSlot];
break;
default:
forCommander = false;
deckType = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK;
prefKey = FPref.CONSTRUCTED_DECK_STATES[iSlot];
break;
}
return cachedDeckChoosers.computeIfAbsent(prefKey, (key) -> {
final GameType gameType = forCommander ? type : GameType.Constructed;
final FDeckChooser fdc = new FDeckChooser(null, ai, gameType, forCommander);
fdc.initialize(prefKey, deckType);
fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, forCommander));
return fdc;
});
}
final ActionListener nameListener = new ActionListener() { final ActionListener nameListener = new ActionListener() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
@@ -1007,7 +847,7 @@ public class VLobby implements ILobbyView {
} }
public boolean isPlayerArchenemy(final int playernum) { public boolean isPlayerArchenemy(final int playernum) {
return playerPanels.get(playernum).isArchenemy(); return getPlayerPanel(playernum).isArchenemy();
} }
/** Gets the list of Vanguard avatar lists. */ /** Gets the list of Vanguard avatar lists. */
@@ -1027,11 +867,6 @@ public class VLobby implements ILobbyView {
return vgdAllAvatars; return vgdAllAvatars;
} }
/** Return the Vanguard avatars not flagged RemoveDeck:All. */
public List<PaperCard> getAllAiAvatars() {
return vgdAllAiAvatars;
}
/** Return the Vanguard avatars not flagged RemoveDeck:Random. */ /** Return the Vanguard avatars not flagged RemoveDeck:Random. */
public List<PaperCard> getNonRandomHumanAvatars() { public List<PaperCard> getNonRandomHumanAvatars() {
return nonRandomHumanAvatars; return nonRandomHumanAvatars;
@@ -1055,7 +890,6 @@ public class VLobby implements ILobbyView {
} }
if (!cp.getRules().getAiHints().getRemAIDecks()) { if (!cp.getRules().getAiHints().getRemAIDecks()) {
aiListData.add(cp); aiListData.add(cp);
vgdAllAiAvatars.add(cp);
if (!cp.getRules().getAiHints().getRemRandomDecks()) { if (!cp.getRules().getAiHints().getRemRandomDecks()) {
nonRandomAiAvatars.add(cp); nonRandomAiAvatars.add(cp);
} }

View File

@@ -28,31 +28,32 @@ public final class CDev implements ICDoc {
this.view = new VDev(this); this.view = new VDev(this);
addListener(view); addListener(view);
view.getLblUnlimitedLands().addMouseListener(madUnlimited); view.getLblUnlimitedLands().addMouseListener(onClick(this::togglePlayManyLandsPerTurn));
view.getLblViewAll().addMouseListener(madViewAll); view.getLblViewAll().addMouseListener(onClick(this::toggleViewAllCards));
view.getLblGenerateMana().addMouseListener(madMana); view.getLblGenerateMana().addMouseListener(onClick(this::generateMana));
view.getLblSetupGame().addMouseListener(madSetup); view.getLblSetupGame().addMouseListener(onClick(this::setupGameState));
view.getLblDumpGame().addMouseListener(madDump); view.getLblDumpGame().addMouseListener(onClick(this::dumpGameState));
view.getLblTutor().addMouseListener(madTutor); view.getLblTutor().addMouseListener(onClick(this::tutorForCard));
view.getLblCardToHand().addMouseListener(madCardToHand); view.getLblCardToHand().addMouseListener(onClick(this::addCardToHand));
view.getLblExileFromHand().addMouseListener(madExileFromHand); view.getLblExileFromHand().addMouseListener(onClick(this::exileCardsFromHand));
view.getLblCardToBattlefield().addMouseListener(madCardToBattlefield); view.getLblCardToBattlefield().addMouseListener(onClick(this::addCardToBattlefield));
view.getLblCardToLibrary().addMouseListener(madCardToLibrary); view.getLblCardToLibrary().addMouseListener(onClick(this::addCardToLibrary));
view.getLblCardToGraveyard().addMouseListener(madCardToGraveyard); view.getLblCardToGraveyard().addMouseListener(onClick(this::addCardToGraveyard));
view.getLblCardToExile().addMouseListener(madCardToExile); view.getLblCardToExile().addMouseListener(onClick(this::addCardToExile));
view.getLblCastSpell().addMouseListener(madCastASpell); view.getLblCastSpell().addMouseListener(onClick(this::castASpell));
view.getLblRepeatAddCard().addMouseListener(madRepeatAddCard); view.getLblRepeatAddCard().addMouseListener(onClick(this::repeatAddCard));
view.getLblAddCounterPermanent().addMouseListener(madAddCounter); view.getLblAddCounterPermanent().addMouseListener(onClick(this::addCounterToPermanent));
view.getLblSubCounterPermanent().addMouseListener(madSubCounter); view.getLblSubCounterPermanent().addMouseListener(onClick(this::removeCountersFromPermanent));
view.getLblTapPermanent().addMouseListener(madTap); view.getLblTapPermanent().addMouseListener(onClick(this::tapPermanent));
view.getLblUntapPermanent().addMouseListener(madUntap); view.getLblUntapPermanent().addMouseListener(onClick(this::untapPermanent));
view.getLblSetLife().addMouseListener(madLife); view.getLblSetLife().addMouseListener(onClick(this::setPlayerLife));
view.getLblWinGame().addMouseListener(madWinGame); view.getLblWinGame().addMouseListener(onClick(this::winGame));
view.getLblExileFromPlay().addMouseListener(madExileFromPlay); view.getLblExileFromPlay().addMouseListener(onClick(this::exileCardsFromPlay));
view.getLblRemoveFromGame().addMouseListener(madRemoveFromGame); view.getLblRemoveFromGame().addMouseListener(onClick(this::removeCardsFromGame));
view.getLblRiggedRoll().addMouseListener(madRiggedRoll); view.getLblRiggedRoll().addMouseListener(onClick(this::riggedPlanerRoll));
view.getLblWalkTo().addMouseListener(madWalkToPlane); view.getLblWalkTo().addMouseListener(onClick(this::planeswalkTo));
view.getLblAskAI().addMouseListener(madAskAI); view.getLblAskAI().addMouseListener(onClick(this::askAI));
view.getLblAskSimulationAI().addMouseListener(onClick(this::askSimulationAI));
} }
public IGameController getController() { public IGameController getController() {
return matchUI.getGameController(); return matchUI.getGameController();
@@ -66,258 +67,99 @@ public final class CDev implements ICDoc {
listeners.add(listener); listeners.add(listener);
} }
private final MouseListener madUnlimited = new MouseAdapter() { private MouseListener onClick(Runnable r) {
@Override return new MouseAdapter() {
public void mousePressed(final MouseEvent e) { @Override
togglePlayManyLandsPerTurn(); public void mousePressed(final MouseEvent e) {
} r.run();
}; }
};
}
public void togglePlayManyLandsPerTurn() { public void togglePlayManyLandsPerTurn() {
final boolean newValue = !view.getLblUnlimitedLands().getToggled(); final boolean newValue = !view.getLblUnlimitedLands().getToggled();
getController().cheat().setCanPlayUnlimitedLands(newValue); getController().cheat().setCanPlayUnlimitedLands(newValue);
update(); update();
} }
private final MouseListener madViewAll = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
toggleViewAllCards();
}
};
public void toggleViewAllCards() { public void toggleViewAllCards() {
final boolean newValue = !view.getLblViewAll().getToggled(); final boolean newValue = !view.getLblViewAll().getToggled();
getController().cheat().setViewAllCards(newValue); getController().cheat().setViewAllCards(newValue);
update(); update();
} }
private final MouseListener madMana = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
generateMana();
}
};
public void generateMana() { public void generateMana() {
getController().cheat().generateMana(); getController().cheat().generateMana();
} }
private final MouseListener madSetup = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
setupGameState();
}
};
public void setupGameState() { public void setupGameState() {
getController().cheat().setupGameState(); getController().cheat().setupGameState();
} }
private final MouseListener madDump = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
dumpGameState();
}
};
public void dumpGameState() { public void dumpGameState() {
getController().cheat().dumpGameState(); getController().cheat().dumpGameState();
} }
private final MouseListener madTutor = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
tutorForCard();
}
};
public void tutorForCard() { public void tutorForCard() {
getController().cheat().tutorForCard(); getController().cheat().tutorForCard();
} }
private final MouseListener madCardToHand = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToHand();
}
};
public void addCardToHand() { public void addCardToHand() {
getController().cheat().addCardToHand(); getController().cheat().addCardToHand();
} }
private final MouseListener madCardToLibrary = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToLibrary();
}
};
public void addCardToLibrary() { public void addCardToLibrary() {
getController().cheat().addCardToLibrary(); getController().cheat().addCardToLibrary();
} }
private final MouseListener madCardToGraveyard = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToGraveyard();
}
};
public void addCardToGraveyard() { public void addCardToGraveyard() {
getController().cheat().addCardToGraveyard(); getController().cheat().addCardToGraveyard();
} }
private final MouseListener madCardToExile = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToExile();
}
};
public void addCardToExile() { public void addCardToExile() {
getController().cheat().addCardToExile(); getController().cheat().addCardToExile();
} }
private final MouseListener madCastASpell = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
castASpell();
}
};
public void castASpell() { public void castASpell() {
getController().cheat().castASpell(); getController().cheat().castASpell();
} }
private final MouseListener madRepeatAddCard = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
repeatAddCard();
}
};
public void repeatAddCard() { public void repeatAddCard() {
getController().cheat().repeatLastAddition(); getController().cheat().repeatLastAddition();
} }
private final MouseListener madExileFromHand = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
exileCardsFromHand();
}
};
public void exileCardsFromHand() { public void exileCardsFromHand() {
getController().cheat().exileCardsFromHand(); getController().cheat().exileCardsFromHand();
} }
private final MouseListener madAddCounter = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCounterToPermanent();
}
};
public void addCounterToPermanent() { public void addCounterToPermanent() {
getController().cheat().addCountersToPermanent(); getController().cheat().addCountersToPermanent();
} }
private final MouseListener madSubCounter = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
removeCountersFromPermanent();
}
};
public void removeCountersFromPermanent() { public void removeCountersFromPermanent() {
getController().cheat().removeCountersFromPermanent(); getController().cheat().removeCountersFromPermanent();
} }
private final MouseListener madTap = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
tapPermanent();
}
};
public void tapPermanent() { public void tapPermanent() {
getController().cheat().tapPermanents(); getController().cheat().tapPermanents();
} }
private final MouseListener madUntap = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
untapPermanent();
}
};
public void untapPermanent() { public void untapPermanent() {
getController().cheat().untapPermanents(); getController().cheat().untapPermanents();
} }
private final MouseListener madLife = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
setPlayerLife();
}
};
public void setPlayerLife() { public void setPlayerLife() {
getController().cheat().setPlayerLife(); getController().cheat().setPlayerLife();
} }
private final MouseListener madWinGame = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
winGame();
}
};
public void winGame() { public void winGame() {
getController().cheat().winGame(); getController().cheat().winGame();
} }
private final MouseListener madCardToBattlefield = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToBattlefield();
}
};
public void addCardToBattlefield() { public void addCardToBattlefield() {
getController().cheat().addCardToBattlefield(); getController().cheat().addCardToBattlefield();
} }
private final MouseListener madExileFromPlay = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
exileCardsFromPlay();
}
};
public void exileCardsFromPlay() { public void exileCardsFromPlay() {
getController().cheat().exileCardsFromBattlefield(); getController().cheat().exileCardsFromBattlefield();
} }
private final MouseListener madRemoveFromGame = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
removeCardsFromGame();
}
};
public void removeCardsFromGame() { public void removeCardsFromGame() {
getController().cheat().removeCardsFromGame(); getController().cheat().removeCardsFromGame();
} }
private final MouseListener madRiggedRoll = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
riggedPlanerRoll();
}
};
public void riggedPlanerRoll() { public void riggedPlanerRoll() {
getController().cheat().riggedPlanarRoll(); getController().cheat().riggedPlanarRoll();
} }
private final MouseListener madWalkToPlane = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
planeswalkTo();
}
};
public void planeswalkTo() { public void planeswalkTo() {
getController().cheat().planeswalkTo(); getController().cheat().planeswalkTo();
} }
private final MouseListener madAskAI = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
askAI();
}
};
public void askAI() { public void askAI() {
getController().cheat().askAI(); getController().cheat().askAI(false);
}
public void askSimulationAI() {
getController().cheat().askAI(true);
} }
//========== End mouse listener inits //========== End mouse listener inits

View File

@@ -82,6 +82,7 @@ public class VDev implements IVDoc<CDev>, IDevListener {
private final DevLabel lblWalkTo = new DevLabel(Localizer.getInstance().getMessage("lblWalkTo")); private final DevLabel lblWalkTo = new DevLabel(Localizer.getInstance().getMessage("lblWalkTo"));
private final DevLabel lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI")); private final DevLabel lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI"));
private final DevLabel lblAskSimulationAI = new DevLabel(Localizer.getInstance().getMessage("lblAskSimulationAI"));
private final CDev controller; private final CDev controller;
@@ -91,7 +92,6 @@ public class VDev implements IVDoc<CDev>, IDevListener {
public VDev(final CDev controller) { public VDev(final CDev controller) {
this.controller = controller; this.controller = controller;
final String constraints = "w 95%!, gap 0 0 4px 0";
final String halfConstraints = "w 47%!, gap 0 0 4px 0"; final String halfConstraints = "w 47%!, gap 0 0 4px 0";
final String halfConstraintsLeft = halfConstraints + ", split 2"; final String halfConstraintsLeft = halfConstraints + ", split 2";
viewport.setOpaque(false); viewport.setOpaque(false);
@@ -113,13 +113,14 @@ public class VDev implements IVDoc<CDev>, IDevListener {
viewport.add(this.lblWinGame, halfConstraints); viewport.add(this.lblWinGame, halfConstraints);
viewport.add(this.lblAddCounterPermanent, halfConstraintsLeft); viewport.add(this.lblAddCounterPermanent, halfConstraintsLeft);
viewport.add(this.lblSubCounterPermanent, halfConstraints); viewport.add(this.lblSubCounterPermanent, halfConstraints);
viewport.add(this.lblSetupGame, halfConstraintsLeft);
viewport.add(this.lblDumpGame, halfConstraints);
viewport.add(this.lblTapPermanent, halfConstraintsLeft); viewport.add(this.lblTapPermanent, halfConstraintsLeft);
viewport.add(this.lblUntapPermanent, halfConstraints); viewport.add(this.lblUntapPermanent, halfConstraints);
viewport.add(this.lblRiggedRoll, halfConstraintsLeft); viewport.add(this.lblRiggedRoll, halfConstraintsLeft);
viewport.add(this.lblWalkTo, halfConstraints); viewport.add(this.lblWalkTo, halfConstraints);
viewport.add(this.lblAskAI, halfConstraintsLeft); viewport.add(this.lblAskAI, halfConstraintsLeft);
viewport.add(this.lblAskSimulationAI, halfConstraintsLeft);
viewport.add(this.lblSetupGame, halfConstraintsLeft);
viewport.add(this.lblDumpGame, halfConstraints);
} }
//========= Overridden methods //========= Overridden methods
@@ -302,20 +303,23 @@ public class VDev implements IVDoc<CDev>, IDevListener {
return this.lblAskAI; return this.lblAskAI;
} }
public DevLabel getLblAskSimulationAI() {
return this.lblAskSimulationAI;
}
/** /**
* Labels that act as buttons which control dev mode functions. Labels are * Labels that act as buttons which control dev mode functions. Labels are
* used to support multiline text. * used to support multiline text.
*/ */
public class DevLabel extends SkinnedLabel { public static class DevLabel extends SkinnedLabel {
private static final long serialVersionUID = 7917311680519060700L; private static final long serialVersionUID = 7917311680519060700L;
private FSkin.SkinColor defaultBG; private FSkin.SkinColor defaultBG;
private final FSkin.SkinColor hoverBG = FSkin.getColor(FSkin.Colors.CLR_HOVER); private final FSkin.SkinColor hoverBG = FSkin.getColor(FSkin.Colors.CLR_HOVER);
private final FSkin.SkinColor pressedBG = FSkin.getColor(FSkin.Colors.CLR_INACTIVE); private final FSkin.SkinColor pressedBG = FSkin.getColor(FSkin.Colors.CLR_INACTIVE);
private boolean toggled; private boolean toggled;
private int w, h; // Width, height, radius, insets (for paintComponent)
private final int r, i; private final int r, i; // Radius, insets (for paintComponent)
public DevLabel(final String text0) { public DevLabel(final String text0) {
super(); super();
@@ -380,10 +384,10 @@ public class VDev implements IVDoc<CDev>, IDevListener {
*/ */
@Override @Override
protected void paintComponent(final Graphics g) { protected void paintComponent(final Graphics g) {
this.w = this.getWidth(); int w = this.getWidth();
this.h = this.getHeight(); int h = this.getHeight();
g.setColor(this.getBackground()); g.setColor(this.getBackground());
g.fillRoundRect(this.i, this.i, this.w - (2 * this.i), this.h - this.i, this.r, this.r); g.fillRoundRect(this.i, this.i, w - (2 * this.i), h - this.i, this.r, this.r);
super.paintComponent(g); super.paintComponent(g);
} }
} }

View File

@@ -483,4 +483,126 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
AssertJUnit.assertEquals("Chaos Warp", sa.getHostCard().getName()); AssertJUnit.assertEquals("Chaos Warp", sa.getHostCard().getName());
AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard()); AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard());
} }
@Test
public void testNoSimulationsWhenNoTargets() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Island", p);
addCard("Island", p);
addCardToZone("Counterspell", p, ZoneType.Hand);
addCardToZone("Unsummon", p, ZoneType.Hand);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNull(sa);
AssertJUnit.assertEquals(0, picker.getNumSimulations());
}
@Test
public void testLandDropPruning() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCardToZone("Island", p, ZoneType.Hand);
addCardToZone("Island", p, ZoneType.Hand);
addCardToZone("Island", p, ZoneType.Hand);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNotNull(sa);
// Only one land drop should be simulated, since the cards are identical.
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
@Test
public void testSpellCantTargetSelf() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
Player opponent = game.getPlayers().get(0);
addCardToZone("Unsubstantiate", p, ZoneType.Hand);
addCard("Forest", p);
addCard("Island", p);
Card expectedTarget = addCard("Flying Men", opponent);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNotNull(sa);
AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard());
// Only a single simulation expected (no target self).
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
@Test
public void testModalSpellCantTargetSelf() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
Player opponent = game.getPlayers().get(0);
addCardToZone("Decisive Denial", p, ZoneType.Hand);
addCard("Forest", p);
addCard("Island", p);
addCard("Runeclaw Bear", p);
addCard("Flying Men", opponent);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNotNull(sa);
// Expected: Runeclaw Bear fights Flying Men
// Only a single simulation expected (no target self).
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
@Test
public void testModalSpellNoTargetsForModeWithSubAbility() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCardToZone("Temur Charm", p, ZoneType.Hand);
addCard("Forest", p);
addCard("Island", p);
addCard("Mountain", p);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
picker.chooseSpellAbilityToPlay(null);
// Only mode "Creatures with power 3 or less cant block this turn" should be simulated.
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
@Test
public void testModalSpellNoTargetsForAnyModes() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCardToZone("Drown in the Loch", p, ZoneType.Hand);
addCard("Swamp", p);
addCard("Island", p);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
picker.chooseSpellAbilityToPlay(null);
// TODO: Ideally, this would be 0 simulations, but we currently only determine there are no
// valid modes in SpellAbilityChoicesIterator, which runs already when we're simulating.
// Still, this test case exercises the code path and ensures we don't crash in this case.
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
} }

View File

@@ -41,7 +41,7 @@ public class PointOfInterestChanges implements SaveFileContent {
} }
data.storeObject("keys",keys.toArray(new String[0])); data.storeObject("keys",keys.toArray(new String[0]));
for(int i=0;i<items.size();i++) for(int i=0;i<items.size();i++)
data.store("value_"+i,items.get(0).save()); data.store("value_"+i,items.get(i).save());
return data; return data;
} }
} }

View File

@@ -79,7 +79,6 @@ public class WorldSave {
System.err.println("Generating New World"); System.err.println("Generating New World");
currentSave.world.generateNew(0); currentSave.world.generateNew(0);
} }
currentSave.pointOfInterestChanges.clear();
currentSave.onLoadList.emit(); currentSave.onLoadList.emit();

View File

@@ -1360,9 +1360,8 @@ public class FDeckChooser extends FScreen {
if (StringUtils.isBlank(savedState)) { if (StringUtils.isBlank(savedState)) {
return new ArrayList<>(); return new ArrayList<>();
} }
else { final String[] parts = savedState.split(";", -1);
return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER)); return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER));
}
} }
catch (Exception ex) { catch (Exception ex) {
System.err.println(ex + " [savedState=" + savedState + "]"); System.err.println(ex + " [savedState=" + savedState + "]");

View File

@@ -99,7 +99,7 @@ public class FDeckViewer extends FScreen {
deckList.append(s.toString()).append(": "); deckList.append(s.toString()).append(": ");
deckList.append(nl); deckList.append(nl);
for (final Entry<PaperCard, Integer> ev : cp) { for (final Entry<PaperCard, Integer> ev : cp) {
deckList.append(ev.getValue()).append(" ").append(ev.getKey()).append(nl); deckList.append(ev.getValue()).append(" ").append(ev.getKey().getCardName()).append(nl);
} }
deckList.append(nl); deckList.append(nl);
} }

View File

@@ -3,6 +3,6 @@ ManaCost:B R
Types:Creature Sliver Types:Creature Sliver
PT:2/2 PT:2/2
S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Damage | Description$ All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Damage | Description$ All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target."
SVar:Damage:AB$DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. SVar:Damage:AB$ DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target.
SVar:BuffedBy:Sliver SVar:BuffedBy:Sliver
Oracle:All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." Oracle:All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target."

View File

@@ -9,7 +9,7 @@ SVar:VolverStrength:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | Counter
SVar:VolverLaunch:DB$ Animate | Defined$ Self | Keywords$ Flying | Duration$ Permanent SVar:VolverLaunch:DB$ Animate | Defined$ Self | Keywords$ Flying | Duration$ Permanent
SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ETB$ True | SubAbility$ VolverResilience | SpellDescription$ If CARDNAME was kicked with its {B} kicker, it enters the battlefield with a +1/+1 counter on it and with "Pay 3 life: Regenerate CARDNAME." SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ETB$ True | SubAbility$ VolverResilience | SpellDescription$ If CARDNAME was kicked with its {B} kicker, it enters the battlefield with a +1/+1 counter on it and with "Pay 3 life: Regenerate CARDNAME."
SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent
SVar:ABRegen:AB$Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME. SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME.
AI:RemoveDeck:Random AI:RemoveDeck:Random
DeckNeeds:Color$Blue|Black DeckNeeds:Color$Blue|Black
DeckHas:Ability$Counters DeckHas:Ability$Counters

View File

@@ -4,7 +4,7 @@ Types:Enchantment Aura
K:Enchant land K:Enchant land
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Land | AILogic$ Pump A:SP$ Attach | Cost$ 2 W | ValidTgts$ Land | AILogic$ Pump
S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness." S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness."
SVar:GainLife:AB$GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness. SVar:GainLife:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
SVar:AnimalBoneyardX:Sacrificed$CardToughness SVar:AnimalBoneyardX:Sacrificed$CardToughness
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:NonStackingAttachEffect:True SVar:NonStackingAttachEffect:True

View File

@@ -3,13 +3,14 @@ ManaCost:1 B
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant creature card in a graveyard K:Enchant creature card in a graveyard
A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay
SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When Animate Dead leaves the battlefield, that creature's controller sacrifices it. SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DelayTriggerRememberedLKI
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0.
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard

View File

@@ -4,5 +4,5 @@ Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 2 R | ValidTgts$ Creature | AILogic$ Pump A:SP$ Attach | Cost$ 2 R | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | AddAbility$ Damage | Description$ Enchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | AddAbility$ Damage | Description$ Enchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target."
SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target.
Oracle:Enchant creature (Target a creature as you cast this. This card enters the battlefield attached to that creature.)\nEnchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." Oracle:Enchant creature (Target a creature as you cast this. This card enters the battlefield attached to that creature.)\nEnchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target."

View File

@@ -4,6 +4,6 @@ Types:Enchantment Aura
K:Enchant land K:Enchant land
A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Land | AILogic$ Pump A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Land | AILogic$ Pump
S:Mode$ Continuous | Affected$ Land.EnchantedBy | AddAbility$ Damage | Description$ Enchanted land has "{T}: This land deals 1 damage to any target." S:Mode$ Continuous | Affected$ Land.EnchantedBy | AddAbility$ Damage | Description$ Enchanted land has "{T}: This land deals 1 damage to any target."
SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target.
SVar:NonStackingAttachEffect:True SVar:NonStackingAttachEffect:True
Oracle:Enchant land\nEnchanted land has "{T}: This land deals 1 damage to any target." Oracle:Enchant land\nEnchanted land has "{T}: This land deals 1 damage to any target."

View File

@@ -3,6 +3,6 @@ ManaCost:2 B
Types:Creature Sliver Types:Creature Sliver
PT:2/2 PT:2/2
S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Mana | Description$ All Slivers have "Sacrifice this permanent: Add {B}{B}." S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Mana | Description$ All Slivers have "Sacrifice this permanent: Add {B}{B}."
SVar:Mana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}. SVar:Mana:AB$ Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}.
AI:RemoveDeck:All AI:RemoveDeck:All
Oracle:All Slivers have "Sacrifice this permanent: Add {B}{B}." Oracle:All Slivers have "Sacrifice this permanent: Add {B}{B}."

View File

@@ -4,12 +4,11 @@ Types:Creature Vampire
PT:2/2 PT:2/2
K:Haste K:Haste
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs.
SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBExile SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBExile
SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast
SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ ExileMe SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ DBCleanup
SVar:ExileMe:DB$ ChangeZoneAll | Origin$ Stack | Destination$ Exile | ChangeType$ Card.Self | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:NumTimes:Number$0 SVar:X:Count$TriggerRememberAmount
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:Haste\nWhen Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. Oracle:Haste\nWhen Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs.

View File

@@ -16,7 +16,7 @@ SVar:RepeatOpp:DB$ RepeatEach | RepeatSubAbility$ ChooseCardsToTap | RepeatPlaye
SVar:ChooseCardsToTap:DB$ ChooseCard | Defined$ Opponent | MinAmount$ 0 | Amount$ NumCreatures | Choices$ Creature.untapped+RememberedPlayerCtrl | ChoiceTitle$ Choose any number of untapped creatures you control | ChoiceZone$ Battlefield | RememberChosen$ True | AILogic$ BowToMyCommand | SubAbility$ TapChosenCards SVar:ChooseCardsToTap:DB$ ChooseCard | Defined$ Opponent | MinAmount$ 0 | Amount$ NumCreatures | Choices$ Creature.untapped+RememberedPlayerCtrl | ChoiceTitle$ Choose any number of untapped creatures you control | ChoiceZone$ Battlefield | RememberChosen$ True | AILogic$ BowToMyCommand | SubAbility$ TapChosenCards
SVar:TapChosenCards:DB$ Tap | Defined$ Remembered | SubAbility$ AbandonSelf | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 SVar:TapChosenCards:DB$ Tap | Defined$ Remembered | SubAbility$ AbandonSelf | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8
SVar:AbandonSelf:DB$ Abandon | SubAbility$ DBCleanup | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 SVar:AbandonSelf:DB$ Abandon | SubAbility$ DBCleanup | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8
T:Mode$ Abandoned | ValidCard$ Self | Execute$ DBCleanup T:Mode$ Abandoned | ValidCard$ Card.Self | Execute$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True
SVar:NumCreatures:Count$Valid Creature.RememberedPlayerCtrl SVar:NumCreatures:Count$Valid Creature.RememberedPlayerCtrl
SVar:TappedCreaturePower:Count$SumPower_Card.IsRemembered SVar:TappedCreaturePower:Count$SumPower_Card.IsRemembered

View File

@@ -7,7 +7,7 @@ SVar:TrigAnimate:DB$ Animate | ValidTgts$ Permanent.nonLand+YouCtrl | TgtPrompt$
SVar:CrashLand:Mode$ DamageDealtOnce | ValidSource$ Card.Self | ValidTarget$ Player,Permanent | Execute$ RollCounters | TriggerZones$ Battlefield | TriggerDescription$ Crash Land — Whenever this Vehicle deals damage, roll a six-sided die. If the result is equal to this Vehicle's mana value, sacrifice this Vehicle, then it deals that much damage to any target. SVar:CrashLand:Mode$ DamageDealtOnce | ValidSource$ Card.Self | ValidTarget$ Player,Permanent | Execute$ RollCounters | TriggerZones$ Battlefield | TriggerDescription$ Crash Land — Whenever this Vehicle deals damage, roll a six-sided die. If the result is equal to this Vehicle's mana value, sacrifice this Vehicle, then it deals that much damage to any target.
SVar:RollCounters:DB$ RollDice | ResultSVar$ Result | SubAbility$ Crash SVar:RollCounters:DB$ RollDice | ResultSVar$ Result | SubAbility$ Crash
SVar:Crash:DB$ Sacrifice | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY | SubAbility$ CrashDamage SVar:Crash:DB$ Sacrifice | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY | SubAbility$ CrashDamage
SVar:CrashDamage:DB$ DealDamage | ValidTgt$ Planeswalker,Player,Permanent | TgtPromp$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY SVar:CrashDamage:DB$ DealDamage | ValidTgts$ Planeswalker,Player,Creature | TgtPrompt$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY
SVar:X:Targeted$CardManaCost SVar:X:Targeted$CardManaCost
SVar:Y:Count$CardManaCost SVar:Y:Count$CardManaCost
DeckHints:Type$Vehicle DeckHints:Type$Vehicle

View File

@@ -5,8 +5,6 @@ K:Enchant creature
A:SP$ Attach | Cost$ 3 W W W | ValidTgts$ Creature | AILogic$ Pump A:SP$ Attach | Cost$ 3 W W W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddToughness$ 3 | Description$ Enchanted creature gets +3/+3. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddToughness$ 3 | Description$ Enchanted creature gets +3/+3.
T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | Execute$ TrigSetLife | CombatDamage$ True | TriggerDescription$ Whenever enchanted creature deals combat damage to a player, double its controller's life total. T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | Execute$ TrigSetLife | CombatDamage$ True | TriggerDescription$ Whenever enchanted creature deals combat damage to a player, double its controller's life total.
SVar:TrigSetLife:DB$ Pump | RememberObjects$ TriggeredSourceController | SubAbility$ DBSet SVar:TrigSetLife:DB$ SetLife | Defined$ TriggeredSourceController | LifeAmount$ X
SVar:DBSet:DB$ SetLife | Defined$ Remembered | LifeAmount$ X | SubAbility$ DBCleanup SVar:X:PlayerCountDefinedTriggeredSourceController$LifeTotal/Twice
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:PlayerCountRemembered$LifeTotal/Twice
Oracle:Enchant creature\nEnchanted creature gets +3/+3.\nWhenever enchanted creature deals combat damage to a player, double its controller's life total. Oracle:Enchant creature\nEnchanted creature gets +3/+3.\nWhenever enchanted creature deals combat damage to a player, double its controller's life total.

View File

@@ -1,10 +1,8 @@
Name:Cephalid Shrine Name:Cephalid Shrine
ManaCost:1 U U ManaCost:1 U U
Types:Enchantment Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounterRem | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell.
SVar:TrigCounterRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBCounter SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator
SVar:DBCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator | SubAbility$ DBCleanup SVar:X:Count$ValidGraveyard Card.sharesNameWith TriggeredCard
SVar:X:Count$ValidGraveyard Card.sharesNameWith Remembered
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:Random AI:RemoveDeck:Random
Oracle:Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. Oracle:Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell.

View File

@@ -5,5 +5,8 @@ PT:7/7
K:Flash K:Flash
K:Flying K:Flying
K:This spell can't be countered. K:This spell can't be countered.
A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | HiddenKeywords$ Unblockable | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SubAbility$ DBUnblockable | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn.
SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
DeckHas:Ability$Discard
Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn.

View File

@@ -6,7 +6,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$
SVar:TrigDraw:AB$ Draw | Cost$ 1 Discard<1/Card> SVar:TrigDraw:AB$ Draw | Cost$ 1 Discard<1/Card>
T:Mode$ DiscardedAll | ValidPlayer$ You | ValidCard$ Card.nonLand | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. T:Mode$ DiscardedAll | ValidPlayer$ You | ValidCard$ Card.nonLand | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn.
SVar:TrigEffect:AB$ Effect | Cost$ ExileFromGrave<1/Card.TriggeredCards> | RememberObjects$ ExiledCards | StaticAbilities$ MayCast | ExileOnMoved$ Stack SVar:TrigEffect:AB$ Effect | Cost$ ExileFromGrave<1/Card.TriggeredCards> | RememberObjects$ ExiledCards | StaticAbilities$ MayCast | ExileOnMoved$ Stack
SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn. SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered+nonLand | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn.
SVar:HasAttackEffect:TRUE SVar:HasAttackEffect:TRUE
DeckHas:Ability$Discard DeckHas:Ability$Discard
Oracle:Whenever Conspiracy Theorist attacks, you may pay {1} and discard a card. If you do, draw a card.\nWhenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. Oracle:Whenever Conspiracy Theorist attacks, you may pay {1} and discard a card. If you do, draw a card.\nWhenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn.

View File

@@ -3,5 +3,8 @@ ManaCost:no cost
Types:Land Types:Land
K:CARDNAME enters the battlefield tapped. K:CARDNAME enters the battlefield tapped.
A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}.
A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land.
SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
DeckHas:Type$Elemental
Oracle:Creeping Tar Pit enters the battlefield tapped.\n{T}: Add {U} or {B}.\n{1}{U}{B}: Creeping Tar Pit becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. Oracle:Creeping Tar Pit enters the battlefield tapped.\n{T}: Add {U} or {B}.\n{1}{U}{B}: Creeping Tar Pit becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land.

View File

@@ -3,13 +3,14 @@ ManaCost:1 B
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant creature card in a graveyard K:Enchant creature card in a graveyard
A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | Tapped$ True | SubAbility$ DBAnimate SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | Tapped$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay
SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ TrigUntap | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may pay {1}{B}. If the player does, untap that creature. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ TrigUntap | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may pay {1}{B}. If the player does, untap that creature.

View File

@@ -3,6 +3,6 @@ ManaCost:1 W U
Types:Creature Vedalken Wizard Types:Creature Vedalken Wizard
PT:1/3 PT:1/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until CARDNAME leaves the battlefield. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until CARDNAME leaves the battlefield.
SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll | Duration$ UntilHostLeavesPlay SVar:TrigExile:DB$ Pump | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll
SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedOrController | Duration$ UntilHostLeavesPlay SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ TargetedCard.Self,Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedController | Duration$ UntilHostLeavesPlay
Oracle:When Deputy of Detention enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until Deputy of Detention leaves the battlefield. Oracle:When Deputy of Detention enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until Deputy of Detention leaves the battlefield.

View File

@@ -2,7 +2,10 @@ Name:Dimir Keyrune
ManaCost:3 ManaCost:3
Types:Artifact Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}.
A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn.
SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
AI:RemoveDeck:Random AI:RemoveDeck:Random
DeckHas:Type$Horror
DeckNeeds:Color$Blue|Black DeckNeeds:Color$Blue|Black
Oracle:{T}: Add {U} or {B}.\n{U}{B}: Dimir Keyrune becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. Oracle:{T}: Add {U} or {B}.\n{U}{B}: Dimir Keyrune becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn.

View File

@@ -4,9 +4,7 @@ Types:Creature Djinn Pirate
PT:4/2 PT:4/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch.
SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You
T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand. T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand.
SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttackingPlayer | SubAbility$ DBDmg SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredAttackingPlayer | NumDmg$ X
SVar:DBDmg:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup SVar:X:Count$ValidHand Card.OwnedBy TriggeredAttackingPlayer
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidHand Card.RememberedPlayerCtrl
Oracle:When Emberwilde Captain enters the battlefield, you become the monarch.\nWhenever an opponent attacks you while you're the monarch, Emberwilde Captain deals damage to that player equal to the number of cards in their hand. Oracle:When Emberwilde Captain enters the battlefield, you become the monarch.\nWhenever an opponent attacks you while you're the monarch, Emberwilde Captain deals damage to that player equal to the number of cards in their hand.

View File

@@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Battlefield | Destination$ Exile | Vali
SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None
SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None
SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None
SVar:NumInHand:RememberedController$CardsInHand SVar:NumInHand:PlayerCountRememberedController$CardsInHand
SVar:NumInLib:RememberedController$CardsInLibrary SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:Exile target nonblack creature. Search its controller's graveyard, hand, and library for all cards with the same name as that creature and exile them. Then that player shuffles. Oracle:Exile target nonblack creature. Search its controller's graveyard, hand, and library for all cards with the same name as that creature and exile them. Then that player shuffles.

View File

@@ -2,11 +2,11 @@ Name:Eye of Singularity
ManaCost:3 W ManaCost:3 W
Types:World Enchantment Types:World Enchantment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated.
SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy | UseImprinted$ True | SubAbility$ DBCleanup SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBRem | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Imprinted+IsNotImprinted | NoRegen$ True SVar:DBRem:DB$ Pump | ImprintCards$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroyRem | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Card.IsImprinted | NoRegen$ True | SubAbility$ DBCleanup
SVar:TrigDestroyRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBDestroyAll SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Permanent.IsNotRemembered+sharesNameWith Remembered | NoRegen$ True | SubAbility$ DBCleanup T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True
AI:RemoveDeck:All AI:RemoveDeck:All
Oracle:When Eye of Singularity enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated.\nWhenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. Oracle:When Eye of Singularity enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated.\nWhenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated.

View File

@@ -4,6 +4,6 @@ Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink. S:Mode$ Continuous | Affected$ Creature.EnchantedBy+EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink.
SVar:EnchantMe:Multiple SVar:EnchantMe:Multiple
Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nAs long as another Aura is attached to enchanted creature, it has first strike and lifelink. Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nAs long as another Aura is attached to enchanted creature, it has first strike and lifelink.

View File

@@ -5,12 +5,12 @@ PT:3/3
T:Mode$ ChangesZone | ValidCard$ Artifact.nonToken+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap. T:Mode$ ChangesZone | ValidCard$ Artifact.nonToken+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.
SVar:TrigToken:DB$ Token | TokenScript$ scrap SVar:TrigToken:DB$ Token | TokenScript$ scrap
A:AB$ Charm | Cost$ 1 R Sac<1/Artifact> | Choices$ DBCounter,DBGoad,DBLoot | CharmNum$ 1 A:AB$ Charm | Cost$ 1 R Sac<1/Artifact> | Choices$ DBCounter,DBGoad,DBLoot | CharmNum$ 1
SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains haste until end of turn. SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains menace until end of turn.
SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Haste SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Menace
SVar:DBGoad:DB$ Goad | ValidTgts$ Creature | SpellDescription$ Goad target creature. SVar:DBGoad:DB$ Goad | ValidTgts$ Creature | SpellDescription$ Goad target creature.
SVar:DBLoot:DB$ Discard | Mode$ TgtChoose | SubAbility$ DBDraw | SpellDescription$ Discard a card, then draw a card. SVar:DBLoot:DB$ Discard | Mode$ TgtChoose | SubAbility$ DBDraw | SpellDescription$ Discard a card, then draw a card.
SVar:DBDraw:DB$ Draw SVar:DBDraw:DB$ Draw
SVar:AIPreference:SacCost$Artifact.token SVar:AIPreference:SacCost$Artifact.token
DeckHints:Type$Artifact DeckHints:Type$Artifact
DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Haste DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Menace
Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains haste until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card. Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains menace until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card.

View File

@@ -2,7 +2,7 @@ Name:Gorilla Tactics
ManaCost:1 G ManaCost:1 G
Types:Instant Types:Instant
A:SP$ Token | Cost$ 1 G | TokenScript$ g_2_2_gorilla | SpellDescription$ Create a 2/2 green Gorilla creature token. A:SP$ Token | Cost$ 1 G | TokenScript$ g_2_2_gorilla | SpellDescription$ Create a 2/2 green Gorilla creature token.
T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, create two 2/2 green Gorilla creature tokens.
SVar:TrigDouble:DB$ Token | TokenScript$ g_2_2_gorilla | TokenAmount$ 2 SVar:TrigDouble:DB$ Token | TokenScript$ g_2_2_gorilla | TokenAmount$ 2
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:Create a 2/2 green Gorilla creature token.\nWhen a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. Oracle:Create a 2/2 green Gorilla creature token.\nWhen a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens.

View File

@@ -1,7 +1,7 @@
Name:Grafdigger's Cage Name:Grafdigger's Cage
ManaCost:1 ManaCost:1
Types:Artifact Types:Artifact
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield. R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards and libraries can't enter the battlefield.
S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
AI:RemoveDeck:Random AI:RemoveDeck:Random

View File

@@ -3,8 +3,9 @@ ManaCost:1 W B
Types:Legendary Creature Rat Pilot Types:Legendary Creature Rat Pilot
PT:4/3 PT:4/3
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step.
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | AnimateSubAbility$ Animate SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | SubAbility$ Animate | RememberChanged$ True | AtEOT$ Hand
SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | AtEOT$ Hand SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard
DeckNeeds:Type$Vehicle DeckNeeds:Type$Vehicle
Oracle:At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. Oracle:At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step.

View File

@@ -3,6 +3,6 @@ ManaCost:1 B B
Types:Sorcery Types:Sorcery
A:SP$ Charm | Choices$ DBDraw,DBDebuff A:SP$ Charm | Choices$ DBDraw,DBDebuff
SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBLoseLife | SpellDescription$ You draw two cards and you lose 2 life. SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBLoseLife | SpellDescription$ You draw two cards and you lose 2 life.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2
SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Creatures your opponents control get -1/-1 until end of turn. SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Creatures your opponents control get -1/-1 until end of turn.
Oracle:Choose one —\n• You draw two cards and you lose 2 life.\n• Creatures your opponents control get -1/-1 until end of turn. Oracle:Choose one —\n• You draw two cards and you lose 2 life.\n• Creatures your opponents control get -1/-1 until end of turn.

View File

@@ -7,7 +7,7 @@ SVar:SacMe:3
T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you draw your second card each turn, put a +1/+1 counter on CARDNAME. T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you draw your second card each turn, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME dies, return another target creature card with mana value less than or equal to CARDNAME's power from your graveyard to the battlefield. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME dies, return another target creature card with mana value less than or equal to CARDNAME's power from your graveyard to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | ValidTgt$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield
SVar:X:TriggeredCard$CardPower SVar:X:TriggeredCard$CardPower
DeckHas:Ability$Counters|Graveyard DeckHas:Ability$Counters|Graveyard
Oracle:Flying\nWhenever you draw your second card each turn, put a +1/+1 counter on Gurgling Anointer.\nWhen Gurgling Anointer dies, return another target creature card with mana value less than or equal to Gurgling Anointer's power from your graveyard to the battlefield. Oracle:Flying\nWhenever you draw your second card each turn, put a +1/+1 counter on Gurgling Anointer.\nWhen Gurgling Anointer dies, return another target creature card with mana value less than or equal to Gurgling Anointer's power from your graveyard to the battlefield.

View File

@@ -6,9 +6,7 @@ K:Mutate:3 RG U U
K:Flying K:Flying
K:Trample K:Trample
T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerDescription$ Whenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerDescription$ Whenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand.
SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChoose SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChange
SVar:DBChoose:DB$ GenericChoice | Choices$ Battlefield,Hand | Defined$ You SVar:DBChange:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | DestinationAlternative$ Hand | AlternativeDestinationMessage$ Put that card onto the battlefield instead of putting it into your hand? | SubAbility$ DBCleanup
SVar:Battlefield:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent onto the battlefield
SVar:Hand:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent into your hand
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:Mutate {3}{R/G}{U}{U} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nFlying, trample\nWhenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. Oracle:Mutate {3}{R/G}{U}{U} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nFlying, trample\nWhenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand.

View File

@@ -4,9 +4,9 @@ Types:Creature Human Scout
PT:3/1 PT:3/1
K:Lifelink K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on CARDNAME. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on CARDNAME.
SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ NumTimes SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ X
SVar:NumTimes:Number$0 SVar:X:Count$TriggerRememberAmount
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ Z | AddToughness$ Z | Description$ Creatures you control get +1/+1 for each valor counter on CARDNAME. S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ Z | AddToughness$ Z | Description$ Creatures you control get +1/+1 for each valor counter on CARDNAME.
SVar:Z:Count$CardCounters.VALOR SVar:Z:Count$CardCounters.VALOR
DeckHas:Ability$LifeGain|Counters DeckHas:Ability$LifeGain|Counters

View File

@@ -5,7 +5,7 @@ PT:3/3
K:Vigilance K:Vigilance
K:Menace K:Menace
K:Lifelink K:Lifelink
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield. R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards can't enter the battlefield.
S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards. S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards. Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards.

View File

@@ -1,6 +1,7 @@
Name:Legions to Ashes Name:Legions to Ashes
ManaCost:1 W B ManaCost:1 W B
Types:Sorcery Types:Sorcery
A:SP$ ChangeZoneAll | TgtPrompt$ Select target nonland permanent an opponent controls | ValidTgts$ Permanent.nonLand+OppCtrl | ChangeType$ TargetedCard.Self,Creature.NotDefinedTargeted+token+sharesNameWith Targeted | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. A:SP$ Pump | ValidTgts$ Permanent.nonland | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ ExileAll | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent.
SVar:ExileAll:DB$ ChangeZoneAll | ChangeType$ TargetedCard.Self,Card.NotDefinedTargeted+token+sharesNameWith Targeted+ControlledBy TargetedController | Origin$ Battlefield | Destination$ Exile
AI:RemoveDeck:Random AI:RemoveDeck:Random
Oracle:Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. Oracle:Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent.

View File

@@ -3,7 +3,7 @@ ManaCost:1 W
Types:Legendary Creature Human Knight Types:Legendary Creature Human Knight
PT:2/2 PT:2/2
A:AB$ ChangeZone | Cost$ 1 W | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Optional$ True | DefinedPlayer$ TargetedController | Chooser$ TargetedController | Origin$ Battlefield | Destination$ Exile | WithCountersType$ AEGIS | StackDescription$ {p:You} chooses {c:Targeted}. {p:TargetedController} may exile it with an aegis counter on it. | SpellDescription$ Choose another target creature. Its controller may exile it with an aegis counter on it. A:AB$ ChangeZone | Cost$ 1 W | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Optional$ True | DefinedPlayer$ TargetedController | Chooser$ TargetedController | Origin$ Battlefield | Destination$ Exile | WithCountersType$ AEGIS | StackDescription$ {p:You} chooses {c:Targeted}. {p:TargetedController} may exile it with an aegis counter on it. | SpellDescription$ Choose another target creature. Its controller may exile it with an aegis counter on it.
A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Creature.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control. A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Card.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control.
K:Partner K:Partner
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:{1}{W}: Choose another target creature. Its controller may exile it with an aegis counter on it.\n{2}{W}, {T}: Return all exiled cards with aegis counters on them to the battlefield under their owners' control.\nPartner (You can have two commanders if both have partner.) Oracle:{1}{W}: Choose another target creature. Its controller may exile it with an aegis counter on it.\n{2}{W}, {T}: Return all exiled cards with aegis counters on them to the battlefield under their owners' control.\nPartner (You can have two commanders if both have partner.)

View File

@@ -7,6 +7,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature
SVar:TrigReanimate:AB$ ChangeZone | Cost$ PayLife<X> | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | ChangeNum$ 1 | AnimateSubAbility$ Animate SVar:TrigReanimate:AB$ ChangeZone | Cost$ PayLife<X> | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | ChangeNum$ 1 | AnimateSubAbility$ Animate
SVar:Animate:DB$ Animate | Defined$ TriggeredCard | Types$ Warlock | Duration$ Permanent SVar:Animate:DB$ Animate | Defined$ TriggeredCard | Types$ Warlock | Duration$ Permanent
SVar:X:TriggeredCard$CardManaCost SVar:X:TriggeredCard$CardManaCost
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Creature.Warlock+YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead. R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Warlock.YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead.
SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard
Oracle:Flying\nWhenever a creature card is put into an opponent's graveyard from anywhere, you may pay life equal to its mana value. If you do, put it onto the battlefield under your control. It's a Warlock in addition to its other types.\nIf a Warlock you control would die, exile it instead. Oracle:Flying\nWhenever a creature card is put into an opponent's graveyard from anywhere, you may pay life equal to its mana value. If you do, put it onto the battlefield under your control. It's a Warlock in addition to its other types.\nIf a Warlock you control would die, exile it instead.

View File

@@ -3,6 +3,6 @@ ManaCost:2 W
Types:Creature Human Soldier Types:Creature Human Soldier
PT:2/2 PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.powerLE2+YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDraw | Optional$ True | TriggerDescription$ Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.powerLE2+YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDraw | Optional$ True | TriggerDescription$ Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card.
SVar:TrigDraw:AB$Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card. SVar:TrigDraw:AB$ Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card.
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
Oracle:Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. Oracle:Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card.

View File

@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Creature Faerie Types:Creature Faerie
PT:2/2 PT:2/2
K:Flying K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn.
SVar:TrigDraw:DB$ Draw | NumCards$ X SVar:TrigDraw:DB$ Draw | NumCards$ X
SVar:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn SVar:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn
AlternateMode:Adventure AlternateMode:Adventure

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Sorcery Types:Sorcery
A:SP$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:OneOppSac,10-19:EachOppSac,20:SacTopPower | SpellDescription$ Roll a d20. A:SP$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:OneOppSac,10-19:EachOppSac,20:SacTopPower | SpellDescription$ Roll a d20.
SVar:OneOppSac:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature. SVar:OneOppSac:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature.
SVar:DBSac:DB$ Sacrifice | Defined$ Chosen | SacValid$ Creature | SubAbility$ DBCleanupChosen SVar:DBSac:DB$ Sacrifice | Defined$ ChosenPlayer | SacValid$ Creature | SubAbility$ DBCleanupChosen
SVar:EachOppSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature | StackDescription$ SpellDescription | SpellDescription$ 10—19 VERT Each opponent sacrifices a creature. SVar:EachOppSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature | StackDescription$ SpellDescription | SpellDescription$ 10—19 VERT Each opponent sacrifices a creature.
SVar:SacTopPower:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ 20 VERT Each opponent sacrifices a creature with the greatest power among creatures that player controls. SVar:SacTopPower:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ 20 VERT Each opponent sacrifices a creature with the greatest power among creatures that player controls.
SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | ChoiceTitle$ Choose a creature you control with the greatest power | Mandatory$ True | RememberChosen$ True SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | ChoiceTitle$ Choose a creature you control with the greatest power | Mandatory$ True | RememberChosen$ True

View File

@@ -1,10 +1,8 @@
Name:Nature's Will Name:Nature's Will
ManaCost:2 G G ManaCost:2 G G
Types:Enchantment Types:Enchantment
T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigRememberTarget | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ DBTapAll | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control.
SVar:TrigRememberTarget:DB$ Pump | RememberObjects$ TriggeredTarget | SubAbility$ DBTapAll SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.ControlledBy TriggeredTarget | SubAbility$ DBUntapAll
SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.RememberedPlayerCtrl | SubAbility$ DBUntapAll SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl
SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
Oracle:Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. Oracle:Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control.

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