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;
}
if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) {
return true;
}
if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
// Planning to choose Haste for Riot, so do this in Main 1
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 (ai.getManaPool().willManaBeLostAtEndOfPhase()) {
// TODO should check if some will be kept and skip those
boolean canUseToPayCost = false;
for (byte color : ManaAtom.MANATYPES) {
// tries to reuse any amount of colorless if cost only has generic
@@ -1089,10 +1094,6 @@ public class ComputerUtil {
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
if (card.isEquipment()) {
boolean playNow = false;

View File

@@ -93,6 +93,7 @@ public class ComputerUtilMana {
ability.setActivatingPlayer(card.getController(), true);
if (ability.isManaAbility()) {
score += ability.calculateScoreForManaAbility();
// TODO check TriggersWhenSpent
}
else if (!ability.isTrigger() && ability.isPossible()) {
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 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.Affected, hostCard);
repParams.put(AbilityKey.Activator, ai);
repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility
// TODO Damping Sphere might replace later?
@@ -1614,9 +1615,9 @@ public class ComputerUtilMana {
// setup produce mana replacement effects
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.Affected, sourceCard);
repParams.put(AbilityKey.Activator, ai);
repParams.put(AbilityKey.AbilityMana, m); // RootAbility
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 com.google.common.collect.*;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import forge.StaticData;
@@ -52,27 +53,24 @@ public abstract class GameState {
ZONES.put(ZoneType.Sideboard, "sideboard");
}
private int humanLife = -1;
private int computerLife = -1;
private String humanCounters = "";
private String computerCounters = "";
private String humanManaPool = "";
private String computerManaPool = "";
private String humanPersistentMana = "";
private String computerPersistentMana = "";
private int humanLandsPlayed = 0;
private int computerLandsPlayed = 0;
private int humanLandsPlayedLastTurn = 0;
private int computerLandsPlayedLastTurn = 0;
static class PlayerState {
private int life = -1;
private String counters = "";
private String manaPool = "";
private String persistentMana = "";
private int landsPlayed = 0;
private int landsPlayedLastTurn = 0;
private String precast = null;
private String putOnStack = null;
private final Map<ZoneType, String> cardTexts = new EnumMap<>(ZoneType.class);
}
private final List<PlayerState> playerStates = new ArrayList<>();
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<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, List<String>> cardToChosenClrs = new HashMap<>();
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
@@ -98,12 +96,6 @@ public abstract class GameState {
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 boolean removeSummoningSickness = false;
@@ -134,32 +126,27 @@ public abstract class GameState {
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"));
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("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();
}
@@ -169,33 +156,21 @@ public abstract class GameState {
}
}
public void initFromGame(Game game) throws Exception {
FCollectionView<Player> players = game.getPlayers();
// Can only serialize a two player game with one AI and one human.
if (players.size() != 2) {
throw new Exception("Game not supported");
public void initFromGame(Game game) {
playerStates.clear();
for (Player player : game.getPlayers()) {
PlayerState p = new PlayerState();
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();
turn = game.getPhaseHandler().getTurn();
aiCardTexts.clear();
humanCardTexts.clear();
// Mark the cards that need their ID remembered for various reasons
cardsReferencedByID.clear();
@@ -238,8 +213,9 @@ public abstract class GameState {
for (ZoneType zone : ZONES.keySet()) {
// Init texts to empty, so that restoring will clear the state
// if the zone had no cards in it (e.g. empty hand).
aiCardTexts.put(zone, "");
humanCardTexts.put(zone, "");
for (PlayerState p : playerStates) {
p.cardTexts.put(zone, "");
}
for (Card card : game.getCardsIncludePhasingIn(zone)) {
if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) {
puzzleCreatorState = true;
@@ -247,18 +223,35 @@ public abstract class GameState {
if (card instanceof DetachedCardEffect) {
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) {
StringBuilder newText = new StringBuilder(cardTexts.get(zoneType));
if (newText.length() > 0) {
newText.append(";");
}
if (c.isToken()) {
newText.append("t:").append(new TokenInfo(c).toString());
newText.append("t:").append(new TokenInfo(c));
} else {
if (c.getPaperCard() == null) {
return;
@@ -281,8 +274,7 @@ public abstract class GameState {
if (zoneType == ZoneType.Battlefield) {
if (c.getOwner() != c.getController()) {
// TODO: Handle more than 2-player games.
newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human"));
newText.append("|Owner:").append(getPlayerString(c.getOwner()));
}
if (c.isTapped()) {
newText.append("|Tapped");
@@ -298,7 +290,7 @@ public abstract class GameState {
}
if (c.isPhasedOut()) {
newText.append("|PhasedOut:");
newText.append(c.getPhasedOut().isAI() ? "AI" : "HUMAN");
newText.append(getPlayerString(c.getPhasedOut()));
}
if (c.isFaceDown()) {
newText.append("|FaceDown");
@@ -319,10 +311,8 @@ public abstract class GameState {
}
if (c.getPlayerAttachedTo() != null) {
// TODO: improve this for game states with more than two players
newText.append("|EnchantingPlayer:");
Player p = c.getPlayerAttachedTo();
newText.append(p.getController().isAI() ? "AI" : "HUMAN");
newText.append(getPlayerString(c.getPlayerAttachedTo()));
} else if (c.isAttachedToEntity()) {
newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId());
}
@@ -348,11 +338,8 @@ public abstract class GameState {
}
List<String> chosenCardIds = Lists.newArrayList();
for (Object obj : c.getChosenCards()) {
if (obj instanceof Card) {
int id = ((Card)obj).getId();
chosenCardIds.add(String.valueOf(id));
}
for (Card obj : c.getChosenCards()) {
chosenCardIds.add(String.valueOf(obj.getId()));
}
if (!chosenCardIds.isEmpty()) {
newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ","));
@@ -465,16 +452,36 @@ public abstract class GameState {
public void parse(InputStream in) throws Exception {
final BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
parseLine(line);
}
parse(br.lines());
}
public void parse(List<String> lines) {
for (String line : lines) {
parseLine(line);
parse(lines.stream());
}
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;
}
boolean isHuman = categoryName.startsWith("human");
if (categoryName.equals("turn")) {
turn = Integer.parseInt(categoryValue);
}
else if (categoryName.equals("removesummoningsickness")) {
} else if (categoryName.equals("removesummoningsickness")) {
removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
}
else if (categoryName.endsWith("life")) {
if (isHuman)
humanLife = Integer.parseInt(categoryValue);
else
computerLife = Integer.parseInt(categoryValue);
}
else if (categoryName.endsWith("counters")) {
if (isHuman)
humanCounters = categoryValue;
else
computerCounters = categoryValue;
}
else if (categoryName.endsWith("landsplayed")) {
if (isHuman)
humanLandsPlayed = Integer.parseInt(categoryValue);
else
computerLandsPlayed = Integer.parseInt(categoryValue);
}
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")) {
} else if (categoryName.endsWith("life")) {
getPlayerState(categoryName).life = Integer.parseInt(categoryValue);
} else if (categoryName.endsWith("counters")) {
getPlayerState(categoryName).counters = categoryValue;
} else if (categoryName.endsWith("landsplayed")) {
getPlayerState(categoryName).landsPlayed = Integer.parseInt(categoryValue);
} else if (categoryName.endsWith("landsplayedlastturn")) {
getPlayerState(categoryName).landsPlayedLastTurn = Integer.parseInt(categoryValue);
} else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Battlefield, categoryValue);
} else if (categoryName.endsWith("hand")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Hand, categoryValue);
} else if (categoryName.endsWith("graveyard")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Graveyard, categoryValue);
} else if (categoryName.endsWith("library")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Library, categoryValue);
} else if (categoryName.endsWith("exile")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Exile, categoryValue);
} else if (categoryName.endsWith("command")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Command, categoryValue);
} else if (categoryName.endsWith("sideboard")) {
getPlayerState(categoryName).cardTexts.put(ZoneType.Sideboard, categoryValue);
} else if (categoryName.startsWith("ability")) {
abilityString.put(categoryName.substring("ability".length()), categoryValue);
}
else if (categoryName.endsWith("precast")) {
if (isHuman)
precastHuman = categoryValue;
else
precastAI = categoryValue;
}
else if (categoryName.endsWith("putonstack")) {
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);
} else if (categoryName.endsWith("precast")) {
getPlayerState(categoryName).precast = categoryValue;
} else if (categoryName.endsWith("putonstack")) {
getPlayerState(categoryName).putOnStack = categoryValue;
} else if (categoryName.endsWith("manapool")) {
getPlayerState(categoryName).manaPool = categoryValue;
} else if (categoryName.endsWith("persistentmana")) {
getPlayerState(categoryName).persistentMana = categoryValue;
} else {
System.err.println("Unknown key: " + categoryName);
}
}
public void applyToGame(final Game game) {
game.getAction().invoke(new Runnable() {
@Override
public void run() {
applyGameOnThread(game);
}
});
game.getAction().invoke(() -> applyGameOnThread(game));
}
protected void applyGameOnThread(final Game game) {
final Player human = game.getPlayers().get(0);
final Player ai = game.getPlayers().get(1);
if (game.getPlayers().size() != playerStates.size()) {
throw new RuntimeException("Non-matching number of players, (" +
game.getPlayers().size() + " vs. " + playerStates.size() + ")");
}
idToCard.clear();
cardToAttachId.clear();
@@ -647,32 +568,21 @@ public abstract class GameState {
cardToScript.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 advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase);
// Set stack to resolving so things won't trigger/effects be checked right away
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.getTriggerHandler().setSuppressAllTriggers(true);
setupPlayerState(humanLife, humanCardTexts, human, humanLandsPlayed, humanLandsPlayedLastTurn);
setupPlayerState(computerLife, aiCardTexts, ai, computerLandsPlayed, computerLandsPlayedLastTurn);
for (int i = 0; i < playerStates.size(); i++) {
setupPlayerState(game.getPlayers().get(i), playerStates.get(i));
}
handleCardAttachments();
handleChosenEntities();
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
// pre-setting negative life (e.g. PS_NEO4).
if (humanLife <= 0) {
human.setLife(humanLife, null);
} else if (computerLife <= 0) {
ai.setLife(computerLife, null);
for (int i = 0; i < playerStates.size(); i++) {
int life = playerStates.get(i).life;
if (life <= 0) {
game.getPlayers().get(i).setLife(life, null);
}
}
}
@@ -746,12 +657,7 @@ public abstract class GameState {
produced.put("PersistentMana", "True");
}
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
game.getAction().invoke(new Runnable() {
@Override
public void run() {
abMana.produceMana(null);
}
});
game.getAction().invoke(() -> abMana.produceMana(null));
}
}
@@ -832,8 +738,7 @@ public abstract class GameState {
}
private int parseTargetInScript(final String tgtDef) {
int tgtID = TARGET_NONE;
int tgtID;
if (tgtDef.equalsIgnoreCase("human")) {
tgtID = TARGET_HUMAN;
} else if (tgtDef.equalsIgnoreCase("ai")) {
@@ -972,37 +877,23 @@ public abstract class GameState {
}
private void handlePrecastSpells(final Game game) {
Player human = game.getPlayers().get(0);
Player ai = game.getPlayers().get(1);
if (precastHuman != null) {
String[] spellList = TextUtil.split(precastHuman, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, human, game);
}
}
if (precastAI != null) {
String[] spellList = TextUtil.split(precastAI, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, ai, game);
for (int i = 0; i < playerStates.size(); i++) {
if (playerStates.get(i).precast != null) {
String[] spellList = TextUtil.split(playerStates.get(i).precast, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, game.getPlayers().get(i), game);
}
}
}
}
private void handleAddSAsToStack(final Game game) {
Player human = game.getPlayers().get(0);
Player ai = game.getPlayers().get(1);
if (putOnStackHuman != null) {
String[] spellList = TextUtil.split(putOnStackHuman, ';');
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);
for (int i = 0; i < playerStates.size(); i++) {
if (playerStates.get(i).putOnStack != null) {
String[] spellList = TextUtil.split(playerStates.get(i).putOnStack, ';');
for (String spell : spellList) {
precastSpellFromCard(spell, game.getPlayers().get(i), game, true);
}
}
}
}
@@ -1138,14 +1029,9 @@ public abstract class GameState {
}
}
// Enchant players by ID
for (Entry<Card, Integer> entry : cardToEnchantPlayerId.entrySet()) {
// TODO: improve this for game states with more than two players
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);
// Enchant players
for (Entry<Card, Player> entry : cardToEnchantPlayerId.entrySet()) {
entry.getKey().attachToEntity(entry.getValue(), null);
}
}
@@ -1184,7 +1070,7 @@ public abstract class GameState {
top.removeCloneState(top.getMutatedTimestamp());
}
final Long ts = game.getNextTimestamp();
final long ts = game.getNextTimestamp();
top.setMutatedTimestamp(ts);
if (top.getCurrentStateName() != CardStateName.FaceDown) {
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
// 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());
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();
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
}
if (life >= 0) p.setLife(life, null);
p.setLandsPlayedThisTurn(landsPlayed);
p.setLandsPlayedLastTurn(landsPlayedLastTurn);
if (state.life >= 0) p.setLife(state.life, null);
p.setLandsPlayedThisTurn(state.landsPlayed);
p.setLandsPlayedLastTurn(state.landsPlayedLastTurn);
p.clearPaidForSA();
@@ -1275,6 +1161,13 @@ public abstract class GameState {
for (Card cmd : p.getCommanders()) {
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);
} else if (info.startsWith("PhasedOut")) {
String tgt = info.substring(info.indexOf(':') + 1);
Player human = player.getGame().getPlayers().get(0);
Player ai = player.getGame().getPlayers().get(1);
c.setPhasedOut(tgt.equalsIgnoreCase("AI") ? ai : human);
c.setPhasedOut(parsePlayerString(player.getGame(), tgt));
} else if (info.startsWith("Counters:")) {
applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("SummonSick")) {
@@ -1377,16 +1268,12 @@ public abstract class GameState {
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
cardToAttachId.put(c, id);
} else if (info.startsWith("EnchantingPlayer:")) {
// TODO: improve this for game states with more than two players
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:")) {
// 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);
Player controller = c.getController();
c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human);
c.setOwner(parsePlayerString(player.getGame(), owner));
c.setController(controller, c.getGame().getNextTimestamp());
} else if (info.startsWith("Ability:")) {
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();

View File

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

View File

@@ -266,6 +266,7 @@ public class ManaEffectAi extends SpellAbilityAi {
ManaPool mp = ai.getManaPool();
Mana test = null;
if (mp.isEmpty()) {
// TODO use color from ability
test = new Mana((byte) ManaAtom.COLORLESS, source, null);
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.Destination, ZoneType.Battlefield);
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()) {
return false;
}

View File

@@ -423,7 +423,7 @@ public class PumpAi extends PumpAiBase {
}
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;
}
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);

View File

@@ -76,21 +76,23 @@ public class GameCopier {
for (RegisteredPlayer p : origPlayers) {
newPlayers.add(clonePlayer(p));
}
GameRules currentRules = origGame.getRules();
Match newMatch = new Match(currentRules, newPlayers, origGame.getView().getTitle());
Game newGame = new Game(newPlayers, currentRules, newMatch);
for (int i = 0; i < origGame.getPlayers().size(); i++) {
Player origPlayer = origGame.getPlayers().get(i);
Player newPlayer = newGame.getPlayers().get(i);
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.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
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.setRevolt(origPlayer.hasRevolt());
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
@@ -350,6 +352,7 @@ public class GameCopier {
newCard.setPTBoost(c.getPTBoostTable());
// TODO copy by map
newCard.setDamage(c.getDamage());
newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn());
newCard.setChangedCardColors(c.getChangedCardColorsTable());
newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable());

View File

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

View File

@@ -1,5 +1,6 @@
package forge.ai.simulation;
import forge.ai.ComputerUtilAbility;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -16,7 +17,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
public class SpellAbilityChoicesIterator {
private SimulationController controller;
private final SimulationController controller;
private Iterator<int[]> modeIterator;
private int[] selectedModes;
@@ -34,11 +35,13 @@ public class SpellAbilityChoicesIterator {
Card selectedChoice;
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 cpIndex = -1;
private int evalDepth;
// Maps from filtered mode indexes to original ones.
private List<Integer> modesMap;
public SpellAbilityChoicesIterator(SimulationController controller) {
this.controller = controller;
@@ -46,17 +49,28 @@ public class SpellAbilityChoicesIterator {
public List<AbilitySub> chooseModesForAbility(List<AbilitySub> choices, int min, int num, boolean allowRepeat) {
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
// when choosing more modes, like Blessed Alliance?
if (!allowRepeat) {
modeIterator = CombinatoricsUtils.combinationsIterator(choices.size(), num);
if (modesMap.isEmpty()) {
return null;
} else if (!allowRepeat) {
modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num);
} else {
// Note: When allowRepeat is true, it does result in many possibilities being tried.
// 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;
}
// Note: If modeIterator already existed, selectedModes would have been updated in advance().
@@ -78,6 +92,13 @@ public class SpellAbilityChoicesIterator {
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) {
cpIndex++;
if (cpIndex >= choicePoints.size()) {
@@ -86,8 +107,7 @@ public class SpellAbilityChoicesIterator {
ChoicePoint cp = choicePoints.get(cpIndex);
// Prune duplicates.
HashSet<String> uniqueCards = new HashSet<>();
for (int i = 0; i < fetchList.size(); i++) {
Card card = fetchList.get(i);
for (Card card : fetchList) {
if (uniqueCards.add(card.getName()) && uniqueCards.size() == cp.nextChoice + 1) {
cp.selectedChoice = card;
}
@@ -137,14 +157,9 @@ public class SpellAbilityChoicesIterator {
evalDepth++;
pushTarget = false;
}
return;
}
}
public int[] getSelectModes() {
return selectedModes;
}
public boolean advance(Score lastScore) {
cpIndex = -1;
for (ChoicePoint cp : choicePoints) {
@@ -195,7 +210,7 @@ public class SpellAbilityChoicesIterator {
doneEvaluating(bestScoreForMode);
bestScoreForMode = new Score(Integer.MIN_VALUE);
if (modeIterator.hasNext()) {
selectedModes = modeIterator.next();
selectedModes = remapModes(modeIterator.next());
advancedToNextMode = true;
return true;
}
@@ -232,8 +247,8 @@ public class SpellAbilityChoicesIterator {
}
private static class AllowRepeatModesIterator implements Iterator<int[]> {
private int numChoices;
private int max;
private final int numChoices;
private final int max;
private int[] indexes;
public AllowRepeatModesIterator(int numChoices, int min, int max) {

View File

@@ -39,6 +39,7 @@ public class SpellAbilityPicker {
private SpellAbilityChoicesIterator interceptor;
private Plan plan;
private int numSimulations;
public SpellAbilityPicker(Game game, Player player) {
this.game = game;
@@ -66,19 +67,7 @@ public class SpellAbilityPicker {
private List<SpellAbility> getCandidateSpellsAndAbilities() {
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
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));
}
}
HashMap<String, Card> landsDeDupe = new HashMap<>();
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
int writeIndex = 0;
for (int i = 0; i < candidateSAs.size(); i++) {
@@ -86,6 +75,16 @@ public class SpellAbilityPicker {
if (sa.isManaAbility()) {
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);
AiPlayDecision opinion = canPlayAndPayForSim(sa);
@@ -95,7 +94,7 @@ public class SpellAbilityPicker {
if (opinion != AiPlayDecision.WillPlay)
continue;
candidateSAs.set(writeIndex, sa);
candidateSAs.set(writeIndex, sa);
writeIndex++;
}
candidateSAs.subList(writeIndex, candidateSAs.size()).clear();
@@ -353,7 +352,9 @@ public class SpellAbilityPicker {
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
return AiPlayDecision.CantAfford;
}
if (!ComputerUtilAbility.isFullyTargetable(sa)) {
return AiPlayDecision.TargetingFailed;
}
if (shouldWaitForLater(sa)) {
return AiPlayDecision.AnotherTime;
}
@@ -375,10 +376,13 @@ public class SpellAbilityPicker {
final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller);
Score lastScore;
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));
GameSimulator simulator = new GameSimulator(controller, game, player, phase);
simulator.setInterceptor(choicesIterator);
lastScore = simulator.simulateSpellAbility(sa);
numSimulations++;
if (lastScore.value > bestScore.value) {
bestScore = lastScore;
}
@@ -462,4 +466,8 @@ public class SpellAbilityPicker {
}
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
// 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.

View File

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

View File

@@ -164,8 +164,8 @@ public class GameAction {
}
if (!found) {
c.clearControllers();
if (c.removeChangedState()) {
c.updateStateForView();
if (cause != null) {
unanimateOnAbortedChange(cause, c);
}
return c;
}
@@ -365,7 +365,18 @@ public class GameAction {
copied.getOwner().removeInboundToken(copied);
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);
}
@@ -373,10 +384,8 @@ public class GameAction {
copied.clearDelved();
copied.clearConvoked();
copied.clearExploited();
}
// was replaced with another Zone Change
if (toBattlefield && !c.isInPlay()) {
} else if (toBattlefield && !c.isInPlay()) {
// was replaced with another Zone Change
if (c.removeChangedState()) {
c.updateStateForView();
}
@@ -570,6 +579,12 @@ public class GameAction {
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
if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) {
game.getTriggerHandler().registerActiveTrigger(copied, false);
@@ -697,7 +712,6 @@ public class GameAction {
copied.setState(CardStateName.Original, true);
}
unattachCardLeavingBattlefield(copied);
} else if (toBattlefield) {
for (Player p : game.getPlayers()) {
copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p);
@@ -973,9 +987,15 @@ public class GameAction {
if (c.isInZone(ZoneType.Stack)) {
c.getGame().getStack().remove(c);
}
final Zone z = c.getZone();
// in some corner cases there's no zone yet (copied spell that failed targeting)
if (c.getZone() != null) {
c.getZone().remove(c);
if (z != null) {
z.remove(c);
if (z.is(ZoneType.Battlefield)) {
c.runLeavesPlayCommands();
}
}
// CR 603.6c other players LTB triggers should work
@@ -1036,20 +1056,13 @@ public class GameAction {
}
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);
newBattlefield.add(c);
c.setSickness(true);
if (game.getPhaseHandler().inCombat()) {
game.getCombat().removeFromCombat(c);
}
c.setTurnInZone(tiz);
c.setCameUnderControlSinceLastUpkeep(true);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
@@ -1057,9 +1070,6 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
for (Player p : game.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true);
}
c.runChangeControllerCommands();
}
@@ -2560,4 +2570,17 @@ public class GameAction {
}
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();
PlayerCollection players = new PlayerCollection(game.getPlayers());
// CR 613.7k use APNAP
// CR 613.7m use APNAP
int indexAP = players.indexOf(game.getPhaseHandler().getPlayerTurn());
if (indexAP != -1) {
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);
}
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) {
damageReceivedThisTurn.add(dmg);
}

View File

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

View File

@@ -400,6 +400,23 @@ public abstract class SpellAbilityEffect {
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) {
String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True";
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) {
final Card host = sa.getHostCard();
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);

View File

@@ -136,10 +136,10 @@ public class AnimateAllEffect extends AnimateEffectBase {
CardCollectionView list;
if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
list = game.getCardsIn(ZoneType.Battlefield);
} else {
if (sa.usesTargeting() || sa.hasParam("Defined")) {
list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield);
} else {
list = game.getCardsIn(ZoneType.Battlefield);
}
list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa);
@@ -155,18 +155,18 @@ public class AnimateAllEffect extends AnimateEffectBase {
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) {
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);
}
}

View File

@@ -62,7 +62,7 @@ public class BalanceEffect extends SpellAbilityEffect {
discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance));
} else { // Battlefield
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);
}
}

View File

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

View File

@@ -1,10 +1,7 @@
package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.game.Game;
@@ -19,6 +16,7 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;
@@ -28,10 +26,9 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final List<Card> tgtCards = getTargetCards(sa);
// should update when adding effects for defined blocker
sb.append("Reselect the defender of ");
sb.append(StringUtils.join(tgtCards, ", "));
sb.append(Lang.joinHomogenous(getTargetCards(sa)));
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)) {
cards.removeAll(p.getCardsIn(ZoneType.Library));
}
}
if (origin.contains(ZoneType.Library) && sa.hasParam("Search")) {
// Search library using changezoneall effect need a param "Search"
@@ -95,8 +94,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
if (!libCards.isEmpty()) {
sa.getActivatingPlayer().getController().reveal(libCards, ZoneType.Library, libCards.get(0).getOwner());
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, sa.getActivatingPlayer());
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(sa.getActivatingPlayer());
runParams.put(AbilityKey.Target, tgtPlayers);
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
}
@@ -172,9 +170,11 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
// need LKI before Animate does apply
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
source.addRemembered(c);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
AbilityUtils.resolve(animate);
source.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
}
if (sa.hasParam("Tapped")) {
c.setTapped(true);

View File

@@ -133,10 +133,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("ExileFaceDown")) {
sb.append(" face down");
}
sb.append(".");
} 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")) {
final boolean originAlt = sa.hasParam("OriginAlternative");
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);
} else {
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()) {
gameCard.setTapped(true);
}
@@ -583,6 +594,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
if (sa.hasParam("Transformed")) {
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);
} else {
// 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")) {
// need LKI before Animate does apply
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard));
}
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
hostCard.addRemembered(gameCard);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
AbilityUtils.resolve(animate);
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
@@ -776,7 +782,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
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);
}
if (sa.hasParam("NoShuffle")) {
if (sa.hasParam("NoShuffle") || "False".equals(sa.getParam("Shuffle"))) {
shuffleMandatory = false;
}
@@ -1249,7 +1255,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
CardLists.shuffle(chosenCards);
}
// 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);
}
@@ -1310,9 +1316,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need LKI before Animate does apply
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
source.addRemembered(c);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
AbilityUtils.resolve(animate);
source.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
}
if (sa.hasParam("GainControl")) {
final String g = sa.getParam("GainControl");
@@ -1331,6 +1339,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
if (sa.hasParam("Transformed")) {
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);
} else {
// If it can't Transform, don't change zones.
@@ -1390,7 +1402,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
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);
if (list.isEmpty()) {
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;
if (sa.hasParam("ChoiceRestriction")) {
String rest = sa.getParam("ChoiceRestriction");
if (rest.equals("ThisGame")) {
restriction = source.getChosenModesGame(sa);
} else if (rest.equals("ThisTurn")) {
restriction = source.getChosenModesTurn(sa);
}
restriction = source.getChosenModes(sa, sa.getParam("ChoiceRestriction"));
}
List<AbilitySub> choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
List<AbilitySub> toRemove = Lists.newArrayList();
for (AbilitySub ch : choices) {
// 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()))) {
toRemove.add(ch);
}
@@ -97,6 +93,8 @@ public class CharmEffect extends SpellAbilityEffect {
sb.append(" that hasn't been chosen");
} else if (rest.equals("ThisTurn")) {
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
protected String getStackDescription(SpellAbility 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")

View File

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

View File

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

View File

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

View File

@@ -417,13 +417,13 @@ public class DigEffect extends SpellAbilityEffect {
}
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
// 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);
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
AbilityUtils.resolve(animate);
host.removeRemembered(c);
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
}
c = game.getAction().moveTo(zone, c, sa, moveParams);
if (destZone1.equals(ZoneType.Battlefield)) {

View File

@@ -77,10 +77,20 @@ public class DigMultipleEffect extends SpellAbilityEffect {
if (validMap.isEmpty()) {
chooser.getController().notifyOfValue(sa, null, Localizer.getInstance().getMessage("lblNoValidCards"));
} 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()) {
game.getAction().reveal(chosen, chooser, true, Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName()));
if (!chosen.isEmpty()) {
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")) {
@@ -108,21 +118,23 @@ public class DigMultipleEffect extends SpellAbilityEffect {
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone zone = c.getOwner().getZone(destZone1);
if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) {
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);
if (!sa.hasParam("ChangeLater")) {
if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) {
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, 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")) {
@@ -142,38 +154,45 @@ public class DigMultipleEffect extends SpellAbilityEffect {
}
// now, move the rest to destZone2
if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck
|| destZone2 == ZoneType.Graveyard) {
CardCollection afterOrder = rest;
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 (!sa.hasParam("ChangeLater")) {
if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck
|| destZone2 == ZoneType.SchemeDeck || destZone2 == ZoneType.Graveyard) {
CardCollection afterOrder = rest;
if (sa.hasParam("RestRandomOrder")) {
CardLists.shuffle(afterOrder);
}
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
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())) {
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
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);
}
}
if (sa.hasParam("ImprintRest")) {
for (Card c : rest) {
host.addImprintedCard(c);
}
}
}

View File

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

View File

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

View File

@@ -226,7 +226,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private boolean tributed = false;
private boolean embalmed = false;
private boolean eternalized = false;
private boolean madnessWithoutCast = false;
private boolean discarded = false;
private boolean flipped = 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 int planeswalkerAbilityActivated;
private boolean planeswalkerActivationLimitUsed;
private final ActivationTable numberTurnActivations = 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>> 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>> 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;
@@ -3263,7 +3268,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final boolean isFlipped() {
return flipped;
}
public final void setFlipped(boolean value) {
flipped = value;
}
@@ -3271,7 +3275,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void setCanCounter(final boolean b) {
canCounter = b;
}
public final boolean getCanCounter() {
return canCounter;
}
@@ -5458,13 +5461,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
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() {
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();
}
public boolean getMadnessWithoutCast() { return madnessWithoutCast; }
public void setMadnessWithoutCast(boolean state) { madnessWithoutCast = state; }
public boolean wasDiscarded() { return discarded; }
public void setDiscarded(boolean state) { discarded = state; }
public final boolean isMonstrous() {
return monstrous;
@@ -6345,6 +6341,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
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) {
if (!this.hasKeyword("Damage isn't removed from CARDNAME during cleanup steps.")) {
setDamage(0);
@@ -7037,7 +7045,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
numberAbilityResolved.clear();
}
public List<String> getChosenModesTurn(SpellAbility ability) {
public List<String> getChosenModes(SpellAbility ability, String type) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
@@ -7051,32 +7059,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
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;
if (type.equals("ThisTurn")) {
if (ability.getGrantorStatic() != null) {
return chosenModesTurnStatic.get(original, ability.getGrantorStatic());
}
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);
}
if (ability.getGrantorStatic() != null) {
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
}
return chosenModesGame.get(original);
return null;
}
public void addChosenModes(SpellAbility ability, String mode) {
public void addChosenModes(SpellAbility ability, String mode, boolean yourCombat) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
@@ -7103,6 +7105,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
chosenModesGameStatic.put(original, ability.getGrantorStatic(), result);
}
result.add(mode);
if (yourCombat) {
result = chosenModesYourCombatStatic.get(original, ability.getGrantorStatic());
if (result == null) {
result = Lists.newArrayList();
chosenModesYourCombatStatic.put(original, ability.getGrantorStatic(), result);
}
}
} else {
List<String> result = chosenModesTurn.get(original);
if (result == null) {
@@ -7117,6 +7126,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
chosenModesGame.put(original, result);
}
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() {
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() {
planeswalkerAbilityActivated = 0;
planeswalkerActivationLimitUsed = false;
numberTurnActivations.clear();
}

View File

@@ -3855,7 +3855,7 @@ public class CardFactoryUtil {
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);
StringBuilder sbPlay = new StringBuilder();

View File

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

View File

@@ -357,6 +357,9 @@ public class PhaseHandler implements java.io.Serializable {
case COMBAT_END:
// End Combat always happens
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
c.onEndOfCombat(playerTurn);
}
game.getEndOfCombat().executeAt();
//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);
// Play the Discard sound
}
newCard.setDiscarded(true);
if (table != null) {
table.put(origin, newCard.getZone().getZoneType(), newCard);
}

View File

@@ -6,6 +6,7 @@ package forge.game.replacement;
*
*/
public enum ReplacementLayer {
CantHappen, // 614.17
Control, // 616.1b
Copy, // 616.1c
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
final Card source = am.getHostCard();
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.Affected, source);
repParams.put(AbilityKey.Activator, activator);
repParams.put(AbilityKey.AbilityMana, am.getRootAbility());
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;
}
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
if (usesTargeting()) {
final TargetRestrictions tr = getTargetRestrictions();
if (tr.isUniqueTargets() && getUniqueTargets().contains(entity))
return false;

View File

@@ -475,12 +475,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
}
if (sa.isPwAbility()) {
final int initialLimit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 1 : 0;
final int limit = StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) + initialLimit;
int numActivates = c.getPlaneswalkerAbilityActivated();
if (numActivates > limit) {
return false;
int limit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 2 : 1;
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);
}
}
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")) {
String mhs = params.get("AdjustLandPlays");
@@ -573,6 +578,7 @@ public final class StaticAbilityContinuous {
p.addMaxLandPlays(se.getTimestamp(), add);
}
}
if (params.containsKey("ControlOpponentsSearchingLibrary")) {
Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), stAb), null);
p.addControlledWhileSearching(se.getTimestamp(), cntl);
@@ -592,12 +598,6 @@ public final class StaticAbilityContinuous {
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")) {
AbilityUtils.applyManaColorConversion(p.getManaPool(), params);
}

View File

@@ -100,8 +100,8 @@ public class StaticAbilityPanharmonicon {
}
} else if (trigMode.equals(TriggerType.ChangesZoneAll)) {
// Check if the cards have a trigger at all
final String origin = stAb.getParamOrDefault("Origin", null);
final String destination = stAb.getParamOrDefault("Destination", null);
final String origin = stAb.getParam("Origin");
final String destination = stAb.getParam("Destination");
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()) {

View File

@@ -1,12 +1,10 @@
package forge.game.trigger;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardUtil;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
@@ -79,11 +77,11 @@ public class TriggerDamageDoneOnce extends Trigger {
return result;
}
public Set<Card> getDamageSources(Map<Card, Integer> damageMap) {
public CardCollection getDamageSources(Map<Card, Integer> damageMap) {
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()) {
if (matchesValid(c, getParam("ValidSource").split(","))) {
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")) {
// 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
@@ -857,7 +857,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
final Trigger trig = sa.getTrigger();
final Card newHost = game.getCardState(host);
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);
}
}

View File

@@ -20,6 +20,7 @@ package forge.game.zone;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -63,7 +64,7 @@ public class PlayerZone extends Zone {
boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) ||
c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE) ||
c.hasKeyword(Keyword.DISTURB);
boolean exileCastable = (c.isAdventureCard() || c.isForetold()) && c.isInZone(ZoneType.Exile);
boolean exileCastable = c.isForetold() || isOnAdventure(c);
for (final SpellAbility sa : c.getSpellAbilities()) {
final ZoneType restrictZone = sa.getRestrictions().getZone();
@@ -92,6 +93,15 @@ public class PlayerZone extends Zone {
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;

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) {
trigger = b;
}

View File

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

View File

@@ -840,9 +840,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
try {
if (StringUtils.isBlank(savedState)) {
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) {
System.err.println(ex + " [savedState=" + savedState + "]");
return new ArrayList<>();

View File

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

View File

@@ -26,7 +26,9 @@ public class CardSetFilter extends CardFormatFilter {
public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, Collection<String> limitedSets0, boolean allowReprints0){
this(itemManager0, sets0, allowReprints0);
this.limitedSets.addAll(limitedSets0);
if (limitedSets0 != null) {
this.limitedSets.addAll(limitedSets0);
}
}
@Override

View File

@@ -1,7 +1,5 @@
package forge.screens.home;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Vector;
@@ -10,8 +8,6 @@ import javax.swing.SwingUtilities;
import com.google.common.collect.Iterables;
import forge.deck.DeckProxy;
import forge.deck.DeckType;
import forge.deckchooser.FDeckChooser;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
@@ -22,12 +18,10 @@ public class CLobby {
private final VLobby view;
public CLobby(final VLobby view) {
this.view = view;
this.view.setForCommander(true);
}
private void addDecks(final Iterable<DeckProxy> commanderDecks, FList<Object> deckList, String... initialItems) {
Vector<Object> listData = new Vector<>();
listData.addAll(Arrays.asList(initialItems));
Vector<Object> listData = new Vector<>(Arrays.asList(initialItems));
listData.add("Generate");
if (!Iterables.isEmpty(commanderDecks)) {
listData.add("Random");
@@ -46,89 +40,38 @@ public class CLobby {
}
public void update() {
SwingUtilities.invokeLater(new Runnable() {
@Override public final void run() {
final Iterable<DeckProxy> schemeDecks = DeckProxy.getAllSchemeDecks();
final Iterable<DeckProxy> planarDecks = DeckProxy.getAllPlanarDecks();
SwingUtilities.invokeLater(() -> {
final Iterable<DeckProxy> schemeDecks = DeckProxy.getAllSchemeDecks();
final Iterable<DeckProxy> planarDecks = DeckProxy.getAllPlanarDecks();
for (int i = 0; i < VLobby.MAX_PLAYERS; i++) {
addDecks(schemeDecks, view.getSchemeDeckLists().get(i),
"Use deck's scheme section (random if unavailable)");
addDecks(planarDecks, view.getPlanarDeckLists().get(i),
"Use deck's planes section (random if unavailable)");
view.updateVanguardList(i);
}
// General updates when switching back to this view
view.getBtnStart().requestFocusInWindow();
for (int i = 0; i < VLobby.MAX_PLAYERS; i++) {
addDecks(schemeDecks, view.getSchemeDeckLists().get(i),
"Use deck's scheme section (random if unavailable)");
addDecks(planarDecks, view.getPlanarDeckLists().get(i),
"Use deck's planes section (random if unavailable)");
view.updateVanguardList(i);
}
// General updates when switching back to this view
view.getBtnStart().requestFocusInWindow();
});
}
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();
// Checkbox event handling
view.getCbSingletons().addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent arg0) {
prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected()));
prefs.save();
}
view.getCbSingletons().addActionListener(arg0 -> {
prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected()));
prefs.save();
});
view.getCbArtifacts().addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent arg0) {
prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected()));
prefs.save();
}
view.getCbArtifacts().addActionListener(arg0 -> {
prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected()));
prefs.save();
});
// Pre-select checkboxes
view.getCbSingletons().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_SINGLETONS));
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;
import forge.deckchooser.FDeckChooser;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -103,6 +104,8 @@ public class PlayerPanel extends FPanel {
private boolean allowNetworking;
private FDeckChooser deckChooser;
private final VLobby lobby;
public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) {
super();
@@ -463,10 +466,6 @@ public class PlayerPanel extends FPanel {
radioAiUseSimulation.setSelected(useSimulation);
}
public boolean isLocal() {
return type == LobbySlotType.LOCAL;
}
public boolean isArchenemy() {
return aeTeamComboBox.getSelectedIndex() == 0;
}
@@ -540,9 +539,6 @@ public class PlayerPanel extends FPanel {
}
};
/**
* @param index
*/
private void addHandlersToVariantsControls() {
// Archenemy buttons
scmDeckSelectorBtn.setCommand(new Runnable() {
@@ -621,9 +617,6 @@ public class PlayerPanel extends FPanel {
});
}
/**
* @param index
*/
private void createPlayerTypeOptions() {
radioHuman = new FRadioButton(localizer.getMessage("lblHuman"));
radioAi = new FRadioButton(localizer.getMessage("lblAI"));
@@ -674,9 +667,6 @@ public class PlayerPanel extends FPanel {
});
}
/**
* @param index
*/
private void addHandlersDeckSelector() {
deckBtn.setCommand(new Runnable() {
@Override
@@ -688,10 +678,6 @@ public class PlayerPanel extends FPanel {
});
}
/**
* @param index
* @return
*/
private FLabel createNameRandomizer() {
final FLabel newNameBtn = new FLabel.Builder().tooltip(localizer.getMessage("lblGetaNewRandomName")).iconInBackground(false)
.icon(FSkin.getIcon(FSkinProp.ICO_EDIT)).hoverable(true).opaque(false)
@@ -717,10 +703,6 @@ public class PlayerPanel extends FPanel {
return newNameBtn;
}
/**
* @param index
* @return
*/
private void createNameEditor() {
String name;
if (index == 0) {
@@ -898,4 +880,12 @@ public class PlayerPanel extends FPanel {
public void setMayRemove(final boolean 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.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.swing.*;
@@ -36,7 +36,6 @@ import forge.gamemodes.match.LobbySlotType;
import forge.gamemodes.net.event.UpdateLobbyPlayerEvent;
import forge.gui.CardDetailPanel;
import forge.gui.GuiBase;
import forge.gui.UiCommand;
import forge.gui.interfaces.ILobbyView;
import forge.gui.util.SOptionPane;
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 FScrollPanel playersScroll = new FScrollPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"), true);
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();
// Deck frame elements
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 cbArtifacts = new FCheckBox(localizer.getMessage("cbRemoveArtifacts"));
private final Deck[] decks = new Deck[MAX_PLAYERS];
// 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<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<CardDetailPanel> vgdAvatarDetails = 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> nonRandomAiAvatars = new ArrayList<>();
private final Vector<Object> humanListData = 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
public VLobby(final GameLobby lobby) {
this.lobby = lobby;
@@ -178,12 +162,7 @@ public class VLobby implements ILobbyView {
if (lobby.hasControl()) {
addPlayerBtn.setFocusable(true);
addPlayerBtn.setCommand(new Runnable() {
@Override
public final void run() {
lobby.addSlot();
}
});
addPlayerBtn.setCommand(lobby::addSlot);
playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx");
}
@@ -207,7 +186,7 @@ public class VLobby implements ILobbyView {
// Start button event handling
btnStart.addActionListener(new ActionListener() {
@Override
public final void actionPerformed(final ActionEvent arg0) {
public void actionPerformed(final ActionEvent arg0) {
Runnable startGame = lobby.startGame();
if (startGame != null) {
if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) {
@@ -226,17 +205,8 @@ public class VLobby implements ILobbyView {
}
public void updateDeckPanel() {
for (int iPlayer = 0; iPlayer < activePlayersNum; iPlayer++) {
final FDeckChooser fdc = getDeckChooser(iPlayer);
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();
for (final PlayerPanel playerPanel : playerPanels) {
playerPanel.getDeckChooser().restoreSavedState();
}
}
@@ -244,8 +214,12 @@ public class VLobby implements ILobbyView {
getPlayerPanelWithFocus().focusOnAvatar();
}
private PlayerPanel getPlayerPanel(int slot) {
return playerPanels.get(slot);
}
@Override
public void update(final int slot, final LobbySlotType type){
public void update(final int slot, final LobbySlotType type) {
final FDeckChooser deckChooser = getDeckChooser(slot);
deckChooser.setIsAi(type==LobbySlotType.AI);
DeckType selectedDeckType = deckChooser.getSelectedDeckType();
@@ -259,28 +233,13 @@ public class VLobby implements ILobbyView {
case COLOR_DECK:
case STANDARD_COLOR_DECK:
case MODERN_COLOR_DECK:
case RANDOM_CARDGEN_COMMANDER_DECK:
case RANDOM_COMMANDER_DECK:
deckChooser.refreshDeckListForAI();
break;
default:
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
@@ -308,11 +267,6 @@ public class VLobby implements ILobbyView {
if (i < activePlayersNum) {
// visible panels
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 boolean isNewPanel;
if (hasPanel) {
@@ -326,15 +280,6 @@ public class VLobby implements ILobbyView {
constraints += ", gaptop 5px";
}
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;
}
@@ -353,14 +298,24 @@ public class VLobby implements ILobbyView {
panel.update();
final boolean isSlotAI = slot.getType() == LobbySlotType.AI;
deckChooser.setIsAi(isSlotAI);
commanderDeckChooser.setIsAi(isSlotAI);
oathbreakerDeckChooser.setIsAi(isSlotAI);
tinyLeaderDeckChooser.setIsAi(isSlotAI);
brawlDeckChooser.setIsAi(isSlotAI);
if (isNewPanel || fullUpdate) {
final FDeckChooser deckChooser = createDeckChooser(lobby.getGameType(), i, isSlotAI);
deckChooser.populate();
panel.setDeckChooser(deckChooser);
if (i == 0) {
// 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)) {
selectDeck(i);
// Deck section selection
panel.getDeckChooser().getLstDecks().getSelectCommand().run();
selectSchemeDeck(i);
selectPlanarDeck(i);
selectVanguardAvatar(i);
}
if (isNewPanel) {
panel.setVisible(true);
@@ -373,7 +328,7 @@ public class VLobby implements ILobbyView {
if (playerWithFocus >= activePlayersNum) {
changePlayerFocus(activePlayersNum - 1);
} else {
populateDeckPanel(getCurrentGameMode());
populateDeckPanel(lobby.getGameType());
}
refreshPanels(true, true);
}
@@ -395,8 +350,7 @@ public class VLobby implements ILobbyView {
void setDevMode(final int index) {
// clear ready for everyone
for (int i = 0; i < activePlayersNum; i++) {
final PlayerPanel panel = playerPanels.get(i);
panel.setIsReady(false);
getPlayerPanel(i).setIsReady(false);
firePlayerChangeListener(i);
}
changePlayerFocus(index);
@@ -430,7 +384,7 @@ public class VLobby implements ILobbyView {
}
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());
}
@@ -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. */
@SuppressWarnings("serial")
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
buildDeckPanel(localizer.getMessage("lblSchemeDeck"), playerIndex, schemeDeckLists, schemeDeckPanels, new ListSelectionListener() {
@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
buildDeckPanel(localizer.getMessage("lblPlanarDeck"), playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() {
@Override public final void valueChanged(final ListSelectionEvent e) {
@@ -532,26 +440,11 @@ public class VLobby implements ILobbyView {
deckPanels.add(deckPanel);
}
private void selectDeck(final int playerIndex) {
// Full deck selection
selectMainDeck(playerIndex);
selectCommanderDeck(playerIndex);
selectOathbreakerDeck(playerIndex);
selectTinyLeadersDeck(playerIndex);
selectBrawlDeck(playerIndex);
// Deck section selection
selectSchemeDeck(playerIndex);
selectPlanarDeck(playerIndex);
selectVanguardAvatar(playerIndex);
private FDeckChooser getDeckChooser(final int iSlot) {
return getPlayerPanel(iSlot).getDeckChooser();
}
private void selectMainDeck(final int playerIndex) {
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);
private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) {
final DeckType type = mainChooser.getSelectedDeckType();
final Deck deck = mainChooser.getDeck();
// 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();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME);
playerPanels.get(playerIndex).setDeckSelectorButtonText(text);
fireDeckChangeListener(playerIndex, deck);
}
mainChooser.saveState();
}
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);
if (isCommanderDeck) {
getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text);
} else {
getPlayerPanel(playerIndex).setDeckSelectorButtonText(text);
}
fireDeckChangeListener(playerIndex, deck);
}
mainChooser.saveState();
@@ -653,7 +482,7 @@ public class VLobby implements ILobbyView {
}
}
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);
}
} else if (selected instanceof Deck) {
@@ -684,7 +513,7 @@ public class VLobby implements ILobbyView {
}
}
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);
}
} else if (selected instanceof Deck) {
@@ -703,7 +532,7 @@ public class VLobby implements ILobbyView {
}
final Object selected = vgdAvatarLists.get(playerIndex).getSelectedValue();
final PlayerPanel pp = playerPanels.get(playerIndex);
final PlayerPanel pp = getPlayerPanel(playerIndex);
final CardDetailPanel cdp = vgdAvatarDetails.get(playerIndex);
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)) {
vanguardAvatar = deck.get(DeckSection.Avatar).get(0);
} else { //Only other string is "Random"
if (playerPanels.get(playerIndex).isAi()) { //AI
if (isPlayerAI(playerIndex)) { //AI
vanguardAvatar = Aggregates.random(getNonRandomAiAvatars());
} else { //Human
vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars());
@@ -750,8 +579,8 @@ public class VLobby implements ILobbyView {
switch (forGameType) {
case Constructed:
decksFrame.add(deckChoosers.get(playerWithFocus), "grow, push");
if (deckChoosers.get(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) {
decksFrame.add(getDeckChooser(playerWithFocus), "grow, push");
if (getDeckChooser(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) {
final String strCheckboxConstraints = "h 30px!, gap 0 20px 0 0";
decksFrame.add(cbSingletons, strCheckboxConstraints);
decksFrame.add(cbArtifacts, strCheckboxConstraints);
@@ -766,16 +595,10 @@ public class VLobby implements ILobbyView {
}
break;
case Commander:
decksFrame.add(commanderDeckChoosers.get(playerWithFocus), "grow, push");
break;
case Oathbreaker:
decksFrame.add(oathbreakerDeckChoosers.get(playerWithFocus), "grow, push");
break;
case TinyLeaders:
decksFrame.add(tinyLeadersDeckChoosers.get(playerWithFocus), "grow, push");
break;
case Brawl:
decksFrame.add(brawlDeckChoosers.get(playerWithFocus), "grow, push");
decksFrame.add(getDeckChooser(playerWithFocus), "grow, push");
break;
case Planechase:
decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push");
@@ -798,7 +621,13 @@ public class VLobby implements ILobbyView {
public LblHeader getLblTitle() { return lblTitle; }
public JPanel getConstructedFrame() { return constructedFrame; }
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. */
FCheckBox getCbSingletons() { return cbSingletons; }
@@ -816,29 +645,6 @@ public class VLobby implements ILobbyView {
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) {
lobby.setGameType(mode);
update(true);
@@ -851,10 +657,6 @@ public class VLobby implements ILobbyView {
return true;
}
public int getNumPlayers() {
return activePlayersNum;
}
/** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */
private void refreshPanels(final boolean refreshPlayerFrame, final boolean refreshDeckFrame) {
if (refreshPlayerFrame) {
@@ -888,8 +690,8 @@ public class VLobby implements ILobbyView {
/** Saves avatar prefs for players one and two. */
void updateAvatarPrefs() {
final int pOneIndex = playerPanels.get(0).getAvatarIndex();
final int pTwoIndex = playerPanels.get(1).getAvatarIndex();
final int pOneIndex = getPlayerPanel(0).getAvatarIndex();
final int pTwoIndex = getPlayerPanel(1).getAvatarIndex();
prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex);
prefs.save();
@@ -897,8 +699,8 @@ public class VLobby implements ILobbyView {
/** Saves sleeve prefs for players one and two. */
void updateSleevePrefs() {
final int pOneIndex = playerPanels.get(0).getSleeveIndex();
final int pTwoIndex = playerPanels.get(1).getSleeveIndex();
final int pOneIndex = getPlayerPanel(0).getSleeveIndex();
final int pTwoIndex = getPlayerPanel(1).getSleeveIndex();
prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex);
prefs.save();
@@ -925,7 +727,6 @@ public class VLobby implements ILobbyView {
return usedSleeves;
}
private static final ImmutableList<String> genderOptions = ImmutableList.of("Male", "Female", "Any"),
typeOptions = ImmutableList.of("Fantasy", "Generic", "Any");
final String getNewName() {
@@ -973,18 +774,57 @@ public class VLobby implements ILobbyView {
this.variant = variantType;
setToolTipText(variantType.getDescription());
addItemListener(new ItemListener() {
@Override public final void itemStateChanged(final ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
lobby.applyVariant(variantType);
} else {
lobby.removeVariant(variantType);
}
addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
lobby.applyVariant(variantType);
} else {
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() {
@Override
public void actionPerformed(final ActionEvent e) {
@@ -1007,7 +847,7 @@ public class VLobby implements ILobbyView {
}
public boolean isPlayerArchenemy(final int playernum) {
return playerPanels.get(playernum).isArchenemy();
return getPlayerPanel(playernum).isArchenemy();
}
/** Gets the list of Vanguard avatar lists. */
@@ -1027,11 +867,6 @@ public class VLobby implements ILobbyView {
return vgdAllAvatars;
}
/** Return the Vanguard avatars not flagged RemoveDeck:All. */
public List<PaperCard> getAllAiAvatars() {
return vgdAllAiAvatars;
}
/** Return the Vanguard avatars not flagged RemoveDeck:Random. */
public List<PaperCard> getNonRandomHumanAvatars() {
return nonRandomHumanAvatars;
@@ -1055,7 +890,6 @@ public class VLobby implements ILobbyView {
}
if (!cp.getRules().getAiHints().getRemAIDecks()) {
aiListData.add(cp);
vgdAllAiAvatars.add(cp);
if (!cp.getRules().getAiHints().getRemRandomDecks()) {
nonRandomAiAvatars.add(cp);
}

View File

@@ -28,31 +28,32 @@ public final class CDev implements ICDoc {
this.view = new VDev(this);
addListener(view);
view.getLblUnlimitedLands().addMouseListener(madUnlimited);
view.getLblViewAll().addMouseListener(madViewAll);
view.getLblGenerateMana().addMouseListener(madMana);
view.getLblSetupGame().addMouseListener(madSetup);
view.getLblDumpGame().addMouseListener(madDump);
view.getLblTutor().addMouseListener(madTutor);
view.getLblCardToHand().addMouseListener(madCardToHand);
view.getLblExileFromHand().addMouseListener(madExileFromHand);
view.getLblCardToBattlefield().addMouseListener(madCardToBattlefield);
view.getLblCardToLibrary().addMouseListener(madCardToLibrary);
view.getLblCardToGraveyard().addMouseListener(madCardToGraveyard);
view.getLblCardToExile().addMouseListener(madCardToExile);
view.getLblCastSpell().addMouseListener(madCastASpell);
view.getLblRepeatAddCard().addMouseListener(madRepeatAddCard);
view.getLblAddCounterPermanent().addMouseListener(madAddCounter);
view.getLblSubCounterPermanent().addMouseListener(madSubCounter);
view.getLblTapPermanent().addMouseListener(madTap);
view.getLblUntapPermanent().addMouseListener(madUntap);
view.getLblSetLife().addMouseListener(madLife);
view.getLblWinGame().addMouseListener(madWinGame);
view.getLblExileFromPlay().addMouseListener(madExileFromPlay);
view.getLblRemoveFromGame().addMouseListener(madRemoveFromGame);
view.getLblRiggedRoll().addMouseListener(madRiggedRoll);
view.getLblWalkTo().addMouseListener(madWalkToPlane);
view.getLblAskAI().addMouseListener(madAskAI);
view.getLblUnlimitedLands().addMouseListener(onClick(this::togglePlayManyLandsPerTurn));
view.getLblViewAll().addMouseListener(onClick(this::toggleViewAllCards));
view.getLblGenerateMana().addMouseListener(onClick(this::generateMana));
view.getLblSetupGame().addMouseListener(onClick(this::setupGameState));
view.getLblDumpGame().addMouseListener(onClick(this::dumpGameState));
view.getLblTutor().addMouseListener(onClick(this::tutorForCard));
view.getLblCardToHand().addMouseListener(onClick(this::addCardToHand));
view.getLblExileFromHand().addMouseListener(onClick(this::exileCardsFromHand));
view.getLblCardToBattlefield().addMouseListener(onClick(this::addCardToBattlefield));
view.getLblCardToLibrary().addMouseListener(onClick(this::addCardToLibrary));
view.getLblCardToGraveyard().addMouseListener(onClick(this::addCardToGraveyard));
view.getLblCardToExile().addMouseListener(onClick(this::addCardToExile));
view.getLblCastSpell().addMouseListener(onClick(this::castASpell));
view.getLblRepeatAddCard().addMouseListener(onClick(this::repeatAddCard));
view.getLblAddCounterPermanent().addMouseListener(onClick(this::addCounterToPermanent));
view.getLblSubCounterPermanent().addMouseListener(onClick(this::removeCountersFromPermanent));
view.getLblTapPermanent().addMouseListener(onClick(this::tapPermanent));
view.getLblUntapPermanent().addMouseListener(onClick(this::untapPermanent));
view.getLblSetLife().addMouseListener(onClick(this::setPlayerLife));
view.getLblWinGame().addMouseListener(onClick(this::winGame));
view.getLblExileFromPlay().addMouseListener(onClick(this::exileCardsFromPlay));
view.getLblRemoveFromGame().addMouseListener(onClick(this::removeCardsFromGame));
view.getLblRiggedRoll().addMouseListener(onClick(this::riggedPlanerRoll));
view.getLblWalkTo().addMouseListener(onClick(this::planeswalkTo));
view.getLblAskAI().addMouseListener(onClick(this::askAI));
view.getLblAskSimulationAI().addMouseListener(onClick(this::askSimulationAI));
}
public IGameController getController() {
return matchUI.getGameController();
@@ -66,258 +67,99 @@ public final class CDev implements ICDoc {
listeners.add(listener);
}
private final MouseListener madUnlimited = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
togglePlayManyLandsPerTurn();
}
};
private MouseListener onClick(Runnable r) {
return new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
r.run();
}
};
}
public void togglePlayManyLandsPerTurn() {
final boolean newValue = !view.getLblUnlimitedLands().getToggled();
getController().cheat().setCanPlayUnlimitedLands(newValue);
update();
}
private final MouseListener madViewAll = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
toggleViewAllCards();
}
};
public void toggleViewAllCards() {
final boolean newValue = !view.getLblViewAll().getToggled();
getController().cheat().setViewAllCards(newValue);
update();
}
private final MouseListener madMana = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
generateMana();
}
};
public void generateMana() {
getController().cheat().generateMana();
}
private final MouseListener madSetup = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
setupGameState();
}
};
public void setupGameState() {
getController().cheat().setupGameState();
}
private final MouseListener madDump = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
dumpGameState();
}
};
public void dumpGameState() {
getController().cheat().dumpGameState();
}
private final MouseListener madTutor = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
tutorForCard();
}
};
public void tutorForCard() {
getController().cheat().tutorForCard();
}
private final MouseListener madCardToHand = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToHand();
}
};
public void addCardToHand() {
getController().cheat().addCardToHand();
}
private final MouseListener madCardToLibrary = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToLibrary();
}
};
public void addCardToLibrary() {
getController().cheat().addCardToLibrary();
}
private final MouseListener madCardToGraveyard = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToGraveyard();
}
};
public void addCardToGraveyard() {
getController().cheat().addCardToGraveyard();
}
private final MouseListener madCardToExile = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToExile();
}
};
public void addCardToExile() {
getController().cheat().addCardToExile();
}
private final MouseListener madCastASpell = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
castASpell();
}
};
public void castASpell() {
getController().cheat().castASpell();
}
private final MouseListener madRepeatAddCard = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
repeatAddCard();
}
};
public void repeatAddCard() {
getController().cheat().repeatLastAddition();
}
private final MouseListener madExileFromHand = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
exileCardsFromHand();
}
};
public void exileCardsFromHand() {
getController().cheat().exileCardsFromHand();
}
private final MouseListener madAddCounter = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCounterToPermanent();
}
};
public void addCounterToPermanent() {
getController().cheat().addCountersToPermanent();
}
private final MouseListener madSubCounter = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
removeCountersFromPermanent();
}
};
public void removeCountersFromPermanent() {
getController().cheat().removeCountersFromPermanent();
}
private final MouseListener madTap = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
tapPermanent();
}
};
public void tapPermanent() {
getController().cheat().tapPermanents();
}
private final MouseListener madUntap = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
untapPermanent();
}
};
public void untapPermanent() {
getController().cheat().untapPermanents();
}
private final MouseListener madLife = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
setPlayerLife();
}
};
public void setPlayerLife() {
getController().cheat().setPlayerLife();
}
private final MouseListener madWinGame = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
winGame();
}
};
public void winGame() {
getController().cheat().winGame();
}
private final MouseListener madCardToBattlefield = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
addCardToBattlefield();
}
};
public void addCardToBattlefield() {
getController().cheat().addCardToBattlefield();
}
private final MouseListener madExileFromPlay = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
exileCardsFromPlay();
}
};
public void exileCardsFromPlay() {
getController().cheat().exileCardsFromBattlefield();
}
private final MouseListener madRemoveFromGame = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
removeCardsFromGame();
}
};
public void removeCardsFromGame() {
getController().cheat().removeCardsFromGame();
}
private final MouseListener madRiggedRoll = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
riggedPlanerRoll();
}
};
public void riggedPlanerRoll() {
getController().cheat().riggedPlanarRoll();
}
private final MouseListener madWalkToPlane = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
planeswalkTo();
}
};
public void planeswalkTo() {
getController().cheat().planeswalkTo();
}
private final MouseListener madAskAI = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
askAI();
}
};
public void askAI() {
getController().cheat().askAI();
getController().cheat().askAI(false);
}
public void askSimulationAI() {
getController().cheat().askAI(true);
}
//========== 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 lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI"));
private final DevLabel lblAskSimulationAI = new DevLabel(Localizer.getInstance().getMessage("lblAskSimulationAI"));
private final CDev controller;
@@ -91,7 +92,6 @@ public class VDev implements IVDoc<CDev>, IDevListener {
public VDev(final CDev 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 halfConstraintsLeft = halfConstraints + ", split 2";
viewport.setOpaque(false);
@@ -113,13 +113,14 @@ public class VDev implements IVDoc<CDev>, IDevListener {
viewport.add(this.lblWinGame, halfConstraints);
viewport.add(this.lblAddCounterPermanent, halfConstraintsLeft);
viewport.add(this.lblSubCounterPermanent, halfConstraints);
viewport.add(this.lblSetupGame, halfConstraintsLeft);
viewport.add(this.lblDumpGame, halfConstraints);
viewport.add(this.lblTapPermanent, halfConstraintsLeft);
viewport.add(this.lblUntapPermanent, halfConstraints);
viewport.add(this.lblRiggedRoll, halfConstraintsLeft);
viewport.add(this.lblWalkTo, halfConstraints);
viewport.add(this.lblAskAI, halfConstraintsLeft);
viewport.add(this.lblAskSimulationAI, halfConstraintsLeft);
viewport.add(this.lblSetupGame, halfConstraintsLeft);
viewport.add(this.lblDumpGame, halfConstraints);
}
//========= Overridden methods
@@ -302,20 +303,23 @@ public class VDev implements IVDoc<CDev>, IDevListener {
return this.lblAskAI;
}
public DevLabel getLblAskSimulationAI() {
return this.lblAskSimulationAI;
}
/**
* Labels that act as buttons which control dev mode functions. Labels are
* used to support multiline text.
*/
public class DevLabel extends SkinnedLabel {
public static class DevLabel extends SkinnedLabel {
private static final long serialVersionUID = 7917311680519060700L;
private FSkin.SkinColor defaultBG;
private final FSkin.SkinColor hoverBG = FSkin.getColor(FSkin.Colors.CLR_HOVER);
private final FSkin.SkinColor pressedBG = FSkin.getColor(FSkin.Colors.CLR_INACTIVE);
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) {
super();
@@ -380,10 +384,10 @@ public class VDev implements IVDoc<CDev>, IDevListener {
*/
@Override
protected void paintComponent(final Graphics g) {
this.w = this.getWidth();
this.h = this.getHeight();
int w = this.getWidth();
int h = this.getHeight();
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);
}
}

View File

@@ -483,4 +483,126 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
AssertJUnit.assertEquals("Chaos Warp", sa.getHostCard().getName());
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]));
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;
}
}

View File

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

View File

@@ -1360,9 +1360,8 @@ public class FDeckChooser extends FScreen {
if (StringUtils.isBlank(savedState)) {
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 (Exception ex) {
System.err.println(ex + " [savedState=" + savedState + "]");

View File

@@ -99,7 +99,7 @@ public class FDeckViewer extends FScreen {
deckList.append(s.toString()).append(": ");
deckList.append(nl);
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);
}

View File

@@ -3,6 +3,6 @@ ManaCost:B R
Types:Creature Sliver
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."
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
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: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:ABRegen:AB$Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME.
SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME.
AI:RemoveDeck:Random
DeckNeeds:Color$Blue|Black
DeckHas:Ability$Counters

View File

@@ -4,7 +4,7 @@ Types:Enchantment Aura
K:Enchant land
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."
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
AI:RemoveDeck:All
SVar:NonStackingAttachEffect:True

View File

@@ -3,13 +3,14 @@ ManaCost:1 B
Types:Enchantment Aura
K:Enchant creature card in a graveyard
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: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
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$ DirectRemembered | SubAbility$ DBCleanup
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DelayTriggerRememberedLKI
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0.
DeckHas:Ability$Graveyard

View File

@@ -4,5 +4,5 @@ Types:Enchantment Aura
K:Enchant creature
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."
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."

View File

@@ -4,6 +4,6 @@ Types:Enchantment Aura
K:Enchant land
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."
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
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
PT:2/2
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
Oracle:All Slivers have "Sacrifice this permanent: Add {B}{B}."

View File

@@ -4,12 +4,11 @@ Types:Creature Vampire
PT:2/2
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.
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:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | 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:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ ExileMe
SVar:ExileMe:DB$ ChangeZoneAll | Origin$ Stack | Destination$ Exile | ChangeType$ Card.Self | SubAbility$ DBCleanup
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$ X | SubAbility$ DBExile
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$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:NumTimes:Number$0
SVar:X:Count$TriggerRememberAmount
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.

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:TapChosenCards:DB$ Tap | Defined$ Remembered | SubAbility$ AbandonSelf | 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:NumCreatures:Count$Valid Creature.RememberedPlayerCtrl
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:RollCounters:DB$ RollDice | ResultSVar$ Result | SubAbility$ Crash
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:Y:Count$CardManaCost
DeckHints:Type$Vehicle

View File

@@ -5,8 +5,6 @@ K:Enchant creature
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.
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:DBSet:DB$ SetLife | Defined$ Remembered | LifeAmount$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:PlayerCountRemembered$LifeTotal/Twice
SVar:TrigSetLife:DB$ SetLife | Defined$ TriggeredSourceController | LifeAmount$ X
SVar:X:PlayerCountDefinedTriggeredSourceController$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.

View File

@@ -1,10 +1,8 @@
Name:Cephalid Shrine
ManaCost:1 U U
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.
SVar:TrigCounterRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBCounter
SVar:DBCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator | SubAbility$ DBCleanup
SVar:X:Count$ValidGraveyard Card.sharesNameWith Remembered
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
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:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator
SVar:X:Count$ValidGraveyard Card.sharesNameWith TriggeredCard
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.

View File

@@ -5,5 +5,8 @@ PT:7/7
K:Flash
K:Flying
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.

View File

@@ -6,7 +6,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$
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.
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
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.

View File

@@ -3,5 +3,8 @@ ManaCost:no cost
Types:Land
K:CARDNAME enters the battlefield tapped.
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.

View File

@@ -3,13 +3,14 @@ ManaCost:1 B
Types:Enchantment Aura
K:Enchant creature card in a graveyard
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: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
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 | SubAbility$ DBCleanup
SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ 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.
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
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.
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:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedOrController | 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$ 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.

View File

@@ -2,7 +2,10 @@ Name:Dimir Keyrune
ManaCost:3
Types:Artifact
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
DeckHas:Type$Horror
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.

View File

@@ -4,9 +4,7 @@ Types:Creature Djinn Pirate
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.
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.
SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttackingPlayer | SubAbility$ DBDmg
SVar:DBDmg:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidHand Card.RememberedPlayerCtrl
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:TrigDmg:DB$ DealDamage | Defined$ TriggeredAttackingPlayer | NumDmg$ X
SVar:X:Count$ValidHand Card.OwnedBy TriggeredAttackingPlayer
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: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:NumInHand:RememberedController$CardsInHand
SVar:NumInLib:RememberedController$CardsInLibrary
SVar:NumInHand:PlayerCountRememberedController$CardsInHand
SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary
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.

View File

@@ -2,11 +2,11 @@ Name:Eye of Singularity
ManaCost:3 W
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.
SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy | UseImprinted$ True | SubAbility$ DBCleanup
SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Imprinted+IsNotImprinted | NoRegen$ True
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:TrigDestroyRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBDestroyAll
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Permanent.IsNotRemembered+sharesNameWith Remembered | NoRegen$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBRem | SubAbility$ DBDestroy
SVar:DBRem:DB$ Pump | ImprintCards$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered
SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Card.IsImprinted | NoRegen$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
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:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True
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.

View File

@@ -4,6 +4,6 @@ Types:Enchantment Aura
K:Enchant creature
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 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
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.
SVar:TrigToken:DB$ Token | TokenScript$ scrap
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:DBPump:DB$ Pump | Defined$ Self | KW$ Haste
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$ Menace
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:DBDraw:DB$ Draw
SVar:AIPreference:SacCost$Artifact.token
DeckHints:Type$Artifact
DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Haste
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.
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 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
Types:Instant
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
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.

View File

@@ -1,7 +1,7 @@
Name:Grafdigger's Cage
ManaCost:1
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.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random

View File

@@ -3,8 +3,9 @@ ManaCost:1 W B
Types:Legendary Creature Rat Pilot
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.
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | AnimateSubAbility$ Animate
SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | AtEOT$ Hand
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 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Graveyard
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.

View File

@@ -3,6 +3,6 @@ ManaCost:1 B B
Types:Sorcery
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: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.
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.
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.
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
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.

View File

@@ -6,9 +6,7 @@ K:Mutate:3 RG U U
K:Flying
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.
SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChoose
SVar:DBChoose:DB$ GenericChoice | Choices$ Battlefield,Hand | Defined$ You
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:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChange
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: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.

View File

@@ -4,9 +4,9 @@ Types:Creature Human Scout
PT:3/1
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.
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:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ NumTimes
SVar:NumTimes:Number$0
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$ X
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.
SVar:Z:Count$CardCounters.VALOR
DeckHas:Ability$LifeGain|Counters

View File

@@ -5,7 +5,7 @@ PT:3/3
K:Vigilance
K:Menace
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.
SVar:NonStackingEffect:True
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
ManaCost:1 W B
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
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
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$ 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
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.)

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:Animate:DB$ Animate | Defined$ TriggeredCard | Types$ Warlock | Duration$ Permanent
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
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
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.
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
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
PT:2/2
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:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn
AlternateMode:Adventure

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Sorcery
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: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: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

View File

@@ -1,10 +1,8 @@
Name:Nature's Will
ManaCost:2 G G
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.
SVar:TrigRememberTarget:DB$ Pump | RememberObjects$ TriggeredTarget | SubAbility$ DBTapAll
SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.RememberedPlayerCtrl | SubAbility$ DBUntapAll
SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
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:DBTapAll:DB$ TapAll | ValidCards$ Land.ControlledBy TriggeredTarget | SubAbility$ DBUntapAll
SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl
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.

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