Merge remote-tracking branch 'core-developers/master'

This commit is contained in:
klaxnek
2020-11-01 14:26:14 +01:00
223 changed files with 3278 additions and 465 deletions

View File

@@ -75,6 +75,7 @@ public enum AiProps { /** */
ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */ ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */ ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */ ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
ACTIVELY_PROTECT_VS_CURSE_AURAS("false"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */ DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */ DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */ DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */

View File

@@ -1845,6 +1845,28 @@ public class ComputerUtil {
} }
} }
} }
//Generic curse auras
else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))) {
AiController aic = aiPlayer.isAI() ? ((PlayerControllerAi)aiPlayer.getController()).getAi() : null;
boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false;
if (enableCurseAuraRemoval) {
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && tgt == null) && !grantShroud) {
continue;
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
threatened.add(c);
}
}
}
}
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility())); Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
return threatened; return threatened;

View File

@@ -340,17 +340,19 @@ public class AnimateAi extends SpellAbilityAi {
// select the worst of the best // select the worst of the best
final Card worst = ComputerUtilCard.getWorstAI(maxList); final Card worst = ComputerUtilCard.getWorstAI(maxList);
if (worst.isLand()) { if (worst != null) {
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability if (worst.isLand()) {
this.holdAnimatedTillMain2(ai, worst); // e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) { this.holdAnimatedTillMain2(ai, worst);
this.releaseHeldTillMain2(ai, worst); if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
return false; this.releaseHeldTillMain2(ai, worst);
return false;
}
} }
this.rememberAnimatedThisTurn(ai, worst);
sa.getTargets().add(worst);
} }
this.rememberAnimatedThisTurn(ai, worst); return true;
sa.getTargets().add(worst);
return true;
} }
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or // This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or

View File

@@ -124,6 +124,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private boolean smallSetOverride = false; private boolean smallSetOverride = false;
private String boosterMustContain = ""; private String boosterMustContain = "";
private String boosterReplaceSlotFromPrintSheet = ""; private String boosterReplaceSlotFromPrintSheet = "";
private String[] chaosDraftThemes = new String[0];
private boolean doublePickToStartRound = false; private boolean doublePickToStartRound = false;
private final CardInSet[] cards; private final CardInSet[] cards;
private final Map<String, Integer> tokenNormalized; private final Map<String, Integer> tokenNormalized;
@@ -195,6 +196,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public boolean getDoublePickToStartRound() { return doublePickToStartRound; } public boolean getDoublePickToStartRound() { return doublePickToStartRound; }
public String getBoosterMustContain() { return boosterMustContain; } public String getBoosterMustContain() { return boosterMustContain; }
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; } public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
public CardInSet[] getCards() { return cards; } public CardInSet[] getCards() { return cards; }
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
@@ -385,6 +387,9 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
res.boosterReplaceSlotFromPrintSheet = section.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card res.boosterReplaceSlotFromPrintSheet = section.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
res.chaosDraftThemes = section.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
return res; return res;
} }

View File

@@ -139,16 +139,22 @@ public final class MagicColor {
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest"); public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>() public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
.put("ManaColorConversion", "Additive") .put("ManaColorConversion", "Additive")
.put("WhiteConversion", "All") .put("WhiteConversion", "Color")
.put("BlueConversion", "All") .put("BlueConversion", "Color")
.put("BlackConversion", "All") .put("BlackConversion", "Color")
.put("RedConversion", "All") .put("RedConversion", "Color")
.put("GreenConversion", "All") .put("GreenConversion", "Color")
.put("ColorlessConversion", "Color")
.build(); .build();
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>() public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
.putAll(ANY_COLOR_CONVERSION) .put("ManaColorConversion", "Additive")
.put("ColorlessConversion", "All") .put("WhiteConversion", "Type")
.put("BlueConversion", "Type")
.put("BlackConversion", "Type")
.put("RedConversion", "Type")
.put("GreenConversion", "Type")
.put("ColorlessConversion", "Type")
.build(); .build();
/** /**
* Private constructor to prevent instantiation. * Private constructor to prevent instantiation.

View File

@@ -3,23 +3,20 @@ package forge.item.generation;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.item.BoosterPack; import forge.item.BoosterPack;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.BagRandomizer;
import java.util.List; import java.util.List;
public class ChaosBoosterSupplier implements IUnOpenedProduct { public class ChaosBoosterSupplier implements IUnOpenedProduct {
private List<CardEdition> sets; private BagRandomizer<CardEdition> randomizer;
public ChaosBoosterSupplier(List<CardEdition> sets) { public ChaosBoosterSupplier(Iterable<CardEdition> sets) throws IllegalArgumentException {
this.sets = sets; randomizer = new BagRandomizer<>(sets);
} }
@Override @Override
public List<PaperCard> get() { public List<PaperCard> get() {
if (sets.size() == 0) { final CardEdition set = randomizer.getNextItem();
System.out.println("No chaos boosters left to supply.");
return null;
}
final CardEdition set = sets.remove(0);
final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate()); final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate());
return pack.getCards(); return pack.getCards();
} }

View File

@@ -0,0 +1,76 @@
package forge.util;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
/**
* Data structure that allows random draws from a set number of items,
* where all items are returned once before the first will be retrieved.
* The bag will be shuffled after each time all items have been returned.
* @param <T> an object
*/
public class BagRandomizer<T > implements Iterable<T>{
private static Random random = new SecureRandom();
private T[] bag;
private int currentPosition = 0;
public BagRandomizer(T[] items) throws IllegalArgumentException {
if (items.length == 0) {
throw new IllegalArgumentException("Must include at least one item!");
}
bag = items;
shuffleBag();
}
public BagRandomizer(Iterable<T> items) throws IllegalArgumentException {
ArrayList<T> list = new ArrayList<>();
for (T item : items) {
list.add(item);
}
if (list.size() == 0) {
throw new IllegalArgumentException("Must include at least one item!");
}
bag = (T[]) list.toArray();
shuffleBag();
}
public T getNextItem() {
// reset bag if last position is reached
if (currentPosition >= bag.length) {
shuffleBag();
currentPosition = 0;
}
return bag[currentPosition++];
}
private void shuffleBag() {
int n = bag.length;
for (int i = 0; i < n; i++) {
int r = (int) (random.nextDouble() * (i + 1));
T swap = bag[r];
bag[r] = bag[i];
bag[i] = swap;
}
}
@Override
public Iterator<T> iterator() {
return new BagRandomizerIterator<T>();
}
private class BagRandomizerIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return bag.length > 0;
}
@Override
public T next() {
return (T) BagRandomizer.this.getNextItem();
}
}
}

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -33,7 +33,7 @@ import java.util.Map.Entry;
* <p> * <p>
* GameInfo class. * GameInfo class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id: GameOutcome.java 17608 2012-10-20 22:27:27Z Max mtg $ * @version $Id: GameOutcome.java 17608 2012-10-20 22:27:27Z Max mtg $
*/ */
@@ -47,7 +47,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public final List<PaperCard> lostCards; public final List<PaperCard> lostCards;
public final List<PaperCard> wonCards; public final List<PaperCard> wonCards;
private AnteResult(List<PaperCard> cards, boolean won) { private AnteResult(List<PaperCard> cards, boolean won) {
// Need empty lists for other results for addition of change ownership cards // Need empty lists for other results for addition of change ownership cards
if (won) { if (won) {
@@ -67,15 +67,20 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
this.lostCards.addAll(cards); this.lostCards.addAll(cards);
} }
public static AnteResult won(List<PaperCard> cards) { return new AnteResult(cards, true); } public static AnteResult won(List<PaperCard> cards) {
public static AnteResult lost(List<PaperCard> cards) { return new AnteResult(cards, false); } return new AnteResult(cards, true);
}
public static AnteResult lost(List<PaperCard> cards) {
return new AnteResult(cards, false);
}
} }
private int lastTurnNumber = 0; private int lastTurnNumber = 0;
private int lifeDelta = 0; private int lifeDelta = 0;
private int winningTeam = -1; private int winningTeam = -1;
private final HashMap<RegisteredPlayer, PlayerStatistics> playerRating = new HashMap<>(); private final HashMap<RegisteredPlayer, PlayerStatistics> playerRating = new HashMap<>();
private final HashMap<RegisteredPlayer, String> playerNames = new HashMap<>(); private final HashMap<RegisteredPlayer, String> playerNames = new HashMap<>();
public final Map<RegisteredPlayer, AnteResult> anteResult = new HashMap<>(); public final Map<RegisteredPlayer, AnteResult> anteResult = new HashMap<>();
@@ -83,21 +88,22 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public GameOutcome(GameEndReason reason, final Iterable<Player> players) { public GameOutcome(GameEndReason reason, final Iterable<Player> players) {
winCondition = reason; winCondition = reason;
calculateLifeDelta(players);
int winnersHealth = 0;
int opponentsHealth = 0;
for (final Player p : players) { for (final Player p : players) {
this.playerRating.put(p.getRegisteredPlayer(), p.getStats()); this.playerRating.put(p.getRegisteredPlayer(), p.getStats());
this.playerNames.put(p.getRegisteredPlayer(), p.getName()); this.playerNames.put(p.getRegisteredPlayer(), p.getName());
if (p.getOutcome().hasWon() && winCondition == GameEndReason.AllOpposingTeamsLost) { if (winCondition == GameEndReason.AllOpposingTeamsLost && p.getOutcome().hasWon()) {
// Only mark the WinningTeam when "Team mode" is on. // Only mark the WinningTeam when "Team mode" is on.
winningTeam = p.getTeam(); winningTeam = p.getTeam();
} }
} }
// Unable to calculate lifeDelta between a winning and losing player whe a draw is in place
if (winCondition == GameEndReason.Draw) return;
int winnersHealth = 0;
int opponentsHealth = 0;
for (final Player p : players) { for (final Player p : players) {
if (p.getTeam() == winningTeam) { if (p.getTeam() == winningTeam) {
winnersHealth += p.getLife(); winnersHealth += p.getLife();
@@ -106,22 +112,22 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
} }
} }
calculateLifeDelta(players);
lifeDelta = Math.max(0, winnersHealth - opponentsHealth); lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
} }
private void calculateLifeDelta(Iterable<Player> players) { private void calculateLifeDelta(Iterable<Player> players) {
int opponentsHealth = 0; int opponentsHealth = 0;
int winnersHealth = 0; int winnersHealth = 0;
for (Player p : players) { for (Player p : players) {
if (p.getOutcome().hasWon()) { if (p.getOutcome().hasWon()) {
winnersHealth += p.getLife(); winnersHealth += p.getLife();
} } else {
else {
opponentsHealth += p.getLife(); opponentsHealth += p.getLife();
} }
} }
lifeDelta = Math.max(0, winnersHealth - opponentsHealth); lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
} }
@@ -150,7 +156,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
/** /**
* Gets the winner. * Gets the winner.
* *
* @return the winner * @return the winner
*/ */
public LobbyPlayer getWinningLobbyPlayer() { public LobbyPlayer getWinningLobbyPlayer() {
@@ -169,7 +175,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
* distinguish between human player names (a problem for hotseat games). * distinguish between human player names (a problem for hotseat games).
*/ */
public RegisteredPlayer getWinningPlayer() { public RegisteredPlayer getWinningPlayer() {
for(Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) { for (Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) {
if (pair.getValue().getOutcome().hasWon()) { if (pair.getValue().getOutcome().hasWon()) {
return pair.getKey(); return pair.getKey();
} }
@@ -196,7 +202,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
/** /**
* Gets the win spell effect. * Gets the win spell effect.
* *
* @return the win spell effect * @return the win spell effect
*/ */
public String getWinSpellEffect() { public String getWinSpellEffect() {
@@ -227,7 +233,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public List<String> getOutcomeStrings() { public List<String> getOutcomeStrings() {
List<String> outcomes = Lists.newArrayList(); List<String> outcomes = Lists.newArrayList();
for(RegisteredPlayer player : playerNames.keySet()) { for (RegisteredPlayer player : playerNames.keySet()) {
outcomes.add(getOutcomeString(player)); outcomes.add(getOutcomeString(player));
} }
return outcomes; return outcomes;

View File

@@ -116,6 +116,14 @@ public class GameView extends TrackableObject {
public boolean isMatchOver() { public boolean isMatchOver() {
return get(TrackableProperty.MatchOver); return get(TrackableProperty.MatchOver);
} }
public boolean isMulligan() {
if (get(TrackableProperty.Mulligan) == null)
return false;
return get(TrackableProperty.Mulligan);
}
public void updateIsMulligan(boolean value) {
set(TrackableProperty.Mulligan, value);
}
public String getWinningPlayerName() { public String getWinningPlayerName() {
return get(TrackableProperty.WinningPlayerName); return get(TrackableProperty.WinningPlayerName);
} }

View File

@@ -108,7 +108,7 @@ public class AbilityUtils {
else if (defined.equals("Enchanted")) { else if (defined.equals("Enchanted")) {
c = hostCard.getEnchantingCard(); c = hostCard.getEnchantingCard();
if ((c == null) && (sa.getRootAbility() != null) if ((c == null) && (sa != null) && (sa.getRootAbility() != null)
&& (sa.getRootAbility().getPaidList("Sacrificed") != null) && (sa.getRootAbility().getPaidList("Sacrificed") != null)
&& !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) {
c = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); c = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard();
@@ -168,8 +168,8 @@ public class AbilityUtils {
if (crd instanceof Card) { if (crd instanceof Card) {
c = game.getCardState((Card) crd); c = game.getCardState((Card) crd);
} else if (crd instanceof List<?>) { } else if (crd instanceof Iterable<?>) {
cards.addAll((CardCollection) crd); cards.addAll(Iterables.filter((Iterable<?>) crd, Card.class));
} }
} }
else if (defined.equals("Remembered") || defined.equals("RememberedCard")) { else if (defined.equals("Remembered") || defined.equals("RememberedCard")) {
@@ -657,9 +657,7 @@ public class AbilityUtils {
if (calcX[0].startsWith("TriggeredPlayers")) { if (calcX[0].startsWith("TriggeredPlayers")) {
key = "Triggered" + key.substring(16); key = "Triggered" + key.substring(16);
} }
final List<Player> players = new ArrayList<>(); return CardFactoryUtil.playerXCount(getDefinedPlayers(card, key, sa), calcX[1], card) * multiplier;
Iterables.addAll(players, getDefinedPlayers(card, key, sa));
return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier;
} }
if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
@@ -1078,20 +1076,10 @@ public class AbilityUtils {
} }
if (o != null) { if (o != null) {
if (o instanceof Player) { if (o instanceof Player) {
final Player p = (Player) o; players.add((Player) o);
if (!players.contains(p)) {
players.add(p);
}
} }
if (o instanceof List) { if (o instanceof Iterable) {
final List<?> pList = (List<?>)o; players.addAll(Iterables.filter((Iterable<?>)o, Player.class));
if (!pList.isEmpty()) {
for (final Object p : pList) {
if (p instanceof Player && !players.contains(p)) {
players.add((Player) p);
}
}
}
} }
} }
} }
@@ -1802,9 +1790,12 @@ public class AbilityUtils {
if (params.containsKey(key)) { if (params.containsKey(key)) {
String convertTo = params.get(key); String convertTo = params.get(key);
byte convertByte = 0; byte convertByte = 0;
if ("All".equals(convertTo)) { if ("Type".equals(convertTo)) {
// IMPORTANT! We need to use Mana Color here not Card Color. // IMPORTANT! We need to use Mana Color here not Card Color.
convertByte = ManaAtom.ALL_MANA_TYPES; convertByte = ManaAtom.ALL_MANA_TYPES;
} else if ("Color".equals(convertTo)) {
// IMPORTANT! We need to use Mana Color here not Card Color.
convertByte = ManaAtom.ALL_MANA_COLORS;
} else { } else {
for (final String convertColor : convertTo.split(",")) { for (final String convertColor : convertTo.split(",")) {
convertByte |= ManaAtom.fromName(convertColor); convertByte |= ManaAtom.fromName(convertColor);

View File

@@ -10,6 +10,7 @@ import forge.GameCommand;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameLogEntryType;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -431,6 +432,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final Player player = sa.getActivatingPlayer(); final Player player = sa.getActivatingPlayer();
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = player.getGame(); final Game game = player.getGame();
final CardCollection commandCards = new CardCollection();
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final List<ZoneType> origin = Lists.newArrayList(); final List<ZoneType> origin = Lists.newArrayList();
@@ -664,6 +666,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
tgtC.setExiledWith(host); tgtC.setExiledWith(host);
} }
movedCard = game.getAction().moveTo(destination, tgtC, sa); movedCard = game.getAction().moveTo(destination, tgtC, sa);
if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) {
StringBuilder sb = new StringBuilder();
sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(player).append("'s hand.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
commandCards.add(movedCard); //add to list to reveal the commandzone cards
}
// If a card is Exiled from the stack, remove its spells from the stack // If a card is Exiled from the stack, remove its spells from the stack
if (sa.hasParam("Fizzle")) { if (sa.hasParam("Fizzle")) {
if (tgtC.isInZone(ZoneType.Exile) || tgtC.isInZone(ZoneType.Hand) if (tgtC.isInZone(ZoneType.Exile) || tgtC.isInZone(ZoneType.Hand)
@@ -709,6 +717,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
//reveal command cards that changes zone from command zone to player's hand
if (!commandCards.isEmpty()) {
game.getAction().reveal(commandCards, player, true, "Revealed cards in ");
}
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game);
// for things like Gaea's Blessing // for things like Gaea's Blessing

View File

@@ -56,8 +56,9 @@ public class CharmEffect extends SpellAbilityEffect {
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa); List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
final int num; final int num;
// hotfix for Vindictive Lich when using getCardForUi boolean additionalDesc = sa.hasParam("AdditionalDescription");
if (source.getController() == null && sa.getParamOrDefault("CharmNum", "1").contains("MaxUniqueOpponents")) { // hotfix for complex cards when using getCardForUi
if (source.getController() == null && additionalDesc) {
// using getCardForUi game is not set, so can't guess max charm // using getCardForUi game is not set, so can't guess max charm
num = Integer.MAX_VALUE; num = Integer.MAX_VALUE;
} else { } else {
@@ -73,8 +74,8 @@ public class CharmEffect extends SpellAbilityEffect {
sb.append(sa.getCostDescription()); sb.append(sa.getCostDescription());
sb.append(oppChooses ? "An opponent chooses " : "Choose "); sb.append(oppChooses ? "An opponent chooses " : "Choose ");
if (num == min) { if (num == min || num == Integer.MAX_VALUE) {
sb.append(Lang.getNumeral(num)); sb.append(Lang.getNumeral(min));
} else if (min == 0) { } else if (min == 0) {
sb.append("up to ").append(Lang.getNumeral(num)); sb.append("up to ").append(Lang.getNumeral(num));
} else { } else {
@@ -97,7 +98,6 @@ public class CharmEffect extends SpellAbilityEffect {
sb.append(". You may choose the same mode more than once."); sb.append(". You may choose the same mode more than once.");
} }
boolean additionalDesc = sa.hasParam("AdditionalDescription");
if (additionalDesc) { if (additionalDesc) {
sb.append(" ").append(sa.getParam("AdditionalDescription").trim()); sb.append(" ").append(sa.getParam("AdditionalDescription").trim());
} }

View File

@@ -163,6 +163,11 @@ public class DigEffect extends SpellAbilityEffect {
host.addRemembered(one); host.addRemembered(one);
} }
} }
if (sa.hasParam("ImprintRevealed") && hasRevealed) {
for (final Card one : top) {
host.addImprintedCard(one);
}
}
if (sa.hasParam("Choser")) { if (sa.hasParam("Choser")) {
final FCollectionView<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Choser"), sa); final FCollectionView<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Choser"), sa);
if (!choosers.isEmpty()) { if (!choosers.isEmpty()) {

View File

@@ -62,6 +62,11 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
final int life1 = p1.getLife(); final int life1 = p1.getLife();
final int life2 = p2.getLife(); final int life2 = p2.getLife();
if (sa.hasParam("RememberDifference")) {
final int diff = life1 - life2;
source.addRemembered(diff);
}
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) { if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
final int diff = life1 - life2; final int diff = life1 - life2;
p1.loseLife(diff); p1.loseLife(diff);

View File

@@ -58,6 +58,7 @@ public class SetStateEffect extends SpellAbilityEffect {
final boolean manifestUp = sa.hasParam("ManifestUp"); final boolean manifestUp = sa.hasParam("ManifestUp");
final boolean hiddenAgenda = sa.hasParam("HiddenAgenda"); final boolean hiddenAgenda = sa.hasParam("HiddenAgenda");
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
final CardCollection transformedCards = new CardCollection();
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
@@ -130,8 +131,12 @@ public class SetStateEffect extends SpellAbilityEffect {
if (remChanged) { if (remChanged) {
host.addRemembered(tgt); host.addRemembered(tgt);
} }
transformedCards.add(tgt);
} }
} }
table.triggerCountersPutAll(game); table.triggerCountersPutAll(game);
if (!transformedCards.isEmpty()) {
game.getAction().reveal(transformedCards, p, true, "Transformed cards in ");
}
} }
} }

View File

@@ -1823,7 +1823,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl") || keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt") || keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap") || keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) { || keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
|| keyword.startsWith("Encore")) {
// keyword parsing takes care of adding a proper description // keyword parsing takes care of adding a proper description
} else if (keyword.startsWith("CantBeBlockedByAmount")) { } else if (keyword.startsWith("CantBeBlockedByAmount")) {
sbLong.append(getName()).append(" can't be blocked "); sbLong.append(getName()).append(" can't be blocked ");

View File

@@ -25,14 +25,14 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create(); private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create();
public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) { public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) {
this.putAll(damageMap); putAll(damageMap);
} }
public CardDamageMap() { public CardDamageMap() {
} }
public void triggerPreventDamage(boolean isCombat) { public void triggerPreventDamage(boolean isCombat) {
for (Map.Entry<GameEntity, Map<Card, Integer>> e : this.columnMap().entrySet()) { for (Map.Entry<GameEntity, Map<Card, Integer>> e : columnMap().entrySet()) {
int sum = 0; int sum = 0;
for (final int i : e.getValue().values()) { for (final int i : e.getValue().values()) {
sum += i; sum += i;
@@ -51,7 +51,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) { public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) {
// Source -> Targets // Source -> Targets
for (Map.Entry<Card, Map<GameEntity, Integer>> e : this.rowMap().entrySet()) { for (Map.Entry<Card, Map<GameEntity, Integer>> e : rowMap().entrySet()) {
final Card sourceLKI = e.getKey(); final Card sourceLKI = e.getKey();
int sum = 0; int sum = 0;
for (final Integer i : e.getValue().values()) { for (final Integer i : e.getValue().values()) {
@@ -71,7 +71,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
} }
} }
// Targets -> Source // Targets -> Source
for (Map.Entry<GameEntity, Map<Card, Integer>> e : this.columnMap().entrySet()) { for (Map.Entry<GameEntity, Map<Card, Integer>> e : columnMap().entrySet()) {
int sum = 0; int sum = 0;
for (final int i : e.getValue().values()) { for (final int i : e.getValue().values()) {
sum += i; sum += i;
@@ -106,9 +106,16 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
return dataMap; return dataMap;
} }
public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) { public int totalAmount() {
int result = 0; int result = 0;
for (int i : values()) {
result += i;
}
return result;
}
public CardDamageMap filteredMap(String validSource, String validTarget, Card host, SpellAbility sa) {
CardDamageMap result = new CardDamageMap();
Set<Card> filteredSource = null; Set<Card> filteredSource = null;
Set<GameEntity> filteredTarget = null; Set<GameEntity> filteredTarget = null;
if (validSource != null) { if (validSource != null) {
@@ -125,7 +132,8 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) { if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) {
continue; continue;
} }
result += c.getValue();
result.put(c.getRowKey(), c.getColumnKey(), c.getValue());
} }
return result; return result;

View File

@@ -1156,6 +1156,9 @@ public class CardFactoryUtil {
if (sq[0].contains("Landfall")) { if (sq[0].contains("Landfall")) {
return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c);
} }
if (sq[0].contains("Monarch")) {
return doXMath(Integer.parseInt(sq[cc.equals(game.getMonarch()) ? 1 : 2]), m, c);
}
if (sq[0].contains("Blessing")) { if (sq[0].contains("Blessing")) {
return doXMath(Integer.parseInt(sq[cc.hasBlessing() ? 1 : 2]), m, c); return doXMath(Integer.parseInt(sq[cc.hasBlessing() ? 1 : 2]), m, c);
} }
@@ -2906,7 +2909,7 @@ public class CardFactoryUtil {
final String repeatStr = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer | ChangeZoneTable$ True"; final String repeatStr = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer | ChangeZoneTable$ True";
final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Remembered" final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Remembered"
+ " | ChoosePlayerOrPlaneswalker$ True | ImprintTokens$ True"; + " | ChoosePlayerOrPlaneswalker$ True | ImprintTokens$ True";
final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | RememberObjects$ Imprinted" final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | RememberObjects$ Imprinted"
+ " | TriggerDescription$ Exile the tokens at end of combat."; + " | TriggerDescription$ Exile the tokens at end of combat.";
@@ -4344,6 +4347,52 @@ public class CardFactoryUtil {
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Encore")) {
final String[] k = keyword.split(":");
final String manacost = k[1];
String effect = "AB$ RepeatEach | Cost$ " + manacost + " ExileFromGrave<1/CARDNAME> " +
"| ActivationZone$ Graveyard | RepeatPlayers$ Opponent" +
"| PrecostDesc$ Encore | CostDesc$ " + ManaCostParser.parse(manacost) +
"| SpellDescription$ (" + inst.getReminderText() + ")";
final String copyStr = "DB$ CopyPermanent | Defined$ Self | ImprintTokens$ True " +
"| AddKeywords$ Haste | RememberTokens$ True | TokenRemembered$ Player.IsRemembered";
final String pumpStr = "DB$ PumpAll | ValidCards$ Creature.IsRemembered " +
"| KW$ HIDDEN CARDNAME attacks specific player each combat if able:Remembered";
final String pumpcleanStr = "DB$ Cleanup | ForgetDefined$ RememberedCard";
final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | RememberObjects$ Imprinted " +
"| StackDescription$ None | TriggerDescription$ Sacrifice them at the beginning of the next end step.";
final String sacStr = "DB$ SacrificeAll | Defined$ DelayTriggerRemembered";
final String cleanupStr = "DB$ Cleanup | ClearImprinted$ True";
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa);
AbilitySub copySA = (AbilitySub) AbilityFactory.getAbility(copyStr, card);
sa.setAdditionalAbility("RepeatSubAbility", copySA);
AbilitySub pumpSA = (AbilitySub) AbilityFactory.getAbility(pumpStr, card);
copySA.setSubAbility(pumpSA);
AbilitySub pumpcleanSA = (AbilitySub) AbilityFactory.getAbility(pumpcleanStr, card);
pumpSA.setSubAbility(pumpcleanSA);
AbilitySub delTrigSA = (AbilitySub) AbilityFactory.getAbility(delTrigStr, card);
sa.setSubAbility(delTrigSA);
AbilitySub sacSA = (AbilitySub) AbilityFactory.getAbility(sacStr, card);
delTrigSA.setAdditionalAbility("Execute", sacSA);
AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card);
delTrigSA.setSubAbility(cleanupSA);
} else if (keyword.startsWith("Spectacle")) { } else if (keyword.startsWith("Spectacle")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false); final Cost cost = new Cost(k[1], false);
@@ -4404,6 +4453,8 @@ public class CardFactoryUtil {
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getName(), "with", String.valueOf(counters),"time counters on it."); String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getName(), "with", String.valueOf(counters),"time counters on it.");
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
//reveal suspended card
game.getAction().reveal(new CardCollection(c), c.getOwner(), true, c.getName() + " is suspended with " + counters + " time counters in ");
} }
}; };
final StringBuilder sbDesc = new StringBuilder(); final StringBuilder sbDesc = new StringBuilder();
@@ -4686,7 +4737,7 @@ public class CardFactoryUtil {
altCostSA.setRestrictions(restriction); altCostSA.setRestrictions(restriction);
String costDescription = TextUtil.fastReplace(params.get("Description"),"CARDNAME", card.getName()); String costDescription = TextUtil.fastReplace(params.get("Description"),"CARDNAME", card.getName());
if (costDescription.isEmpty()) { if (costDescription == null || costDescription.isEmpty()) {
costDescription = TextUtil.concatWithSpace("You may", abCost.toStringAlt(), "rather than pay", TextUtil.addSuffix(card.getName(), "'s mana cost.")); costDescription = TextUtil.concatWithSpace("You may", abCost.toStringAlt(), "rather than pay", TextUtil.addSuffix(card.getName(), "'s mana cost."));
} }
@@ -4758,6 +4809,16 @@ public class CardFactoryUtil {
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2)); String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
String operand = part.substring(kwLength + 2); String operand = part.substring(kwLength + 2);
postponedAdjectives.add(Pair.of(true, "power" + opName + operand)); postponedAdjectives.add(Pair.of(true, "power" + opName + operand));
} else if (part.startsWith("toughness")) {
int kwLength = 9;
String operand = part.substring(kwLength + 2);
String opName = "";
if (part.startsWith("toughnessGE")) {
opName = " or greater";
} else {
opName = "update CardFactoryUtil line 4773";
}
postponedAdjectives.add(Pair.of(true, "toughness " + operand + opName));
} else if (CardType.isACreatureType(part)) { } else if (CardType.isACreatureType(part)) {
if (creatures != null && CardType.isACreatureType(creatures)) { // e.g. Kor Castigator if (creatures != null && CardType.isACreatureType(creatures)) { // e.g. Kor Castigator
creatures = StringUtils.capitalize(Lang.getPlural(part)) + creatures; creatures = StringUtils.capitalize(Lang.getPlural(part)) + creatures;

View File

@@ -253,6 +253,13 @@ public class CardView extends GameEntityView {
} }
return counters.equals(otherCard.getCounters()); return counters.equals(otherCard.getCounters());
} }
public boolean hasSamePT(CardView otherCard) {
if (getCurrentState().getPower() != otherCard.getCurrentState().getPower())
return false;
if (getCurrentState().getToughness() != otherCard.getCurrentState().getToughness())
return false;
return true;
}
void updateCounters(Card c) { void updateCounters(Card c) {
set(TrackableProperty.Counters, c.getCounters()); set(TrackableProperty.Counters, c.getCounters());
updateLethalDamage(c); updateLethalDamage(c);
@@ -691,6 +698,20 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.AlternateState); return get(TrackableProperty.AlternateState);
} }
public boolean hasLeftSplitState() {
return getLeftSplitState() != null;
}
public CardStateView getLeftSplitState() {
return get(TrackableProperty.LeftSplitState);
}
public boolean hasRightSplitState() {
return getRightSplitState() != null;
}
public CardStateView getRightSplitState() {
return get(TrackableProperty.RightSplitState);
}
public boolean hasBackSide() { public boolean hasBackSide() {
return get(TrackableProperty.HasBackSide); return get(TrackableProperty.HasBackSide);
} }
@@ -731,9 +752,8 @@ public class CardView extends GameEntityView {
CardState currentState = c.getCurrentState(); CardState currentState = c.getCurrentState();
if (isSplitCard) { if (isSplitCard) {
if (c.getCurrentStateName() != CardStateName.LeftSplit && c.getCurrentStateName() != CardStateName.RightSplit && c.getCurrentStateName() != CardStateName.FaceDown) { set(TrackableProperty.LeftSplitState, c.getState(CardStateName.LeftSplit).getView());
currentState = c.getState(CardStateName.LeftSplit); set(TrackableProperty.RightSplitState, c.getState(CardStateName.RightSplit).getView());
}
} }
CardStateView currentStateView = currentState.getView(); CardStateView currentStateView = currentState.getView();
@@ -901,6 +921,12 @@ public class CardView extends GameEntityView {
public ColorSet getOriginalColors() { public ColorSet getOriginalColors() {
return get(TrackableProperty.OriginalColors); return get(TrackableProperty.OriginalColors);
} }
public ColorSet getLeftSplitColors() {
return get(TrackableProperty.LeftSplitColors);
}
public ColorSet getRightSplitColors() {
return get(TrackableProperty.RightSplitColors);
}
void updateColors(Card c) { void updateColors(Card c) {
set(TrackableProperty.Colors, c.determineColor()); set(TrackableProperty.Colors, c.determineColor());
} }
@@ -909,6 +935,10 @@ public class CardView extends GameEntityView {
} }
void setOriginalColors(Card c) { void setOriginalColors(Card c) {
set(TrackableProperty.OriginalColors, c.determineColor()); set(TrackableProperty.OriginalColors, c.determineColor());
if (c.isSplitCard()) {
set(TrackableProperty.LeftSplitColors, c.determineColor(c.getState(CardStateName.LeftSplit)));
set(TrackableProperty.RightSplitColors, c.determineColor(c.getState(CardStateName.RightSplit)));
}
} }
void updateHasChangeColors(boolean hasChangeColor) { void updateHasChangeColors(boolean hasChangeColor) {
set(TrackableProperty.HasChangedColors, hasChangeColor); set(TrackableProperty.HasChangedColors, hasChangeColor);
@@ -1082,6 +1112,7 @@ public class CardView extends GameEntityView {
public String getProtectionKey() { return get(TrackableProperty.ProtectionKey); } public String getProtectionKey() { return get(TrackableProperty.ProtectionKey); }
public String getHexproofKey() { return get(TrackableProperty.HexproofKey); } public String getHexproofKey() { return get(TrackableProperty.HexproofKey); }
public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); } public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); }
public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); }
public boolean hasDefender() { return get(TrackableProperty.HasDefender); } public boolean hasDefender() { return get(TrackableProperty.HasDefender); }
public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); } public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); }
public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); } public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); }
@@ -1118,6 +1149,7 @@ public class CardView extends GameEntityView {
void updateKeywords(Card c, CardState state) { void updateKeywords(Card c, CardState state) {
c.updateKeywordsCache(state); c.updateKeywordsCache(state);
set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state)); set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state));
set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state));
set(TrackableProperty.HasDefender, c.hasKeyword(Keyword.DEFENDER, state)); set(TrackableProperty.HasDefender, c.hasKeyword(Keyword.DEFENDER, state));
set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state)); set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state));
set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state)); set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state));

View File

@@ -337,15 +337,12 @@ public class CostAdjustment {
return; return;
} }
int value = 0; int value = Integer.parseInt(amount);
if (StringUtils.isNumeric(amount)) {
value = Integer.parseInt(amount); if (staticAbility.hasParam("RaiseTo")) {
} else { int cmc = manaCost.getConvertedManaCost();
if ("Min3".equals(amount)) { if (cmc < value) {
int cmc = manaCost.getConvertedManaCost(); value = Integer.parseInt(amount) - cmc;
if (cmc < 3) {
value = 3 - cmc;
}
} }
} }

View File

@@ -49,6 +49,7 @@ public enum Keyword {
EMBALM("Embalm", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Create a token that's a copy of this card, except it's white, it has no mana cost, and it's a Zombie in addition to its other types. Embalm only as a sorcery."), EMBALM("Embalm", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Create a token that's a copy of this card, except it's white, it has no mana cost, and it's a Zombie in addition to its other types. Embalm only as a sorcery."),
EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's converted mana cost."), EMERGE("Emerge", KeywordWithCost.class, false, "You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's converted mana cost."),
ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."), ENCHANT("Enchant", KeywordWithType.class, false, "Target a %s as you cast this. This card enters the battlefield attached to that %s."),
ENCORE("Encore", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery."),
ENTWINE("Entwine", KeywordWithCost.class, true, "You may choose all modes of this spell instead of just one. If you do, you pay an additional %s."), ENTWINE("Entwine", KeywordWithCost.class, true, "You may choose all modes of this spell instead of just one. If you do, you pay an additional %s."),
EPIC("Epic", SimpleKeyword.class, true, "For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps for the rest of the game, copy this spell except for its epic ability. If the spell has any targets, you may choose new targets for the copy."), EPIC("Epic", SimpleKeyword.class, true, "For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps for the rest of the game, copy this spell except for its epic ability. If the spell has any targets, you may choose new targets for the copy."),
EQUIP("Equip", Equip.class, false, "%s: Attach to target %s you control. Equip only as a sorcery."), EQUIP("Equip", Equip.class, false, "%s: Attach to target %s you control. Equip only as a sorcery."),

View File

@@ -581,7 +581,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (infect) { if (infect) {
addPoisonCounters(amount, source, counterTable); addPoisonCounters(amount, source, counterTable);
} }
else { else if (!hasKeyword("Damage doesn't cause you to lose life.")) {
// Worship does not reduce the damage dealt but changes the effect // Worship does not reduce the damage dealt but changes the effect
// of the damage // of the damage
if (hasKeyword("DamageLifeThreshold:7") && life - 7 <= amount) { if (hasKeyword("DamageLifeThreshold:7") && life - 7 <= amount) {
@@ -741,6 +741,10 @@ public class Player extends GameEntity implements Comparable<Player> {
restDamage = 0; restDamage = 0;
} }
} }
} else if (c.getName().equals("Obosh, the Preypiercer")) {
if (c.getController().equals(source.getController()) && source.getCMC() % 2 != 0) {
restDamage *= 2;
}
} }
} }

View File

@@ -439,6 +439,12 @@ public class PlayerView extends GameEntityView {
return types.size(); return types.size();
} }
public boolean hasDelirium() {
if (get(TrackableProperty.HasDelirium) == null)
return false;
return get(TrackableProperty.HasDelirium);
}
private static TrackableProperty getZoneProp(final ZoneType zone) { private static TrackableProperty getZoneProp(final ZoneType zone) {
switch (zone) { switch (zone) {
case Ante: case Ante:
@@ -466,6 +472,10 @@ public class PlayerView extends GameEntityView {
if (prop == null) { return; } if (prop == null) { return; }
set(prop, CardView.getCollection(zone.getCards(false))); set(prop, CardView.getCollection(zone.getCards(false)));
//update delirium
if (ZoneType.Graveyard == zone.getZoneType())
set(TrackableProperty.HasDelirium, getZoneTypes(TrackableProperty.Graveyard) >= 4);
//update flashback zone when graveyard, library, or exile zones updated //update flashback zone when graveyard, library, or exile zones updated
switch (zone.getZoneType()) { switch (zone.getZoneType()) {
case Command: case Command:

View File

@@ -38,6 +38,7 @@ public class StackItemView extends TrackableObject implements IHasCardView {
updateAbility(si); updateAbility(si);
updateOptionalTrigger(si); updateOptionalTrigger(si);
updateSubInstance(si); updateSubInstance(si);
updateOptionalCost(si);
} }
public String getKey() { public String getKey() {
@@ -47,6 +48,55 @@ public class StackItemView extends TrackableObject implements IHasCardView {
set(TrackableProperty.Key, si.getSpellAbility(false).yieldKey()); set(TrackableProperty.Key, si.getSpellAbility(false).yieldKey());
} }
public String getOptionalCostString() {
return get(TrackableProperty.OptionalCosts);
}
void updateOptionalCost(SpellAbilityStackInstance si) {
String OptionalCostString = "";
boolean kicked = false;
boolean entwined = false;
boolean buyback = false;
boolean retraced = false;
boolean jumpstart = false;
boolean additional = false;
boolean alternate = false;
boolean generic = false;
for (OptionalCost cost : si.getSpellAbility(false).getOptionalCosts()) {
if (cost == OptionalCost.Kicker1 || cost == OptionalCost.Kicker2)
kicked = true;
if (cost == OptionalCost.Entwine)
entwined = true;
if (cost == OptionalCost.Buyback)
buyback = true;
if (cost == OptionalCost.Retrace)
retraced = true;
if (cost == OptionalCost.Jumpstart)
jumpstart = true;
if (cost == OptionalCost.Flash)
additional = true;
if (cost == OptionalCost.Generic)
generic = true;
if (cost == OptionalCost.AltCost)
alternate = true;
}
if (!alternate) {
if (kicked && !generic)
OptionalCostString += "Kicked";
if (entwined)
OptionalCostString += OptionalCostString.equals("") ? "Entwined" : ", Entwined";
if (buyback)
OptionalCostString += OptionalCostString.equals("") ? "Buyback" : ", Buyback";
if (retraced)
OptionalCostString += OptionalCostString.equals("") ? "Retraced" : ", Retraced";
if (jumpstart)
OptionalCostString += OptionalCostString.equals("") ? "Jumpstart" : ", Jumpstart";
if (additional || generic)
OptionalCostString += OptionalCostString.equals("") ? "Additional" : ", Additional";
}
set(TrackableProperty.OptionalCosts, OptionalCostString);
}
public int getSourceTrigger() { public int getSourceTrigger() {
return get(TrackableProperty.SourceTrigger); return get(TrackableProperty.SourceTrigger);
} }

View File

@@ -631,6 +631,13 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
} }
} }
if (hasParam("UnlessDefinedPlayer")) {
List<Player> players = AbilityUtils.getDefinedPlayers(hostCard, getParam("UnlessDefinedPlayer"), null);
if (!players.isEmpty()) {
return false;
}
}
if (hasParam("TopCardOfLibraryIs")) { if (hasParam("TopCardOfLibraryIs")) {
if (controller.getCardsIn(ZoneType.Library).isEmpty()) { if (controller.getCardsIn(ZoneType.Library).isEmpty()) {
return false; return false;

View File

@@ -29,24 +29,25 @@ public class TriggerDamageAll extends Trigger {
} }
} }
final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap); final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap);
return filterTable(table) > 0; return !table.filteredMap(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null).isEmpty();
} }
@Override @Override
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) { public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap); CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap);
table = table.filteredMap(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null);
sa.setTriggeringObject(AbilityKey.DamageAmount, filterTable(table)); sa.setTriggeringObject(AbilityKey.DamageAmount, table.totalAmount());
sa.setTriggeringObject(AbilityKey.Sources, table.rowKeySet());
sa.setTriggeringObject(AbilityKey.Targets, table.columnKeySet());
} }
@Override @Override
public String getImportantStackObjects(SpellAbility sa) { public String getImportantStackObjects(SpellAbility sa) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(Localizer.getInstance().getMessage("lblDamageSource")).append(": ").append(sa.getTriggeringObject(AbilityKey.Sources)).append(", ");
sb.append(Localizer.getInstance().getMessage("lblDamaged")).append(": ").append(sa.getTriggeringObject(AbilityKey.Targets)).append(", ");
sb.append(Localizer.getInstance().getMessage("lblAmount")).append(": ").append(sa.getTriggeringObject(AbilityKey.DamageAmount)); sb.append(Localizer.getInstance().getMessage("lblAmount")).append(": ").append(sa.getTriggeringObject(AbilityKey.DamageAmount));
return sb.toString(); return sb.toString();
} }
private int filterTable(CardDamageMap table) {
return table.filteredAmount(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null);
}
} }

View File

@@ -573,6 +573,11 @@ public class TriggerHandler {
host.addRemembered(sa.getActivatingPlayer()); host.addRemembered(sa.getActivatingPlayer());
} }
if (regtrig.hasParam("RememberTriggeringCard")) {
Card triggeredCard = ((Card) sa.getTriggeringObject(AbilityKey.Card));
host.addRemembered(triggeredCard);
}
sa.setStackDescription(sa.toString()); sa.setStackDescription(sa.toString());
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
// need to be set for demonic pact to look for chosen modes // need to be set for demonic pact to look for chosen modes

View File

@@ -101,10 +101,10 @@ public class TriggerTapsForMana extends Trigger {
if (!this.getHostCard().hasChosenColor() || !produced.contains(MagicColor.toShortString(this.getHostCard().getChosenColor()))) { if (!this.getHostCard().hasChosenColor() || !produced.contains(MagicColor.toShortString(this.getHostCard().getChosenColor()))) {
return false; return false;
} }
if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) { }
if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) {
return false; return false;
} }
}
} }
return true; return true;

View File

@@ -69,6 +69,8 @@ public enum TrackableProperty {
PairedWith(TrackableTypes.CardViewType), PairedWith(TrackableTypes.CardViewType),
CurrentState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze), CurrentState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
AlternateState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze), AlternateState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
LeftSplitState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
RightSplitState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
HiddenId(TrackableTypes.IntegerType), HiddenId(TrackableTypes.IntegerType),
ExertedThisTurn(TrackableTypes.BooleanType), ExertedThisTurn(TrackableTypes.BooleanType),
@@ -76,6 +78,8 @@ public enum TrackableProperty {
Name(TrackableTypes.StringType), Name(TrackableTypes.StringType),
Colors(TrackableTypes.ColorSetType), Colors(TrackableTypes.ColorSetType),
OriginalColors(TrackableTypes.ColorSetType), OriginalColors(TrackableTypes.ColorSetType),
LeftSplitColors(TrackableTypes.ColorSetType),
RightSplitColors(TrackableTypes.ColorSetType),
ImageKey(TrackableTypes.StringType), ImageKey(TrackableTypes.StringType),
Type(TrackableTypes.CardTypeViewType), Type(TrackableTypes.CardTypeViewType),
ManaCost(TrackableTypes.ManaCostType), ManaCost(TrackableTypes.ManaCostType),
@@ -92,6 +96,7 @@ public enum TrackableProperty {
KeywordKey(TrackableTypes.StringType), KeywordKey(TrackableTypes.StringType),
HasDeathtouch(TrackableTypes.BooleanType), HasDeathtouch(TrackableTypes.BooleanType),
HasDevoid(TrackableTypes.BooleanType),
HasDefender(TrackableTypes.BooleanType), HasDefender(TrackableTypes.BooleanType),
HasDoubleStrike(TrackableTypes.BooleanType), HasDoubleStrike(TrackableTypes.BooleanType),
HasFirstStrike(TrackableTypes.BooleanType), HasFirstStrike(TrackableTypes.BooleanType),
@@ -160,6 +165,7 @@ public enum TrackableProperty {
IsExtraTurn(TrackableTypes.BooleanType), IsExtraTurn(TrackableTypes.BooleanType),
ExtraTurnCount(TrackableTypes.IntegerType), ExtraTurnCount(TrackableTypes.IntegerType),
HasPriority(TrackableTypes.BooleanType), HasPriority(TrackableTypes.BooleanType),
HasDelirium(TrackableTypes.BooleanType),
//SpellAbility //SpellAbility
HostCard(TrackableTypes.CardViewType), HostCard(TrackableTypes.CardViewType),
@@ -181,6 +187,7 @@ public enum TrackableProperty {
SubInstance(TrackableTypes.StackItemViewType), SubInstance(TrackableTypes.StackItemViewType),
Ability(TrackableTypes.BooleanType), Ability(TrackableTypes.BooleanType),
OptionalTrigger(TrackableTypes.BooleanType), OptionalTrigger(TrackableTypes.BooleanType),
OptionalCosts(TrackableTypes.StringType),
//Combat //Combat
AttackersWithDefenders(TrackableTypes.GenericMapType, FreezeMode.IgnoresFreeze), AttackersWithDefenders(TrackableTypes.GenericMapType, FreezeMode.IgnoresFreeze),
@@ -199,6 +206,7 @@ public enum TrackableProperty {
WinningPlayerName(TrackableTypes.StringType), WinningPlayerName(TrackableTypes.StringType),
WinningTeam(TrackableTypes.IntegerType), WinningTeam(TrackableTypes.IntegerType),
MatchOver(TrackableTypes.BooleanType), MatchOver(TrackableTypes.BooleanType),
Mulligan(TrackableTypes.BooleanType),
NumGamesInMatch(TrackableTypes.IntegerType), NumGamesInMatch(TrackableTypes.IntegerType),
NumPlayedGamesInMatch(TrackableTypes.IntegerType), NumPlayedGamesInMatch(TrackableTypes.IntegerType),
Stack(TrackableTypes.StackItemViewListType), Stack(TrackableTypes.StackItemViewListType),

View File

@@ -102,7 +102,7 @@
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-backend-android</artifactId> <artifactId>gdx-backend-android</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -204,7 +204,7 @@ public class CardDetailPanel extends SkinnedPanel {
} else { } else {
final String manaCost; final String manaCost;
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack
manaCost = card.getCurrentState().getManaCost() + " // " + card.getAlternateState().getManaCost(); manaCost = card.getLeftSplitState().getManaCost() + " // " + card.getAlternateState().getManaCost();
} else { } else {
manaCost = state.getManaCost().toString(); manaCost = state.getManaCost().toString();
} }
@@ -256,7 +256,7 @@ public class CardDetailPanel extends SkinnedPanel {
idLabel.setText(mayView ? CardDetailUtil.formatCardId(state) : ""); idLabel.setText(mayView ? CardDetailUtil.formatCardId(state) : "");
// fill the card text // fill the card text
cdArea.setText(FSkin.encodeSymbols(CardDetailUtil.composeCardText(state, gameView, mayView), true)); cdArea.setText(FSkin.encodeSymbols(CardDetailUtil.composeCardText( card.isSplitCard() && !isInAltState ? card.getLeftSplitState() : state, gameView, mayView), true));
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
@Override public void run() { @Override public void run() {

View File

@@ -28,6 +28,7 @@ import net.miginfocom.swing.MigLayout;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.game.GameView; import forge.game.GameView;
import forge.gauntlet.GauntletWinLoseController; import forge.gauntlet.GauntletWinLoseController;
import forge.util.Localizer;
import forge.toolbox.FLabel; import forge.toolbox.FLabel;
import forge.toolbox.FSkin; import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinnedPanel; import forge.toolbox.FSkin.SkinnedPanel;
@@ -50,7 +51,7 @@ public class GauntletWinLose extends ControlWinLose {
controller = new GauntletWinLoseController(view0, game0) { controller = new GauntletWinLoseController(view0, game0) {
@Override @Override
protected void showOutcome(boolean isMatchOver, String message1, String message2, FSkinProp icon, List<String> lstEventNames, List<String> lstEventRecords, int len, int num) { protected void showOutcome(boolean isMatchOver, String message1, String message2, FSkinProp icon, List<String> lstEventNames, List<String> lstEventRecords, int len, int num) {
final JLabel lblTitle = new FLabel.Builder().text("Gauntlet Progress") final JLabel lblTitle = new FLabel.Builder().text(Localizer.getInstance().getMessage("lblGauntletProgress"))
.fontAlign(SwingConstants.CENTER).fontSize(18).build(); .fontAlign(SwingConstants.CENTER).fontSize(18).build();
final JPanel pnlResults = new JPanel(); final JPanel pnlResults = new JPanel();

View File

@@ -1,34 +1,33 @@
package forge.view; package forge.view;
import forge.LobbyPlayer;
import forge.deck.Deck;
import forge.deck.DeckGroup;
import forge.deck.io.DeckSerializer;
import forge.game.*;
import forge.game.player.RegisteredPlayer;
import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.properties.ForgeConstants;
import forge.tournament.system.*;
import forge.util.Lang;
import forge.util.TextUtil;
import forge.util.WordUtil;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.time.StopWatch;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import forge.LobbyPlayer;
import forge.deck.DeckGroup;
import forge.game.*;
import forge.properties.ForgeConstants;
import forge.tournament.system.*;
import forge.util.TextUtil;
import forge.util.WordUtil;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.time.StopWatch;
import forge.deck.Deck;
import forge.deck.io.DeckSerializer;
import forge.game.player.RegisteredPlayer;
import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.util.Lang;
public class SimulateMatch { public class SimulateMatch {
public static void simulate(String[] args) { public static void simulate(String[] args) {
FModel.initialize(null, null); FModel.initialize(null, null);
System.out.println("Simulation mode"); System.out.println("Simulation mode");
if(args.length < 4) { if (args.length < 4) {
argumentHelp(); argumentHelp();
return; return;
} }
@@ -49,11 +48,9 @@ public class SimulateMatch {
options = new ArrayList<>(); options = new ArrayList<>();
params.put(a.substring(1), options); params.put(a.substring(1), options);
} } else if (options != null) {
else if (options != null) {
options.add(a); options.add(a);
} } else {
else {
System.err.println("Illegal parameter usage"); System.err.println("Illegal parameter usage");
return; return;
} }
@@ -97,7 +94,7 @@ public class SimulateMatch {
int i = 1; int i = 1;
if (params.containsKey("d")) { if (params.containsKey("d")) {
for(String deck : params.get("d")) { for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, type); Deck d = deckFromCommandLineParameter(deck, type);
if (d == null) { if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start")); System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -130,7 +127,7 @@ public class SimulateMatch {
if (matchSize != 0) { if (matchSize != 0) {
int iGame = 0; int iGame = 0;
while(!mc.isMatchOver()) { while (!mc.isMatchOver()) {
// play games until the match ends // play games until the match ends
simulateSingleMatch(mc, iGame, outputGamelog); simulateSingleMatch(mc, iGame, outputGamelog);
iGame++; iGame++;
@@ -159,38 +156,26 @@ public class SimulateMatch {
} }
public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) { public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
final StopWatch sw = new StopWatch(); final StopWatch sw = new StopWatch();
sw.start(); sw.start();
final Game g1 = mc.createGame(); final Game g1 = mc.createGame();
// will run match in the same thread // will run match in the same thread
long startTime = System.currentTimeMillis();
try { try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() { TimeLimitedCodeBlock.runWithTimeout(() -> {
@Override mc.startGame(g1);
public void run() { sw.stop();
mc.startGame(g1);
sw.stop();
}
}, 120, TimeUnit.SECONDS); }, 120, TimeUnit.SECONDS);
} } catch (TimeoutException e) {
catch (TimeoutException e) {
System.out.println("Stopping slow match as draw"); System.out.println("Stopping slow match as draw");
g1.setGameOver(GameEndReason.Draw); } catch (Exception | StackOverflowError e) {
sw.stop();
}catch (Exception e){
e.printStackTrace(); e.printStackTrace();
g1.setGameOver(GameEndReason.Draw); } finally {
sw.stop();
}catch(StackOverflowError e){
g1.setGameOver(GameEndReason.Draw); g1.setGameOver(GameEndReason.Draw);
sw.stop(); sw.stop();
} }
List<GameLogEntry> log; List<GameLogEntry> log;
if (outputGamelog) { if (outputGamelog) {
log = g1.getGameLog().getLogEntries(null); log = g1.getGameLog().getLogEntries(null);
@@ -198,15 +183,15 @@ public class SimulateMatch {
log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS); log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS);
} }
Collections.reverse(log); Collections.reverse(log);
for(GameLogEntry l : log) { for (GameLogEntry l : log) {
System.out.println(l); System.out.println(l);
} }
// If both players life totals to 0 in a single turn, the game should end in a draw // If both players life totals to 0 in a single turn, the game should end in a draw
if(g1.getOutcome().isDraw()){ if (g1.getOutcome().isDraw()) {
System.out.println(String.format("Game %d ended in a Draw! Took %d ms.", 1+iGame, sw.getTime())); System.out.printf("\nGame Result: Game %d ended in a Draw! Took %d ms.%n", 1 + iGame, sw.getTime());
} else { } else {
System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName())); System.out.printf("\nGame Result: Game %d ended in %d ms. %s has won!\n%n", 1 + iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName());
} }
} }
@@ -219,7 +204,7 @@ public class SimulateMatch {
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
int numPlayers = 0; int numPlayers = 0;
if (params.containsKey("d")) { if (params.containsKey("d")) {
for(String deck : params.get("d")) { for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, rules.getGameType()); Deck d = deckFromCommandLineParameter(deck, rules.getGameType());
if (d == null) { if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start")); System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -239,7 +224,7 @@ public class SimulateMatch {
if (!folder.isDirectory()) { if (!folder.isDirectory()) {
System.out.println("Directory not found - " + foldName); System.out.println("Directory not found - " + foldName);
} else { } else {
for(File deck : folder.listFiles(new FilenameFilter() { for (File deck : folder.listFiles(new FilenameFilter() {
@Override @Override
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
return name.endsWith(".dck"); return name.endsWith(".dck");
@@ -281,16 +266,16 @@ public class SimulateMatch {
System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ", System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ",
String.valueOf(numPlayers), " players over ", String.valueOf(numPlayers), " players over ",
String.valueOf(tourney.getTotalRounds()), " rounds")); String.valueOf(tourney.getTotalRounds()), " rounds"));
while(!tourney.isTournamentOver()) { while (!tourney.isTournamentOver()) {
if (tourney.getActiveRound() != curRound) { if (tourney.getActiveRound() != curRound) {
if (curRound != 0) { if (curRound != 0) {
System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound))); System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound)));
} }
curRound = tourney.getActiveRound(); curRound = tourney.getActiveRound();
System.out.println(); System.out.println();
System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound) ," Pairings:")); System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound), " Pairings:"));
for(TournamentPairing pairing : tourney.getActivePairings()) { for (TournamentPairing pairing : tourney.getActivePairings()) {
System.out.println(pairing.outputHeader()); System.out.println(pairing.outputHeader());
} }
System.out.println(); System.out.println();
@@ -311,10 +296,10 @@ public class SimulateMatch {
int iGame = 0; int iGame = 0;
while (!mc.isMatchOver()) { while (!mc.isMatchOver()) {
// play games until the match ends // play games until the match ends
try{ try {
simulateSingleMatch(mc, iGame, outputGamelog); simulateSingleMatch(mc, iGame, outputGamelog);
iGame++; iGame++;
} catch(Exception e) { } catch (Exception e) {
exceptions++; exceptions++;
System.out.println(e.toString()); System.out.println(e.toString());
if (exceptions > 5) { if (exceptions > 5) {
@@ -349,10 +334,10 @@ public class SimulateMatch {
private static Deck deckFromCommandLineParameter(String deckname, GameType type) { private static Deck deckFromCommandLineParameter(String deckname, GameType type) {
int dotpos = deckname.lastIndexOf('.'); int dotpos = deckname.lastIndexOf('.');
if(dotpos > 0 && dotpos == deckname.length()-4) { if (dotpos > 0 && dotpos == deckname.length() - 4) {
String baseDir = type.equals(GameType.Commander) ? String baseDir = type.equals(GameType.Commander) ?
ForgeConstants.DECK_COMMANDER_DIR : ForgeConstants.DECK_CONSTRUCTED_DIR; ForgeConstants.DECK_COMMANDER_DIR : ForgeConstants.DECK_CONSTRUCTED_DIR;
return DeckSerializer.fromFile(new File(baseDir+deckname)); return DeckSerializer.fromFile(new File(baseDir + deckname));
} }
IStorage<Deck> deckStore = null; IStorage<Deck> deckStore = null;

View File

@@ -462,7 +462,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName()); PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12; int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12;
drawManaCost(g, card.getCurrentState().getManaCost(), ofs); drawManaCost(g, card.getLeftSplitState().getManaCost(), ofs);
drawManaCost(g, card.getAlternateState().getManaCost(), -ofs); drawManaCost(g, card.getAlternateState().getManaCost(), -ofs);
} }
} }

View File

@@ -73,7 +73,7 @@
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-backend-robovm</artifactId> <artifactId>gdx-backend-robovm</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -60,18 +60,18 @@
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-backend-lwjgl</artifactId> <artifactId>gdx-backend-lwjgl</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-platform</artifactId> <artifactId>gdx-platform</artifactId>
<version>1.2.0</version> <version>1.9.11</version>
<classifier>natives-desktop</classifier> <classifier>natives-desktop</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype-platform</artifactId> <artifactId>gdx-freetype-platform</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
<classifier>natives-desktop</classifier> <classifier>natives-desktop</classifier>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli --> <!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->

View File

@@ -63,12 +63,12 @@
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx</artifactId> <artifactId>gdx</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.badlogicgames.gdx</groupId> <groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype</artifactId> <artifactId>gdx-freetype</artifactId>
<version>1.9.10</version> <version>1.9.11</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -64,6 +64,7 @@ public class Forge implements ApplicationListener {
public static float heigtModifier = 0.0f; public static float heigtModifier = 0.0f;
private static boolean isloadingaMatch = false; private static boolean isloadingaMatch = false;
public static boolean showFPS = false; public static boolean showFPS = false;
public static boolean altPlayerLayout = false;
public static boolean enableUIMask = false; public static boolean enableUIMask = false;
public static boolean enablePreloadExtendedArt = false; public static boolean enablePreloadExtendedArt = false;
public static String locale = "en-US"; public static String locale = "en-US";
@@ -123,6 +124,7 @@ public class Forge implements ApplicationListener {
textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING); textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS); showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS);
altPlayerLayout = prefs.getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
enableUIMask = prefs.getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING); enableUIMask = prefs.getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING);
enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART); enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
locale = prefs.getPref(FPref.UI_LANGUAGE); locale = prefs.getPref(FPref.UI_LANGUAGE);

View File

@@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Matrix4;
@@ -37,8 +38,75 @@ public class Graphics {
private int failedClipCount; private int failedClipCount;
private float alphaComposite = 1; private float alphaComposite = 1;
private int transformCount = 0; private int transformCount = 0;
private String sVertex = "uniform mat4 u_projTrans;\n" +
"\n" +
"attribute vec4 a_position;\n" +
"attribute vec2 a_texCoord0;\n" +
"attribute vec4 a_color;\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoord;\n" +
"\n" +
"uniform vec2 u_viewportInverse;\n" +
"\n" +
"void main() {\n" +
" gl_Position = u_projTrans * a_position;\n" +
" v_texCoord = a_texCoord0;\n" +
" v_color = a_color;\n" +
"}";
private String sFragment = "#ifdef GL_ES\n" +
"precision mediump float;\n" +
"precision mediump int;\n" +
"#endif\n" +
"\n" +
"uniform sampler2D u_texture;\n" +
"\n" +
"// The inverse of the viewport dimensions along X and Y\n" +
"uniform vec2 u_viewportInverse;\n" +
"\n" +
"// Color of the outline\n" +
"uniform vec3 u_color;\n" +
"\n" +
"// Thickness of the outline\n" +
"uniform float u_offset;\n" +
"\n" +
"// Step to check for neighbors\n" +
"uniform float u_step;\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoord;\n" +
"\n" +
"#define ALPHA_VALUE_BORDER 0.5\n" +
"\n" +
"void main() {\n" +
" vec2 T = v_texCoord.xy;\n" +
"\n" +
" float alpha = 0.0;\n" +
" bool allin = true;\n" +
" for( float ix = -u_offset; ix < u_offset; ix += u_step )\n" +
" {\n" +
" for( float iy = -u_offset; iy < u_offset; iy += u_step )\n" +
" {\n" +
" float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;\n" +
" allin = allin && newAlpha > ALPHA_VALUE_BORDER;\n" +
" if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)\n" +
" {\n" +
" alpha = newAlpha;\n" +
" }\n" +
" }\n" +
" }\n" +
" if (allin)\n" +
" {\n" +
" alpha = 0.0;\n" +
" }\n" +
"\n" +
" gl_FragColor = vec4(u_color,alpha);\n" +
"}";
private final ShaderProgram shaderOutline = new ShaderProgram(sVertex, sFragment);
public Graphics() { public Graphics() {
ShaderProgram.pedantic = false;
} }
public void begin(float regionWidth0, float regionHeight0) { public void begin(float regionWidth0, float regionHeight0) {
@@ -60,6 +128,7 @@ public class Graphics {
public void dispose() { public void dispose() {
batch.dispose(); batch.dispose();
shapeRenderer.dispose(); shapeRenderer.dispose();
shaderOutline.dispose();
} }
public SpriteBatch getBatch() { public SpriteBatch getBatch() {
@@ -568,7 +637,7 @@ public class Graphics {
public void drawBorderImage(FImage image, Color borderColor, Color tintColor, float x, float y, float w, float h, boolean tint) { public void drawBorderImage(FImage image, Color borderColor, Color tintColor, float x, float y, float w, float h, boolean tint) {
float oldalpha = alphaComposite; float oldalpha = alphaComposite;
if(tint){ if(tint && !tintColor.equals(borderColor)){
drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12); drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12);
fillRoundRect(tintColor, x, y, w, h, (h-w)/12); fillRoundRect(tintColor, x, y, w, h, (h-w)/12);
} else { } else {
@@ -604,6 +673,56 @@ public class Graphics {
public void drawImage(TextureRegion image, float x, float y, float w, float h) { public void drawImage(TextureRegion image, float x, float y, float w, float h) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h); batch.draw(image, adjustX(x), adjustY(y, h), w, h);
} }
public void drawImage(TextureRegion image, TextureRegion glowImageReference, float x, float y, float w, float h, Color glowColor, boolean selected) {
//1st image is the image on top of the shader, 2nd image is for the outline reference for the shader glow...
// if the 1st image don't have transparency in the middle (only on the sides, top and bottom, use the 1st image as outline reference...
if (!selected) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
} else {
batch.end();
shaderOutline.begin();
shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h));
shaderOutline.setUniformf("u_offset", 3f);
shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f));
shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b));
shaderOutline.end();
batch.setShader(shaderOutline);
batch.begin();
//glow
batch.draw(glowImageReference, adjustX(x), adjustY(y, h), w, h);
batch.end();
batch.setShader(null);
batch.begin();
//img
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
}
}
public void drawDeckBox(FImage cardArt, float scale, TextureRegion image, TextureRegion glowImageReference, float x, float y, float w, float h, Color glowColor, boolean selected) {
float yBox = y-(h*0.25f);
if (!selected) {
cardArt.draw(this,x+((w-w*scale)/2), y+((h-h*scale)/3f), w*scale, h*scale/1.85f);
batch.draw(image, adjustX(x), adjustY(yBox, h), w, h);
} else {
batch.end();
shaderOutline.begin();
shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h));
shaderOutline.setUniformf("u_offset", 3f);
shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f));
shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b));
shaderOutline.end();
batch.setShader(shaderOutline);
batch.begin();
//glow
batch.draw(glowImageReference, adjustX(x), adjustY(yBox, h), w, h);
batch.end();
batch.setShader(null);
batch.begin();
//cardart
cardArt.draw(this,x+((w-w*scale)/2), y+((h-h*scale)/3f), w*scale, h*scale/1.85f);
//deckbox
batch.draw(image, adjustX(x), adjustY(yBox, h), w, h);
}
}
public void drawRepeatingImage(Texture image, float x, float y, float w, float h) { public void drawRepeatingImage(Texture image, float x, float y, float w, float h) {
if (startClip(x, y, w, h)) { //only render if clip successful, otherwise it will escape bounds if (startClip(x, y, w, h)) { //only render if clip successful, otherwise it will escape bounds

View File

@@ -23,6 +23,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page; import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import forge.util.TextUtil;
/** /**
* This file is 'borrowed' from gdx-tools in the libgdx source * This file is 'borrowed' from gdx-tools in the libgdx source
@@ -201,7 +202,7 @@ public class BitmapFontWriter {
: " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0"; : " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0";
// INFO LINE // INFO LINE
buf.append(xmlOpen).append("info face=\"").append(info.face == null ? "" : info.face.replaceAll("\"", "'")) buf.append(xmlOpen).append("info face=\"").append(info.face == null ? "" : TextUtil.fastReplace(info.face,"\"", "'"))
.append("\" size=").append(quote(info.size)).append(" bold=").append(quote(info.bold ? 1 : 0)).append(" italic=") .append("\" size=").append(quote(info.size)).append(" bold=").append(quote(info.bold ? 1 : 0)).append(" italic=")
.append(quote(info.italic ? 1 : 0)).append(" charset=\"").append(info.charset == null ? "" : info.charset) .append(quote(info.italic ? 1 : 0)).append(" charset=\"").append(info.charset == null ? "" : info.charset)
.append("\" unicode=").append(quote(info.unicode ? 1 : 0)).append(" stretchH=").append(quote(info.stretchH)) .append("\" unicode=").append(quote(info.unicode ? 1 : 0)).append(" stretchH=").append(quote(info.stretchH))

View File

@@ -30,7 +30,8 @@ public class FSkin {
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>(512); private static final Map<FSkinProp, FSkinImage> images = new HashMap<>(512);
private static final Map<Integer, TextureRegion> avatars = new HashMap<>(150); private static final Map<Integer, TextureRegion> avatars = new HashMap<>(150);
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>(64); private static final Map<Integer, TextureRegion> sleeves = new HashMap<>(64);
private static final Map<Integer, TextureRegion> borders = new HashMap<>(2); private static final Map<Integer, TextureRegion> borders = new HashMap<>();
private static final Map<Integer, TextureRegion> deckbox = new HashMap<>();
private static Array<String> allSkins; private static Array<String> allSkins;
private static FileHandle preferredDir; private static FileHandle preferredDir;
@@ -196,6 +197,7 @@ public class FSkin {
final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE); final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE);
final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE); final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE);
final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE); final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE);
final FileHandle f13 = getDefaultSkinFile(ForgeConstants.SPRITE_DECKBOX_FILE);
try { try {
textures.put(f1.path(), new Texture(f1)); textures.put(f1.path(), new Texture(f1));
@@ -331,10 +333,20 @@ public class FSkin {
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500)); FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
} }
} }
//borders
Texture bordersBW = new Texture(f10); Texture bordersBW = new Texture(f10);
FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 2, 672, 936)); FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 2, 672, 936));
FSkin.borders.put(1, new TextureRegion(bordersBW, 676, 2, 672, 936)); FSkin.borders.put(1, new TextureRegion(bordersBW, 676, 2, 672, 936));
//deckboxes
Texture deckboxes = new Texture(f13, textureFilter);
if (textureFilter)
deckboxes.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
//gold bg
FSkin.deckbox.put(0, new TextureRegion(deckboxes, 2, 2, 488, 680));
//deck box for card art
FSkin.deckbox.put(1, new TextureRegion(deckboxes, 492, 2, 488, 680));
//generic deck box
FSkin.deckbox.put(2, new TextureRegion(deckboxes, 982, 2, 488, 680));
preferredIcons.dispose(); preferredIcons.dispose();
pxDefaultAvatars.dispose(); pxDefaultAvatars.dispose();
@@ -430,5 +442,9 @@ public class FSkin {
return borders; return borders;
} }
public static Map<Integer, TextureRegion> getDeckbox() {
return deckbox;
}
public static boolean isLoaded() { return loaded; } public static boolean isLoaded() { return loaded; }
} }

View File

@@ -288,15 +288,18 @@ public class ImageCache {
public static FImage getBorderImage(String textureString) { public static FImage getBorderImage(String textureString) {
return getBorder(textureString); return getBorder(textureString);
} }
public static Color getTint(CardView c) { public static Color getTint(CardView c, Texture t) {
if (c == null) if (c == null)
return Color.CLEAR; return borderColor(t);
if (c.isFaceDown()) if (c.isFaceDown())
return Color.CLEAR; return Color.valueOf("#171717");
CardView.CardStateView state = c.getCurrentState(); CardView.CardStateView state = c.getCurrentState();
if (state.getColors().isColorless()) //Moonlace -> target spell or permanent becomes colorless. if (state.getColors().isColorless()) { //Moonlace -> target spell or permanent becomes colorless.
if (state.hasDevoid()) //devoid is colorless at all zones so return its corresponding border color...
return borderColor(t);
return Color.valueOf("#A0A6A4"); return Color.valueOf("#A0A6A4");
}
else if (state.getColors().isMonoColor()) { else if (state.getColors().isMonoColor()) {
if (state.getColors().hasBlack()) if (state.getColors().hasBlack())
return Color.valueOf("#48494a"); return Color.valueOf("#48494a");
@@ -311,6 +314,7 @@ public class ImageCache {
} }
else if (state.getColors().isMulticolor()) else if (state.getColors().isMulticolor())
return Color.valueOf("#F9E084"); return Color.valueOf("#F9E084");
return Color.CLEAR;
return borderColor(t);
} }
} }

View File

@@ -190,11 +190,17 @@ public class CardFaceSymbols {
} }
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize) { public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize) {
drawColorSet(g, colorSet, x, y, imageSize, false);
}
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize, boolean vertical) {
final float dx = imageSize; final float dx = imageSize;
for (final ManaCostShard s : colorSet.getOrderedShards()) { for (final ManaCostShard s : colorSet.getOrderedShards()) {
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize); drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
x += dx; if (!vertical)
x += dx;
else
y += dx;
} }
} }

View File

@@ -185,7 +185,7 @@ public class CardImageRenderer {
ManaCost mainManaCost = state.getManaCost(); ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.getAlternateState() != null) { if (card.isSplitCard() && card.getAlternateState() != null) {
//handle rendering both parts of split card //handle rendering both parts of split card
mainManaCost = state.getManaCost(); mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost(); ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING; manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE); CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
@@ -459,7 +459,7 @@ public class CardImageRenderer {
y += cardNameBoxHeight + innerBorderThickness; y += cardNameBoxHeight + innerBorderThickness;
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT); Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawDetailsTextBox(g, state, gameView, canShow, textBoxColors, x, y, w, textBoxHeight); drawDetailsTextBox(g, card.isSplitCard() && !altState ? card.getLeftSplitState() : state, gameView, canShow, textBoxColors, x, y, w, textBoxHeight);
y += textBoxHeight + innerBorderThickness; y += textBoxHeight + innerBorderThickness;
Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT); Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT);
@@ -506,7 +506,7 @@ public class CardImageRenderer {
ManaCost mainManaCost = state.getManaCost(); ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack
//handle rendering both parts of split card //handle rendering both parts of split card
mainManaCost = state.getManaCost(); mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost(); ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING; manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE); CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);

View File

@@ -370,6 +370,7 @@ public class CardRenderer {
ManaCost mainManaCost = card.getCurrentState().getManaCost(); ManaCost mainManaCost = card.getCurrentState().getManaCost();
if (card.isSplitCard()) { if (card.isSplitCard()) {
//handle rendering both parts of split card //handle rendering both parts of split card
mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost(); ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + MANA_COST_PADDING; manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + MANA_COST_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth + MANA_COST_PADDING, y, MANA_SYMBOL_SIZE); CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth + MANA_COST_PADDING, y, MANA_SYMBOL_SIZE);
@@ -522,7 +523,7 @@ public class CardRenderer {
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
else { else {
boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors(); boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors();
g.drawBorderImage(ImageCache.getBorderImage(image.toString(), canshow), ImageCache.borderColor(image), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors g.drawBorderImage(ImageCache.getBorderImage(image.toString(), canshow), ImageCache.borderColor(image), ImageCache.getTint(card, image), x, y, w, h, t); //tint check for changed colors
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea); g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
} }
} else { } else {
@@ -543,9 +544,9 @@ public class CardRenderer {
} }
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) { public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
drawCardWithOverlays(g, card, x, y, w, h, pos, false, false); drawCardWithOverlays(g, card, x, y, w, h, pos, false, false, false);
} }
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview, boolean showAltState) { public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview, boolean showAltState, boolean isChoiceList) {
boolean canShow = MatchController.instance.mayView(card); boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite(); float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting(); boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
@@ -560,7 +561,7 @@ public class CardRenderer {
h -= 2 * padding; h -= 2 * padding;
// TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. // TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards.
final CardStateView details = showAltState ? card.getAlternateState() : card.getCurrentState(); final CardStateView details = showAltState ? card.getAlternateState() : isChoiceList && card.isSplitCard() ? card.getLeftSplitState() : card.getCurrentState();
final boolean isFaceDown = card.isFaceDown(); final boolean isFaceDown = card.isFaceDown();
final DetailColors borderColor = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(details, canShow); // canShow doesn't work here for face down Morphs final DetailColors borderColor = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(details, canShow); // canShow doesn't work here for face down Morphs
Color color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b); Color color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b);
@@ -932,8 +933,8 @@ public class CardRenderer {
dy *= -1; // flip card costs for Aftermath cards dy *= -1; // flip card costs for Aftermath cards
} }
drawManaCost(g, card.getAlternateState().getManaCost(), x - padding, y - dy, w + 2 * padding, h, manaSymbolSize); drawManaCost(g, card.getRightSplitState().getManaCost(), x - padding, y - dy, w + 2 * padding, h, manaSymbolSize);
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y + dy, w + 2 * padding, h, manaSymbolSize); drawManaCost(g, card.getLeftSplitState().getManaCost(), x - padding, y + dy, w + 2 * padding, h, manaSymbolSize);
} }
} }
else { else {

View File

@@ -63,6 +63,7 @@ public class FDeckChooser extends FScreen {
private Callback<Deck> callback; private Callback<Deck> callback;
private NetDeckCategory netDeckCategory; private NetDeckCategory netDeckCategory;
private boolean refreshingDeckType; private boolean refreshingDeckType;
private boolean firstactivation = true;
private final DeckManager lstDecks; private final DeckManager lstDecks;
private final FButton btnNewDeck = new FButton(Localizer.getInstance().getMessage("lblNewDeck")); private final FButton btnNewDeck = new FButton(Localizer.getInstance().getMessage("lblNewDeck"));
@@ -226,6 +227,11 @@ public class FDeckChooser extends FScreen {
@Override @Override
public void onActivate() { public void onActivate() {
//somehow a loaded deck state from startup don't refresh accordingly for imageview so refresh it on first activation
if(firstactivation) {
needRefreshOnActivate = true;
firstactivation = false;
}
if (needRefreshOnActivate) { if (needRefreshOnActivate) {
needRefreshOnActivate = false; needRefreshOnActivate = false;
refreshDecksList(selectedDeckType, true, null); refreshDecksList(selectedDeckType, true, null);

View File

@@ -1,22 +1,28 @@
package forge.itemmanager.views; package forge.itemmanager.views;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.Forge; import forge.Forge;
import forge.Forge.KeyInputAdapter; import forge.Forge.KeyInputAdapter;
import forge.Graphics; import forge.Graphics;
import forge.assets.FImage; import forge.assets.FImage;
import forge.assets.FImageComplex;
import forge.assets.FSkin;
import forge.assets.FSkinColor; import forge.assets.FSkinColor;
import forge.assets.FSkinImage; import forge.assets.FSkinImage;
import forge.assets.FSkinColor.Colors; import forge.assets.FSkinColor.Colors;
import forge.assets.FSkinFont; import forge.assets.FSkinFont;
import forge.assets.ImageCache; import forge.assets.ImageCache;
import forge.card.CardFaceSymbols;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.ColorSet;
import forge.deck.ArchetypeDeckGenerator; import forge.deck.ArchetypeDeckGenerator;
import forge.deck.CardThemedDeckGenerator; import forge.deck.CardThemedDeckGenerator;
import forge.deck.CommanderDeckGenerator; import forge.deck.CommanderDeckGenerator;
import forge.deck.DeckProxy; import forge.deck.DeckProxy;
import forge.deck.FDeckViewer; import forge.deck.FDeckViewer;
import forge.deck.io.DeckPreferences;
import forge.item.InventoryItem; import forge.item.InventoryItem;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.itemmanager.ColumnDef; import forge.itemmanager.ColumnDef;
@@ -27,6 +33,7 @@ import forge.itemmanager.ItemManagerConfig;
import forge.itemmanager.ItemManagerModel; import forge.itemmanager.ItemManagerModel;
import forge.itemmanager.SItemManagerUtil; import forge.itemmanager.SItemManagerUtil;
import forge.itemmanager.filters.ItemFilter; import forge.itemmanager.filters.ItemFilter;
import forge.planarconquest.ConquestCommander;
import forge.toolbox.FCardPanel; import forge.toolbox.FCardPanel;
import forge.toolbox.FComboBox; import forge.toolbox.FComboBox;
import forge.toolbox.FDisplayObject; import forge.toolbox.FDisplayObject;
@@ -36,6 +43,7 @@ import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FLabel; import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane; import forge.toolbox.FScrollPane;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.Utils; import forge.util.Utils;
import java.util.ArrayList; import java.util.ArrayList;
@@ -61,7 +69,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private static final float GROUP_HEADER_GLYPH_WIDTH = Utils.scale(6); private static final float GROUP_HEADER_GLYPH_WIDTH = Utils.scale(6);
private static final float GROUP_HEADER_LINE_THICKNESS = Utils.scale(1); private static final float GROUP_HEADER_LINE_THICKNESS = Utils.scale(1);
private static final float SEL_BORDER_SIZE = Utils.scale(1); private static final float SEL_BORDER_SIZE = Utils.scale(1);
private static final int MIN_COLUMN_COUNT = 1; private static final int MIN_COLUMN_COUNT = Forge.isLandscapeMode() ? 2 : 1;
private static final int MAX_COLUMN_COUNT = 10; private static final int MAX_COLUMN_COUNT = 10;
private final List<Integer> selectedIndices = new ArrayList<>(); private final List<Integer> selectedIndices = new ArrayList<>();
@@ -550,7 +558,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} }
else if (count == 2) { else if (count == 2) {
if (item != null && item.selected) { if (item != null && item.selected) {
itemManager.activateSelectedItems(); if (!(item.getKey() instanceof DeckProxy))
itemManager.activateSelectedItems();
} }
} }
return true; return true;
@@ -854,6 +863,19 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
@Override @Override
public boolean tap(float x, float y, int count) { public boolean tap(float x, float y, int count) {
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
if (item != null) {
if(item.getKey() instanceof DeckProxy) {
DeckProxy dp = (DeckProxy)item.getKey();
if (count >= 2 && !dp.isGeneratedDeck()) {
//double tap to add to favorites or remove....
if (DeckPreferences.getPrefs(dp).getStarCount() > 0)
DeckPreferences.getPrefs(dp).setStarCount(0);
else
DeckPreferences.getPrefs(dp).setStarCount(1);
}
}
}
if (groupBy != null && !items.isEmpty() && y < GROUP_HEADER_HEIGHT) { if (groupBy != null && !items.isEmpty() && y < GROUP_HEADER_HEIGHT) {
isCollapsed = !isCollapsed; isCollapsed = !isCollapsed;
btnExpandCollapseAll.updateIsAllCollapsed(); btnExpandCollapseAll.updateIsAllCollapsed();
@@ -869,7 +891,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop()); ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
if (item != null) { if (item != null) {
if(item.getKey() instanceof CardThemedDeckGenerator || item.getKey() instanceof CommanderDeckGenerator if(item.getKey() instanceof CardThemedDeckGenerator || item.getKey() instanceof CommanderDeckGenerator
|| item.getKey() instanceof ArchetypeDeckGenerator){ || item.getKey() instanceof ArchetypeDeckGenerator || item.getKey() instanceof DeckProxy){
FDeckViewer.show(((DeckProxy)item.getKey()).getDeck()); FDeckViewer.show(((DeckProxy)item.getKey()).getDeck());
return true; return true;
} }
@@ -922,6 +944,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private int index; private int index;
private CardStackPosition pos; private CardStackPosition pos;
private boolean selected; private boolean selected;
private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE;
private ItemInfo(T item0, Group group0) { private ItemInfo(T item0, Group group0) {
item = item0; item = item0;
@@ -954,30 +977,100 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
final float y = getTop() - group.getTop() - getScrollValue(); final float y = getTop() - group.getTop() - getScrollValue();
final float w = getWidth(); final float w = getWidth();
final float h = getHeight(); final float h = getHeight();
Texture dpImg = null;
if (selected) { //if round border is enabled, the select highlight is also rounded.. boolean deckSelectMode = false;
if (Forge.enableUIMask) { if (item instanceof DeckProxy) {
//fillroundrect has rough/aliased corner dpImg = ImageCache.getImage(item);
g.fillRoundRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, deckSelectMode = true;
w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE, (h - w) / 10); }
//drawroundrect has GL_SMOOTH to `smoothen/faux` the aliased corner if (selected) {
g.drawRoundRect(1f, Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, if (!deckSelectMode) {
w + 1.5f * SEL_BORDER_SIZE, h + 1.5f * SEL_BORDER_SIZE, (h - w) / 10); //if round border is enabled, the select highlight is also rounded..
if (Forge.enableUIMask) {
//fillroundrect has rough/aliased corner
g.fillRoundRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE, (h - w) / 10);
//drawroundrect has GL_SMOOTH to `smoothen/faux` the aliased corner
g.drawRoundRect(1f, Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 1.5f * SEL_BORDER_SIZE, h + 1.5f * SEL_BORDER_SIZE, (h - w) / 10);
}
else //default rectangle highlight
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
} }
else //default rectangle highlight
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE,
w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
} }
if (item instanceof PaperCard) { if (item instanceof PaperCard) {
CardRenderer.drawCard(g, (PaperCard)item, x, y, w, h, pos); CardRenderer.drawCard(g, (PaperCard) item, x, y, w, h, pos);
} } else if (item instanceof ConquestCommander) {
else { CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos);
} else if (deckSelectMode) {
DeckProxy dp = ((DeckProxy) item);
ColorSet deckColor = dp.getColor();
float scale = 0.75f;
if (dpImg != null) {//generated decks have missing info...
if (Forge.enableUIMask){
//commander bg
g.drawImage(FSkin.getDeckbox().get(0), FSkin.getDeckbox().get(0), x, y, w, h, Color.GREEN, selected);
TextureRegion tr = ImageCache.croppedBorderImage(dpImg);
g.drawImage(tr, x+(w-w*scale)/2, y+(h-h*scale)/1.5f, w*scale, h*scale);
} else {
if (selected)
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
g.drawImage(dpImg, x, y, w, h);
}
//fake labelname shadow
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING)-1f, (y + PADDING*2)+1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
//labelname
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING*2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
} else {
if (!dp.isGeneratedDeck()){
FImageComplex cardArt = CardRenderer.getCardArt(dp.getHighestCMCCard().getImageKey(false), false, false, false);
//draw the deckbox
if (cardArt == null){
//draw generic box if null or still loading
g.drawImage(FSkin.getDeckbox().get(2), FSkin.getDeckbox().get(2), x, y-(h*0.25f), w, h, Color.GREEN, selected);
} else {
g.drawDeckBox(cardArt, scale, FSkin.getDeckbox().get(1), FSkin.getDeckbox().get(2), x, y, w, h, Color.GREEN, selected);
}
} else {
//generic box
g.drawImage(FSkin.getDeckbox().get(2), FSkin.getDeckbox().get(2), x, y-(h*0.25f), w, h, Color.GREEN, selected);
}
if (deckColor != null) {
//deck color identity
float symbolSize = IMAGE_SIZE;
if (Forge.isLandscapeMode()) {
if (columnCount == 4)
symbolSize = IMAGE_SIZE * 1.5f;
else if (columnCount == 3)
symbolSize = IMAGE_SIZE * 2f;
else if (columnCount == 2)
symbolSize = IMAGE_SIZE * 3f;
else if (columnCount == 1)
symbolSize = IMAGE_SIZE * 4f;
} else {
if (columnCount > 2)
symbolSize = IMAGE_SIZE * (0.5f);
}
//vertical mana icons
CardFaceSymbols.drawColorSet(g, deckColor, x +(w-symbolSize), y+(h/8), symbolSize, true);
if(!dp.isGeneratedDeck()) {
if (Forge.hdbuttons)
g.drawImage(DeckPreferences.getPrefs(dp).getStarCount() > 0 ? FSkinImage.HDSTAR_FILLED : FSkinImage.HDSTAR_OUTLINE, x, y, symbolSize, symbolSize);
else
g.drawImage(DeckPreferences.getPrefs(dp).getStarCount() > 0 ? FSkinImage.STAR_FILLED : FSkinImage.STAR_OUTLINE, x, y, symbolSize, symbolSize);
}
}
String deckname = TextUtil.fastReplace(item.getName(),"] #", "]\n#");
//deckname fakeshadow
g.drawText(deckname, GROUP_HEADER_FONT, Color.BLACK, (x + PADDING)-1f, (y + (h/10) + PADDING)+1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, true);
//deck name
g.drawText(deckname, GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + (h/10) + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, true);
}
} else {
Texture img = ImageCache.getImage(item); Texture img = ImageCache.getImage(item);
if (img != null) { if (img != null) {
g.drawImage(img, x, y, w, h); g.drawImage(img, x, y, w, h);
} } else {
else {
g.fillRect(Color.BLACK, x, y, w, h); g.fillRect(Color.BLACK, x, y, w, h);
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
} }

View File

@@ -628,7 +628,8 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
} }
if (Forge.gameInProgress) { if (Forge.gameInProgress) {
/*preload deck to cache*/ /*preload deck to cache*/
ImageCache.preloadCache(decks[i]); if(slot.getType() == LobbySlotType.LOCAL)
ImageCache.preloadCache(decks[i]);
} }
Gdx.graphics.requestRendering(); Gdx.graphics.requestRendering();
} }

View File

@@ -358,10 +358,10 @@ public class MatchScreen extends FScreen {
if(gameMenu!=null) { if(gameMenu!=null) {
if(gameMenu.getChildCount()>3){ if(gameMenu.getChildCount()>3){
if(viewWinLose == null) { if(viewWinLose == null) {
gameMenu.getChildAt(0).setEnabled(true); gameMenu.getChildAt(0).setEnabled(!game.isMulligan());
gameMenu.getChildAt(1).setEnabled(true); gameMenu.getChildAt(1).setEnabled(!game.isMulligan());
gameMenu.getChildAt(2).setEnabled(true); gameMenu.getChildAt(2).setEnabled(!game.isMulligan());
gameMenu.getChildAt(3).setEnabled(true); gameMenu.getChildAt(3).setEnabled(!game.isMulligan());
gameMenu.getChildAt(4).setEnabled(false); gameMenu.getChildAt(4).setEnabled(false);
} else { } else {
gameMenu.getChildAt(0).setEnabled(false); gameMenu.getChildAt(0).setEnabled(false);

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import forge.Forge;
import forge.Graphics; import forge.Graphics;
import forge.assets.FImage; import forge.assets.FImage;
import forge.assets.FSkinFont; import forge.assets.FSkinFont;
@@ -69,6 +70,9 @@ public class VAvatar extends FDisplayObject {
float h = getHeight(); float h = getHeight();
g.drawImage(image, 0, 0, w, h); g.drawImage(image, 0, 0, w, h);
if (Forge.altPlayerLayout && Forge.isLandscapeMode())
return;
//display XP in lower right corner of avatar //display XP in lower right corner of avatar
int xp = player.getCounters(CounterEnumType.EXPERIENCE); int xp = player.getCounters(CounterEnumType.EXPERIENCE);
if (xp > 0) { if (xp > 0) {

View File

@@ -114,6 +114,7 @@ public class VField extends FContainer {
if (!c.hasCardAttachments() && if (!c.hasCardAttachments() &&
cardName.equals(c.getCurrentState().getName()) && cardName.equals(c.getCurrentState().getName()) &&
card.hasSameCounters(c) && card.hasSameCounters(c) &&
card.hasSamePT(c) && //don't stack token with different PT
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) && card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens
card.isSick() == c.isSick() && //don't stack sick tokens on non sick card.isSick() == c.isSick() && //don't stack sick tokens on non sick

View File

@@ -50,6 +50,12 @@ public class VGameMenu extends FDropDownMenu {
addItem(new FMenuItem(localizer.getMessage("lblDeckList"), FSkinImage.DECKLIST, new FEventHandler() { addItem(new FMenuItem(localizer.getMessage("lblDeckList"), FSkinImage.DECKLIST, new FEventHandler() {
@Override @Override
public void handleEvent(FEvent e) { public void handleEvent(FEvent e) {
//pause game when spectating AI Match
if (!MatchController.instance.hasLocalPlayers()) {
if(!MatchController.instance.isGamePaused())
MatchController.instance.pauseMatch();
}
final Player player = MatchController.getHostedMatch().getGame().getPhaseHandler().getPlayerTurn(); final Player player = MatchController.getHostedMatch().getGame().getPhaseHandler().getPlayerTurn();
if (player != null) { if (player != null) {
final Deck deck = player.getRegisteredPlayer().getDeck(); final Deck deck = player.getRegisteredPlayer().getDeck();
@@ -89,6 +95,11 @@ public class VGameMenu extends FDropDownMenu {
addItem(new FMenuItem(localizer.getMessage("lblSettings"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, new FEventHandler() { addItem(new FMenuItem(localizer.getMessage("lblSettings"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, new FEventHandler() {
@Override @Override
public void handleEvent(FEvent e) { public void handleEvent(FEvent e) {
//pause game when spectating AI Match
if (!MatchController.instance.hasLocalPlayers()) {
if(!MatchController.instance.isGamePaused())
MatchController.instance.pauseMatch();
}
SettingsScreen.show(false); SettingsScreen.show(false);
} }
})); }));

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import forge.Forge; import forge.Forge;
@@ -30,8 +31,10 @@ import forge.util.Utils;
public class VPlayerPanel extends FContainer { public class VPlayerPanel extends FContainer {
private static final FSkinFont LIFE_FONT = FSkinFont.get(18); private static final FSkinFont LIFE_FONT = FSkinFont.get(18);
private static final FSkinFont INFO_FONT = FSkinFont.get(12); private static final FSkinFont INFO_FONT = FSkinFont.get(12);
private static final FSkinFont INFO2_FONT = FSkinFont.get(14);
private static final FSkinColor INFO_FORE_COLOR = FSkinColor.get(Colors.CLR_TEXT); private static final FSkinColor INFO_FORE_COLOR = FSkinColor.get(Colors.CLR_TEXT);
private static final FSkinColor DISPLAY_AREA_BACK_COLOR = FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f); private static final FSkinColor DISPLAY_AREA_BACK_COLOR = FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f);
private static final FSkinColor DELIRIUM_HIGHLIGHT = FSkinColor.get(Colors.CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f);
private static final float INFO_TAB_PADDING_X = Utils.scale(2); private static final float INFO_TAB_PADDING_X = Utils.scale(2);
private static final float INFO_TAB_PADDING_Y = Utils.scale(2); private static final float INFO_TAB_PADDING_Y = Utils.scale(2);
@@ -48,6 +51,7 @@ public class VPlayerPanel extends FContainer {
private float avatarHeight = VAvatar.HEIGHT; private float avatarHeight = VAvatar.HEIGHT;
private float displayAreaHeightFactor = 1.0f; private float displayAreaHeightFactor = 1.0f;
private boolean forMultiPlayer = false; private boolean forMultiPlayer = false;
public int adjustHeight = 1;
public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) { public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) {
player = player0; player = player0;
@@ -284,8 +288,14 @@ public class VPlayerPanel extends FContainer {
float y = 0; float y = 0;
avatar.setPosition(x, y); avatar.setPosition(x, y);
y += avatar.getHeight(); y += avatar.getHeight();
lblLife.setBounds(x, y, avatar.getWidth(), LIFE_FONT.getLineHeight());
y += lblLife.getHeight(); lblLife.setBounds(x, Forge.altPlayerLayout ? 0 : y, avatar.getWidth(), Forge.altPlayerLayout ? INFO_FONT.getLineHeight() : LIFE_FONT.getLineHeight());
if (Forge.altPlayerLayout) {
if (adjustHeight > 2)
y += INFO_FONT.getLineHeight()/2;
} else
y += lblLife.getHeight();
float infoTabWidth = avatar.getWidth(); float infoTabWidth = avatar.getWidth();
float infoTabHeight = (height - y) / tabs.size(); float infoTabHeight = (height - y) / tabs.size();
for (InfoTab tab : tabs) { for (InfoTab tab : tabs) {
@@ -361,6 +371,7 @@ public class VPlayerPanel extends FContainer {
private int life = player.getLife(); private int life = player.getLife();
private int poisonCounters = player.getCounters(CounterEnumType.POISON); private int poisonCounters = player.getCounters(CounterEnumType.POISON);
private int energyCounters = player.getCounters(CounterEnumType.ENERGY); private int energyCounters = player.getCounters(CounterEnumType.ENERGY);
private int experienceCounters = player.getCounters(CounterEnumType.EXPERIENCE);
private String lifeStr = String.valueOf(life); private String lifeStr = String.valueOf(life);
private LifeLabel() { private LifeLabel() {
@@ -388,6 +399,7 @@ public class VPlayerPanel extends FContainer {
} }
energyCounters = player.getCounters(CounterEnumType.ENERGY); energyCounters = player.getCounters(CounterEnumType.ENERGY);
experienceCounters = player.getCounters(CounterEnumType.EXPERIENCE);
//when gui player loses life, vibrate device for a length of time based on amount of life lost //when gui player loses life, vibrate device for a length of time based on amount of life lost
if (vibrateDuration > 0 && MatchController.instance.isLocalPlayer(player) && if (vibrateDuration > 0 && MatchController.instance.isLocalPlayer(player) &&
@@ -405,22 +417,50 @@ public class VPlayerPanel extends FContainer {
@Override @Override
public void draw(Graphics g) { public void draw(Graphics g) {
if (poisonCounters == 0 && energyCounters == 0) { adjustHeight = 1;
g.drawText(lifeStr, LIFE_FONT, INFO_FORE_COLOR, 0, 0, getWidth(), getHeight(), false, Align.center, true); if(Forge.altPlayerLayout && Forge.isLandscapeMode()) {
} if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0) {
else { g.drawOutlinedText(lifeStr, INFO2_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, 0, 0, getWidth(), getHeight(), false, Align.left, false);
float halfHeight = getHeight() / 2; } else {
float textStart = halfHeight + Utils.scale(1); float halfHeight = getHeight() / 2;
float textWidth = getWidth() - textStart; float textStart = halfHeight + Utils.scale(1);
g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight); float textWidth = getWidth() - textStart;
g.drawText(lifeStr, INFO_FONT, INFO_FORE_COLOR, textStart, 0, textWidth, halfHeight, false, Align.center, true); int mod = 1;
if (poisonCounters > 0) { //prioritize showing poison counters over energy counters g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight);
g.drawImage(FSkinImage.POISON, 0, halfHeight, halfHeight, halfHeight); g.drawOutlinedText(lifeStr, INFO_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, textStart, 0, textWidth, halfHeight, false, Align.left, false);
g.drawText(String.valueOf(poisonCounters), INFO_FONT, INFO_FORE_COLOR, textStart, halfHeight, textWidth, halfHeight, false, Align.center, true); if (poisonCounters > 0) {
g.drawImage(FSkinImage.POISON, 0, halfHeight+2, halfHeight, halfHeight);
g.drawOutlinedText(String.valueOf(poisonCounters), INFO_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, textStart, halfHeight+2, textWidth, halfHeight, false, Align.left, false);
mod+=1;
}
if (energyCounters > 0) {
g.drawImage(FSkinImage.ENERGY, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
g.drawOutlinedText(String.valueOf(energyCounters), INFO_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
mod+=1;
}
if (experienceCounters > 0) {
g.drawImage(FSkinImage.COMMANDER, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
g.drawOutlinedText(String.valueOf(experienceCounters), INFO_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
mod+=1;
}
adjustHeight = (mod > 2) && (avatar.getHeight() < halfHeight*mod)? mod : 1;
} }
else { } else {
g.drawImage(FSkinImage.ENERGY, 0, halfHeight, halfHeight, halfHeight); if (poisonCounters == 0 && energyCounters == 0) {
g.drawText(String.valueOf(energyCounters), INFO_FONT, INFO_FORE_COLOR, textStart, halfHeight, textWidth, halfHeight, false, Align.center, true); g.drawText(lifeStr, LIFE_FONT, INFO_FORE_COLOR, 0, 0, getWidth(), getHeight(), false, Align.center, true);
} else {
float halfHeight = getHeight() / 2;
float textStart = halfHeight + Utils.scale(1);
float textWidth = getWidth() - textStart;
g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight);
g.drawText(lifeStr, INFO_FONT, INFO_FORE_COLOR, textStart, 0, textWidth, halfHeight, false, Align.center, true);
if (poisonCounters > 0) { //prioritize showing poison counters over energy counters
g.drawImage(FSkinImage.POISON, 0, halfHeight, halfHeight, halfHeight);
g.drawText(String.valueOf(poisonCounters), INFO_FONT, INFO_FORE_COLOR, textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
} else {
g.drawImage(FSkinImage.ENERGY, 0, halfHeight, halfHeight, halfHeight);
g.drawText(String.valueOf(energyCounters), INFO_FONT, INFO_FORE_COLOR, textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
}
} }
} }
} }
@@ -473,19 +513,22 @@ public class VPlayerPanel extends FContainer {
yAcross = y; yAcross = y;
y--; y--;
h++; h++;
} } else {
else {
h -= INFO_TAB_PADDING_Y; h -= INFO_TAB_PADDING_Y;
yAcross = h; yAcross = h;
y--; y--;
h += 2; h += 2;
} }
g.fillRect(DISPLAY_AREA_BACK_COLOR, 0, isFlipped() ? INFO_TAB_PADDING_Y : 0, w, getHeight() - INFO_TAB_PADDING_Y); //change the graveyard tab selection color to active phase color to indicate the player has delirium
if ((icon == FSkinImage.HDGRAVEYARD || icon == FSkinImage.GRAVEYARD) && player.hasDelirium()) {
g.fillRect(DELIRIUM_HIGHLIGHT, 0 ,isFlipped() ? INFO_TAB_PADDING_Y : 0, w, getHeight() - INFO_TAB_PADDING_Y);
} else {
g.fillRect(DISPLAY_AREA_BACK_COLOR, 0, isFlipped() ? INFO_TAB_PADDING_Y : 0, w, getHeight() - INFO_TAB_PADDING_Y);
}
if (!Forge.isLandscapeMode()) { if (!Forge.isLandscapeMode()) {
if (isFlipped()) { //use clip to ensure all corners connect if (isFlipped()) { //use clip to ensure all corners connect
g.startClip(-1, y, w + 2, h); g.startClip(-1, y, w + 2, h);
} } else {
else {
g.startClip(-1, y, w + 2, yAcross - y); g.startClip(-1, y, w + 2, yAcross - y);
} }
if (forMultiPlayer) { if (forMultiPlayer) {
@@ -533,8 +576,7 @@ public class VPlayerPanel extends FContainer {
if (lblLife.getRotate180()) { if (lblLife.getRotate180()) {
g.endTransform(); g.endTransform();
} }
} } else { //show image above text if taller than wide
else { //show image above text if taller than wide
if (lblLife.getRotate180()) { if (lblLife.getRotate180()) {
g.startRotateTransform(getWidth() / 2, getHeight() / 2, 180); g.startRotateTransform(getWidth() / 2, getHeight() / 2, 180);
} }

View File

@@ -364,6 +364,7 @@ public class VStack extends FDropDown {
float y = 0; float y = 0;
float w = getWidth(); float w = getWidth();
float h = preferredHeight; float h = preferredHeight;
CardView sourceCard = stackInstance.getSourceCard();
boolean needAlpha = (activeStackInstance != stackInstance); boolean needAlpha = (activeStackInstance != stackInstance);
if (needAlpha) { //use alpha for non-active items on stack if (needAlpha) { //use alpha for non-active items on stack
@@ -382,16 +383,17 @@ public class VStack extends FDropDown {
x += PADDING; x += PADDING;
y += PADDING; y += PADDING;
CardRenderer.drawCardWithOverlays(g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top, true, false); CardRenderer.drawCardWithOverlays(g, sourceCard, x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top, true, false, false);
x += CARD_WIDTH + PADDING; x += CARD_WIDTH + PADDING;
w -= x + PADDING - BORDER_THICKNESS; w -= x + PADDING - BORDER_THICKNESS;
h -= y + PADDING - BORDER_THICKNESS; h -= y + PADDING - BORDER_THICKNESS;
String name = stackInstance.getSourceCard().getName(); String name = sourceCard.getName();
int index = text.indexOf(name); int index = text.indexOf(name);
String newtext = ""; String newtext = "";
String cId = "(" + stackInstance.getSourceCard().getId() + ")"; String cId = "(" + sourceCard.getId() + ")";
String optionalCostString = !stackInstance.getOptionalCostString().equals("") ? " ("+ stackInstance.getOptionalCostString() + ")" : "";
if (index == -1) { if (index == -1) {
newtext = TextUtil.fastReplace(TextUtil.fastReplace(text.trim(),"--","-"),"- -","-"); newtext = TextUtil.fastReplace(TextUtil.fastReplace(text.trim(),"--","-"),"- -","-");
@@ -405,7 +407,7 @@ public class VStack extends FDropDown {
newtext = TextUtil.fastReplace(trimSecond, " "+cId, name); newtext = TextUtil.fastReplace(trimSecond, " "+cId, name);
if(newtext.equals("\n"+name)) if(newtext.equals("\n"+name))
textRenderer.drawText(g, name + " " + cId, FONT, foreColor, x, y, w, h, y, h, true, Align.left, true); textRenderer.drawText(g, name + " " + cId + optionalCostString, FONT, foreColor, x, y, w, h, y, h, true, Align.left, true);
else { else {
newtext = TextUtil.fastReplace(TextUtil.fastReplace(newtext,name+" -","-"), "\n ", "\n"); newtext = TextUtil.fastReplace(TextUtil.fastReplace(newtext,name+" -","-"), "\n ", "\n");
newtext = "\n"+ TextUtil.fastReplace(newtext.trim(),"--","-"); newtext = "\n"+ TextUtil.fastReplace(newtext.trim(),"--","-");

View File

@@ -21,6 +21,7 @@ import forge.FThreads;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.game.GameView; import forge.game.GameView;
import forge.gauntlet.GauntletWinLoseController; import forge.gauntlet.GauntletWinLoseController;
import forge.util.Localizer;
import forge.util.gui.SOptionPane; import forge.util.gui.SOptionPane;
import java.util.List; import java.util.List;
@@ -70,7 +71,7 @@ public class GauntletWinLose extends ControlWinLose {
} }
} }
SOptionPane.showMessageDialog(sb.toString(), "Gauntlet Progress", icon); SOptionPane.showMessageDialog(sb.toString(), Localizer.getInstance().getMessage("lblGauntletProgress"), icon);
} }
}); });
} }

View File

@@ -101,17 +101,22 @@ public class ConquestRewardDialog extends FScrollPane {
float startX = x; float startX = x;
int cardCount = cardRevealers.size(); int cardCount = cardRevealers.size();
cardRevealers.get(0).setBounds(x, y, cardWidth, cardHeight); try {
for (int i = 1; i < cardCount; i++) { cardRevealers.get(0).setBounds(x, y, cardWidth, cardHeight);
if (i % columnCount == 0) { for (int i = 1; i < cardCount; i++) {
x = startX; if (i % columnCount == 0) {
y += cardHeight + PADDING; x = startX;
y += cardHeight + PADDING;
}
else {
x += cardWidth + PADDING;
}
cardRevealers.get(i).setBounds(x, y, cardWidth, cardHeight);
} }
else { } catch (Exception ex) {
x += cardWidth + PADDING; System.err.println(ex.getMessage());
}
cardRevealers.get(i).setBounds(x, y, cardWidth, cardHeight);
} }
return new ScrollBounds(visibleWidth, y + cardHeight + PADDING); return new ScrollBounds(visibleWidth, y + cardHeight + PADDING);
} }

View File

@@ -190,6 +190,16 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("cbEscapeEndsTurn"), localizer.getMessage("cbEscapeEndsTurn"),
localizer.getMessage("nlEscapeEndsTurn")), localizer.getMessage("nlEscapeEndsTurn")),
1); 1);
lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_PLAYERINFOLAYOUT,
localizer.getMessage("lblAltLifeDisplay"),
localizer.getMessage("nlAltLifeDisplay")){
@Override
public void select() {
super.select();
//update
Forge.altPlayerLayout = FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
}
},1);
//Random Deck Generation //Random Deck Generation
lstSettings.addItem(new BooleanSetting(FPref.DECKGEN_NOSMALL, lstSettings.addItem(new BooleanSetting(FPref.DECKGEN_NOSMALL,

View File

@@ -13,10 +13,13 @@ import forge.assets.FSkinProp;
import forge.assets.IHasSkinProp; import forge.assets.IHasSkinProp;
import forge.assets.TextRenderer; import forge.assets.TextRenderer;
import forge.assets.FSkinColor.Colors; import forge.assets.FSkinColor.Colors;
import forge.card.CardFaceSymbols;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom.ActivateHandler; import forge.card.CardZoom.ActivateHandler;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.card.IHasCardView; import forge.game.card.IHasCardView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
@@ -29,8 +32,11 @@ import forge.itemmanager.filters.ItemFilter;
import forge.screens.match.MatchController; import forge.screens.match.MatchController;
import forge.screens.match.views.VAvatar; import forge.screens.match.views.VAvatar;
import forge.screens.match.views.VStack; import forge.screens.match.views.VStack;
import forge.util.TextUtil;
import forge.util.Utils; import forge.util.Utils;
import static forge.card.CardRenderer.MANA_SYMBOL_SIZE;
public class FChoiceList<T> extends FList<T> implements ActivateHandler { public class FChoiceList<T> extends FList<T> implements ActivateHandler {
public static final FSkinColor ITEM_COLOR = FSkinColor.get(Colors.CLR_ZEBRA); public static final FSkinColor ITEM_COLOR = FSkinColor.get(Colors.CLR_ZEBRA);
public static final FSkinColor ALT_ITEM_COLOR = ITEM_COLOR.getContrastColor(-20); public static final FSkinColor ALT_ITEM_COLOR = ITEM_COLOR.getContrastColor(-20);
@@ -337,7 +343,17 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
@Override @Override
public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) { public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) {
g.drawText(getChoiceText(value), font, foreColor, x, y, w, h, allowDefaultItemWrap(), Align.left, true); //update manacost text to draw symbols instead
if (value.toString().contains(" {")){
String[] values = value.toString().split(" ");
String cost = TextUtil.fastReplace(values[1],"}{", " ");
cost = TextUtil.fastReplace(TextUtil.fastReplace(cost,"{", ""),"}", "");
ManaCost manaCost = new ManaCost(new ManaCostParser(cost));
CardFaceSymbols.drawManaCost(g, manaCost, x + font.getBounds(values[0]+" ").width, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
g.drawText(values[0], font, foreColor, x, y, w, h, allowDefaultItemWrap(), Align.left, true);
} else {
g.drawText(getChoiceText(value), font, foreColor, x, y, w, h, allowDefaultItemWrap(), Align.left, true);
}
} }
} }
protected class NumberRenderer extends DefaultItemRenderer { protected class NumberRenderer extends DefaultItemRenderer {
@@ -500,7 +516,7 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) { public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) {
CardView cv = ((IHasCardView)value).getCardView(); CardView cv = ((IHasCardView)value).getCardView();
boolean showAlternate = showAlternate(cv, value.toString()); boolean showAlternate = showAlternate(cv, value.toString());
CardRenderer.drawCardWithOverlays(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate); CardRenderer.drawCardWithOverlays(g, cv, x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top, false, showAlternate, true);
float dx = VStack.CARD_WIDTH + FList.PADDING; float dx = VStack.CARD_WIDTH + FList.PADDING;
x += dx; x += dx;

View File

@@ -16,6 +16,7 @@ import forge.screens.match.views.VPrompt;
import forge.toolbox.FButton.Corner; import forge.toolbox.FButton.Corner;
import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FEvent.FEventHandler;
import forge.util.PhysicsObject; import forge.util.PhysicsObject;
import forge.util.TextUtil;
import forge.util.Utils; import forge.util.Utils;
public abstract class FDialog extends FOverlay { public abstract class FDialog extends FOverlay {
@@ -48,7 +49,7 @@ public abstract class FDialog extends FOverlay {
buttonCount = buttonCount0; buttonCount = buttonCount0;
prompt = add(new VPrompt("", "", null, null)); prompt = add(new VPrompt("", "", null, null));
if (buttonCount < 3) { if (buttonCount < 3) {
title0 = title0.replaceAll(" - ", "\n"); //breakup into lines title0 = TextUtil.fastReplace(title0," - ", "\n"); //breakup into lines
btnMiddle = null; btnMiddle = null;
prompt.setMessage(title0); //only put title in message if no third button prompt.setMessage(title0); //only put title in message if no third button
} }

View File

@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=500
PREDICT_SPELLS_FOR_MAIN2=true PREDICT_SPELLS_FOR_MAIN2=true
RESERVE_MANA_FOR_MAIN2_CHANCE=100 RESERVE_MANA_FOR_MAIN2_CHANCE=100
# If enabled, the AI will protect its permanents against curse auras with Hexproof, Shroud, and Protection
ACTIVELY_PROTECT_VS_CURSE_AURAS=true
# If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true

View File

@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=400
PREDICT_SPELLS_FOR_MAIN2=true PREDICT_SPELLS_FOR_MAIN2=true
RESERVE_MANA_FOR_MAIN2_CHANCE=100 RESERVE_MANA_FOR_MAIN2_CHANCE=100
# If enabled, the AI will protect its permanents against curse auras with Hexproof, Shroud, and Protection
ACTIVELY_PROTECT_VS_CURSE_AURAS=false
# If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true

View File

@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=350
PREDICT_SPELLS_FOR_MAIN2=true PREDICT_SPELLS_FOR_MAIN2=true
RESERVE_MANA_FOR_MAIN2_CHANCE=100 RESERVE_MANA_FOR_MAIN2_CHANCE=100
# If enabled, the AI will protect its permanents against curse auras with Hexproof, Shroud, and Protection
ACTIVELY_PROTECT_VS_CURSE_AURAS=true
# If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true

View File

@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=300
PREDICT_SPELLS_FOR_MAIN2=true PREDICT_SPELLS_FOR_MAIN2=true
RESERVE_MANA_FOR_MAIN2_CHANCE=100 RESERVE_MANA_FOR_MAIN2_CHANCE=100
# If enabled, the AI will protect its permanents against curse auras with Hexproof, Shroud, and Protection
ACTIVELY_PROTECT_VS_CURSE_AURAS=false
# If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true

View File

@@ -0,0 +1,10 @@
# Order, Tag, Label
1, DEFAULT, All sets (default)
11, MODERN, Modern legal expansions
12, PIONEER, Pioneer legal expansions
13, STANDARD, Standard legal expansions
21, CORE_SET, Core Sets
22, MASTERS_SET, Masters Sets (paper only)
30, MIRRODIN, Mirrodin (Plane)
30, RAVNICA, Ravnica (Plane)
40, GRAVEYARD_MATTERS, Graveyard matters

View File

@@ -4,7 +4,7 @@ Types:Enchantment
S:Mode$ Continuous | Affected$ Land.YouCtrl | AddType$ Plains | RemoveLandTypes$ True | RemoveIntrinsicAbilities$ True | Description$ Lands you control are Plains. S:Mode$ Continuous | Affected$ Land.YouCtrl | AddType$ Plains | RemoveLandTypes$ True | RemoveIntrinsicAbilities$ True | Description$ Lands you control are Plains.
S:Mode$ Continuous | Affected$ Card.YouOwn+nonLand | SetColor$ White | AffectedZone$ Hand,Library,Graveyard,Exile,Command | Description$ Nonland permanents you control are white. The same is true for spells you control and nonland cards you own that aren't on the battlefield. S:Mode$ Continuous | Affected$ Card.YouOwn+nonLand | SetColor$ White | AffectedZone$ Hand,Library,Graveyard,Exile,Command | Description$ Nonland permanents you control are white. The same is true for spells you control and nonland cards you own that aren't on the battlefield.
S:Mode$ Continuous | Affected$ Card.YouCtrl+nonLand | SetColor$ White | AffectedZone$ Battlefield,Stack S:Mode$ Continuous | Affected$ Card.YouCtrl+nonLand | SetColor$ White | AffectedZone$ Battlefield,Stack
S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ All | Description$ You may spend white mana as though it were mana of any color. S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ Color | Description$ You may spend white mana as though it were mana of any color.
S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Restrictive | BlueConversion$ Colorless | BlackConversion$ Colorless | RedConversion$ Colorless | GreenConversion$ Colorless | ColorlessConversion$ Colorless | Description$ You may spend other mana only as though it were colorless mana. S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Restrictive | BlueConversion$ Colorless | BlackConversion$ Colorless | RedConversion$ Colorless | GreenConversion$ Colorless | ColorlessConversion$ Colorless | Description$ You may spend other mana only as though it were colorless mana.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
AI:RemoveDeck:Random AI:RemoveDeck:Random

View File

@@ -1,7 +1,7 @@
Name:Chromatic Orrery Name:Chromatic Orrery
ManaCost:7 ManaCost:7
Types:Legendary Artifact Types:Legendary Artifact
S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ All | BlueConversion$ All | BlackConversion$ All | RedConversion$ All | GreenConversion$ All | ColorlessConversion$ All | Description$ You may spend mana as though it were mana of any color. S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ Color | BlueConversion$ Color | BlackConversion$ Color | RedConversion$ Color | GreenConversion$ Color | ColorlessConversion$ Color | Description$ You may spend mana as though it were mana of any color.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 5 | SpellDescription$ Add {C}{C}{C}{C}{C}. A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 5 | SpellDescription$ Add {C}{C}{C}{C}{C}.
A:AB$ Draw | Cost$ 5 T | NumCards$ X | References$ X | SpellDescription$ Draw a card for each color among permanents you control. A:AB$ Draw | Cost$ 5 T | NumCards$ X | References$ X | SpellDescription$ Draw a card for each color among permanents you control.

View File

@@ -4,11 +4,9 @@ Types:Creature Djinn
PT:4/4 PT:4/4
K:Flying K:Flying
K:etbCounter:WISH:3 K:etbCounter:WISH:3
A:AB$ Dig | Cost$ 2 U U SubCounter<1/WISH> | DigNum$ 1 | Reveal$ True | NoMove$ True | RememberRevealed$ True | SubAbility$ DBPlayIT | SpellDescription$ Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it. A:AB$ Dig | Cost$ 2 U U SubCounter<1/WISH> | DigNum$ 1 | Reveal$ True | NoMove$ True | ImprintRevealed$ True | SubAbility$ DBPlay | StackDescription$ {p:You} reveals the top card of their library. {p:You} may play that card without paying its mana cost or exile it. | SpellDescription$ Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it.
SVar:DBPlayIT:DB$ Play | Defined$ Remembered | Controller$ You | WithoutManaCost$ True | Optional$ True | RememberPlayed$ True | SubAbility$ DBExileIfNotPlayed SVar:DBPlay:DB$ Play | Defined$ Imprinted | Controller$ You | WithoutManaCost$ True | Optional$ True | RememberPlayed$ True | SubAbility$ DBExileIfNotPlayed | StackDescription$ None
SVar:DBExileIfNotPlayed:DB$ ChangeZone | Origin$ Library | Destination$ Exile | Defined$ Remembered | DefinedPlayer$ You | ConditionCheckSVar$ DjinnX | ConditionSVarCompare$ EQ1 | SubAbility$ DBDjinnCleanup | References$ DjinnX SVar:DBExileIfNotPlayed:DB$ ChangeZone | Origin$ Library | Destination$ Exile | Defined$ Imprinted | DefinedPlayer$ You | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBDjinnCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:DjinnX:Remembered$Valid Card.IsRemembered
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/djinn_of_wishes.jpg
Oracle:Flying\nDjinn of Wishes enters the battlefield with three wish counters on it.\n{2}{U}{U}, Remove a wish counter from Djinn of Wishes: Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it. Oracle:Flying\nDjinn of Wishes enters the battlefield with three wish counters on it.\n{2}{U}{U}, Remove a wish counter from Djinn of Wishes: Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it.

View File

@@ -5,8 +5,7 @@ A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ FDRep | StaticAbilities$ FDManaCo
SVar:DBDraw:DB$ Draw | NumCards$ 1 SVar:DBDraw:DB$ Draw | NumCards$ 1
SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | NoTapCheck$ True | ManaReplacement$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead. SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | NoTapCheck$ True | ManaReplacement$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead.
SVar:ProduceW:R->W & B->W & U->W & G->W SVar:ProduceW:R->W & B->W & U->W & G->W
SVar:FDManaConvertion:Mode$ Continuous | EffectZone$ Command | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ All | Description$ You may spend white mana as though it were mana of any color. SVar:FDManaConvertion:Mode$ Continuous | EffectZone$ Command | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ Color | Description$ You may spend white mana as though it were mana of any color.
AI:RemoveDeck:All AI:RemoveDeck:All
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/false_dawn.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/false_dawn.jpg
Oracle:Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color.\nDraw a card. Oracle:Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color.\nDraw a card.

View File

@@ -2,7 +2,7 @@ Name:Inscription of Abundance
ManaCost:1 G ManaCost:1 G
Types:Instant Types:Instant
K:Kicker:2 G K:Kicker:2 G
A:SP$ Charm | Cost$ 1 G | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBPutCounter,DBGainLife,DBPump | AdditionalDescription$ If this spell was kicked, choose any number instead. A:SP$ Charm | Cost$ 1 G | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBPutCounter,DBGainLife,DBPump | AdditionalDescription$ . If this spell was kicked, choose any number instead.
SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature. SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature.
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ Z | References$ Z | SpellDescription$ Target player gains X life, where X is the greatest power among creatures they control. SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ Z | References$ Z | SpellDescription$ Target player gains X life, where X is the greatest power among creatures they control.
SVar:DBPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AILogic$ Fight | SubAbility$ DBFight | SpellDescription$ Target creature you control fights target creature you don't control. SVar:DBPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AILogic$ Fight | SubAbility$ DBFight | SpellDescription$ Target creature you control fights target creature you don't control.

View File

@@ -2,7 +2,7 @@ Name:Inscription of Insight
ManaCost:3 U ManaCost:3 U
Types:Sorcery Types:Sorcery
K:Kicker:2 U U K:Kicker:2 U U
A:SP$ Charm | Cost$ 3 U | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBReturn,DBScry,DBToken | AdditionalDescription$ If this spell was kicked, choose any number instead. A:SP$ Charm | Cost$ 3 U | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBReturn,DBScry,DBToken | AdditionalDescription$ . If this spell was kicked, choose any number instead.
SVar:DBReturn:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return up to two target creatures to their owners' hands. SVar:DBReturn:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return up to two target creatures to their owners' hands.
SVar:DBScry:DB$ Scry | ScryNum$ 2 | SubAbility$ DBDraw | SpellDescription$ Scry 2, then draw two cards. SVar:DBScry:DB$ Scry | ScryNum$ 2 | SubAbility$ DBDraw | SpellDescription$ Scry 2, then draw two cards.
SVar:DBDraw:DB$ Draw | NumCards$ 2 SVar:DBDraw:DB$ Draw | NumCards$ 2

View File

@@ -2,7 +2,7 @@ Name:Inscription of Ruin
ManaCost:2 B ManaCost:2 B
Types:Sorcery Types:Sorcery
K:Kicker:2 B B K:Kicker:2 B B
A:SP$ Charm | Cost$ 2 B | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBDiscard,DBReturn,DBDestroy | AdditionalDescription$ If this spell was kicked, choose any number instead. A:SP$ Charm | Cost$ 2 B | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBDiscard,DBReturn,DBDestroy | AdditionalDescription$ . If this spell was kicked, choose any number instead.
SVar:DBDiscard:DB$ Discard | ValidTgts$ Opponent | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target opponent discards two cards. SVar:DBDiscard:DB$ Discard | ValidTgts$ Opponent | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target opponent discards two cards.
SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Creature+cmcLE2+YouOwn | TgtPrompt$ Select target creature card with converted mana cost 2 or less | SpellDescription$ Return target creature card with converted mana cost 2 or less from your graveyard to the battlefield. SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Creature+cmcLE2+YouOwn | TgtPrompt$ Select target creature card with converted mana cost 2 or less | SpellDescription$ Return target creature card with converted mana cost 2 or less from your graveyard to the battlefield.
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.cmcLE3 | TgtPrompt$ Select target creature with converted mana cost 3 or less | SpellDescription$ Destroy target creature with converted mana cost 3 or less. SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.cmcLE3 | TgtPrompt$ Select target creature with converted mana cost 3 or less | SpellDescription$ Destroy target creature with converted mana cost 3 or less.

View File

@@ -3,12 +3,12 @@ ManaCost:1 U U
Types:Legendary Planeswalker Jace Types:Legendary Planeswalker Jace
Loyalty:4 Loyalty:4
K:Kicker:2 K:Kicker:2
T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, create a token that's a copy of CARDNAME, except it's not legendary and its starting loyalty is 1. T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enters the battlefield, if NICKNAME was kicked, create a token that's a copy of CARDNAME, except it's not legendary and its starting loyalty is 1.
SVar:TrigCopy:DB$ CopyPermanent | Defined$ Self | NonLegendary$ True | SetLoyalty$ 1 SVar:TrigCopy:DB$ CopyPermanent | Defined$ Self | NonLegendary$ True | SetLoyalty$ 1
A:AB$ Scry | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ScryNum$ 1 | SpellDescription$ Scry 1. A:AB$ Scry | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ScryNum$ 2 | SpellDescription$ Scry 2.
A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | NumCards$ 1 | Reveal$ True | RememberDrawn$ True | SubAbility$ DBRemoveCounters | SpellDescription$ Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from CARDNAME. A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | Ultimate$ True | NumCards$ 1 | Reveal$ True | RememberDrawn$ True | SubAbility$ DBRemoveCounters | SpellDescription$ Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from CARDNAME.
SVar:DBRemoveCounters:DB$ RemoveCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBRemoveCounters:DB$ RemoveCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$CardManaCost SVar:X:Remembered$CardManaCost
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:Kicker {2}\nWhen Jace, Mirror Mage enters the battlefield, if Jace was kicked, create a token that's a copy of Jace, Mirror Mage, except it's not legendary and its starting loyalty is 1.\n[+1]: Scry 1.\n[0]: Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from Jace, Mirror Mage. Oracle:Kicker {2}\nWhen Jace, Mirror Mage enters the battlefield, if Jace was kicked, create a token that's a copy of Jace, Mirror Mage, except it's not legendary and its starting loyalty is 1.\n[+1]: Scry 2.\n[0]: Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from Jace, Mirror Mage.

View File

@@ -3,10 +3,10 @@ ManaCost:2 W
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ LightOfPromiseTrig | AddSVar$ LightOfPromisePutCounter & X | Description$ Enchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature." S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ LightOfPromiseTrig | AddSVar$ LightOfPromisePutCounter & LightOfPromiseAmount | Description$ Enchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."
SVar:LightOfPromiseTrig:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ LightOfPromisePutCounter | TriggerDescription$ Whenever you gain life, put that many +1/+1 counters on CARDNAME. SVar:LightOfPromiseTrig:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ LightOfPromisePutCounter | TriggerDescription$ Whenever you gain life, put that many +1/+1 counters on this creature.
SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | References$ X SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ LightOfPromiseAmount | References$ LightOfPromiseAmount
SVar:X:TriggerCount$LifeAmount SVar:LightOfPromiseAmount:TriggerCount$LifeAmount
DeckHints:Ability$LifeGain DeckNeeds:Ability$LifeGain
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:Enchant creature\nEnchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature." Oracle:Enchant creature\nEnchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."

View File

@@ -3,7 +3,7 @@ ManaCost:6
Types:Artifact Types:Artifact
S:Mode$ Continuous | Affected$ Permanent | AddType$ Artifact | Description$ All permanents are artifact in addition to their other types. S:Mode$ Continuous | Affected$ Permanent | AddType$ Artifact | Description$ All permanents are artifact in addition to their other types.
S:Mode$ Continuous| Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless. S:Mode$ Continuous| Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless.
S:Mode$ Continuous | Affected$ Player | ManaColorConversion$ Additive | WhiteConversion$ All | BlueConversion$ All | BlackConversion$ All | RedConversion$ All | GreenConversion$ All | ColorlessConversion$ All | Description$ Players may spend mana as though it were mana of any color. S:Mode$ Continuous | Affected$ Player | ManaColorConversion$ Additive | WhiteConversion$ Color | BlueConversion$ Color | BlackConversion$ Color | RedConversion$ Color | GreenConversion$ Color | ColorlessConversion$ Color | Description$ Players may spend mana as though it were mana of any color.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/mycosynth_lattice.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/mycosynth_lattice.jpg

View File

@@ -12,4 +12,5 @@ SVar:X:Count$xPaid
SVar:Y:Count$RememberedSize SVar:Y:Count$RememberedSize
SVar:XLands:Number$0 SVar:XLands:Number$0
DeckHas:Ability$Sacrifice DeckHas:Ability$Sacrifice
AI:RemoveDeck:All
Oracle:Sacrifice X lands. For each land sacrificed this way, draw a card. You may play X additional lands this turn. Lands you control enter the battlefield tapped this turn. Oracle:Sacrifice X lands. For each land sacrificed this way, draw a card. You may play X additional lands this turn. Lands you control enter the battlefield tapped this turn.

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Kor Cleric
PT:3/3 PT:3/3
K:Lifelink K:Lifelink
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric with lesser converted mana cost from your graveyard to the battlefield. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric with lesser converted mana cost from your graveyard to the battlefield.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | TriggerZone$ Battlefield | ValidCard$ Cleric.YouCtrl+Other | Execute$ TrigReturn | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric card with lesser converted mana cost from your graveyard to the battlefield. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Cleric.YouCtrl+Other | Execute$ TrigReturn | Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric card with lesser converted mana cost from your graveyard to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Cleric.cmcLTX+YouOwn | TgtPrompt$ Choose target Cleric card with lesser converted mana cost | References$ X | Origin$ Graveyard | Destination$ Battlefield SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Cleric.cmcLTX+YouOwn | TgtPrompt$ Choose target Cleric card with lesser converted mana cost | References$ X | Origin$ Graveyard | Destination$ Battlefield
SVar:X:TriggeredCard$CardManaCost SVar:X:TriggeredCard$CardManaCost
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard

View File

@@ -2,9 +2,8 @@ Name:Rushing River
ManaCost:2 U ManaCost:2 U
Types:Instant Types:Instant
K:Kicker:Sac<1/Land> K:Kicker:Sac<1/Land>
A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | TargetMin$ X | TargetMax$ X | References$ X | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target nonland permanent to its owner's hand. If CARDNAME was kicked, return another target nonland permanent to its owner's hand. A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | TargetMin$ X | TargetMax$ X | References$ X,Z | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target nonland permanent to its owner's hand. If CARDNAME was kicked, return another target nonland permanent to its owner's hand.
SVar:X:Count$Kicked.2.1 SVar:X:Count$Kicked.2.1
SVar:NeedsToPlayKickedVar:Z GE2 SVar:NeedsToPlayKickedVar:Z GE2
SVar:Z:Count$Valid Permanent.nonLand+OppCtrl SVar:Z:Count$Valid Permanent.nonLand+OppCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/rushing_river.jpg
Oracle:Kicker—Sacrifice a land. (You may sacrifice a land in addition to any other costs as you cast this spell.)\nReturn target nonland permanent to its owner's hand. If Rushing River was kicked, return another target nonland permanent to its owner's hand. Oracle:Kicker—Sacrifice a land. (You may sacrifice a land in addition to any other costs as you cast this spell.)\nReturn target nonland permanent to its owner's hand. If Rushing River was kicked, return another target nonland permanent to its owner's hand.

View File

@@ -1,7 +1,7 @@
Name:Trinisphere Name:Trinisphere
ManaCost:3 ManaCost:3
Types:Artifact Types:Artifact
S:Mode$ SetCost | ValidCard$ Card | Type$ Spell | Amount$ Min3 | CheckSVar$ X | SVarCompare$ EQ1 | Description$ As long as CARDNAME is untapped, each spell that would cost less than three mana to cast costs three mana to cast. S:Mode$ SetCost | ValidCard$ Card | Type$ Spell | Amount$ 3 | RaiseTo$ True | CheckSVar$ X | SVarCompare$ EQ1 | Description$ As long as CARDNAME is untapped, each spell that would cost less than three mana to cast costs three mana to cast.
SVar:X:Count$Valid Card.Self+untapped SVar:X:Count$Valid Card.Self+untapped
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:NonStackingEffect:True SVar:NonStackingEffect:True

View File

@@ -0,0 +1,8 @@
Name:Amareth, the Lustrous
ManaCost:3 G U W
Types:Legendary Creature Dragon
PT:6/6
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ Whenever another permanent enters the battlefield under your control, look at the top card of your library. If it shares a card type with that permanent, you may reveal that card and put it into your hand.
SVar:TrigDig:DB$ Dig | DigNum$ 1 | ForceRevealToController$ True | ChangeNum$ 1 | Optional$ True | LibraryPosition$ 0 | LibraryPosition2$ 0 | ChangeValid$ Card.sharesCardTypeWith TriggeredCard
Oracle:Flying\nWhenever another permanent enters the battlefield under your control, look at the top card of your library. If it shares a card type with that permanent, you may reveal that card and put it into your hand.

View File

@@ -0,0 +1,9 @@
Name:Amphin Mutineer
ManaCost:3 U
Types:Creature Salamander Pirate
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile up to one target non-Salamander creature. That creature's controller creates a 4/3 blue Salamander Warrior creature token.
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.nonSalamander | TgtPrompt$ Select up to one target non-Salamander creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_4_3_salamander_warrior | TokenOwner$ TargetedController
K:Encore:4 U U
Oracle:When Amphin Mutineer enters the battlefield, exile up to one target non-Salamander creature. That creature's controller creates a 4/3 blue Salamander Warrior creature token.\nEncore {4}{U}{U} ({4}{U}{U}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.)

View File

@@ -0,0 +1,10 @@
Name:Apex Devastator
ManaCost:8 G G
Types:Creature Chimera Hydra
PT:10/10
K:Cascade
K:Cascade
K:Cascade
K:Cascade
SVar:PlayMain1:True
Oracle:Cascade, cascade, cascade, cascade (When you cast this spell, exile cards from the top of your library until you exile a nonland card that costs less. You may cast it without paying its mana cost. Put the exiled cards on the bottom in a random order. Then do it again.)

View File

@@ -0,0 +1,9 @@
Name:Archon of Coronation
ManaCost:4 W W
Types:Creature Archon
PT:5/5
K:Flying
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
S:Mode$ Continuous | Affected$ You | Condition$ You.isMonarch | AddKeyword$ Damage doesn't cause you to lose life. | Description$ As long as you're the monarch, damage doesn't cause you to lose life.
Oracle:Flying\nWhen Archon of Coronation enters the battlefield, you become the monarch.\nAs long as you're the monarch, damage doesn't cause you to lose life.

View File

@@ -0,0 +1,9 @@
Name:Aurora Phoenix
ManaCost:4 R R
Types:Creature Phoenix
PT:5/3
K:Flying
K:Cascade
T:Mode$ SpellCast | ValidCard$ Card.withCascade | ValidActivatingPlayer$ You | TriggerZones$ Graveyard | Execute$ TrigReturn | TriggerDescription$ Whenever you cast a spell with cascade, return CARDNAME from your graveyard to your hand.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand
Oracle:Flying\nCascade (When you cast this spell, exile cards from the top of your library until you exile a nonland card that costs less. You may cast it without paying its mana cost. Put the exiled cards on the bottom of your library in a random order.)\nWhenever you cast a spell with cascade, return Aurora Phoenix from your graveyard to your hand.

View File

@@ -0,0 +1,8 @@
Name:Belbe, Corrupted Observer
ManaCost:B G
Types:Legendary Creature Elf Zombie
PT:2/2
T:Mode$ Phase | Phase$ Main2 | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ At the beginning of each player's postcombat main phase, that player adds {C}{C} for each of your opponents that lost life this turn.
SVar:TrigMana:DB$ Mana | Produced$ C | Amount$ X | References$ X | Defined$ TriggeredPlayer
SVar:X:PlayerCountOpponents$HasPropertyLostLifeThisTurn/Twice
Oracle:At the beginning of each player's postcombat main phase, that player adds {C}{C} for each of your opponents that lost life this turn.

View File

@@ -0,0 +1,8 @@
Name:Bladegriff Prototype
ManaCost:5
Types:Artifact Creature Griffin
PT:3/2
K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDestroy | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, destroy target nonland permanent of that player's choice that one of your opponents controls.
SVar:TrigDestroy:DB$ Destroy | TargetingPlayer$ TriggeredTarget | ValidTgts$ Permanent.nonLand+OppCtrl
Oracle:Flying\nWhenever Bladegriff Prototype deals combat damage to a player, destroy target nonland permanent of that player's choice that one of your opponents controls.

View File

@@ -0,0 +1,16 @@
Name:Blim, Comedic Genius
ManaCost:2 B R
Types:Legendary Creature Imp
PT:4/3
K:Flying
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | TriggerZone$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player gains control of a permanent you control of your choice. Then each player loses life and discards cards equal to the number of permanents they control but don't own.
SVar:TrigPump:DB$ ChooseCard | Choices$ Permanent.YouCtrl | ChoiceTitle$ Choose a permanent you control for damaged player to gain control of | Defined$ You | SubAbility$ DBControl
SVar:DBControl:DB$ GainControl | Defined$ ChosenCard | NewController$ TriggeredTarget | SubAbility$ DBRepeatEach
SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBLoseLife | SubAbility$ DBCleanup
SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X | References$ X | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ Player.IsRemembered | NumCards$ X | References$ X | Mode$ TgtChoose
SVar:X:Count$Valid Permanent.RememberedPlayerCtrl/Minus.Y
SVar:Y:Count$Valid Permanent.RememberedPlayerCtrl+RememberedPlayerOwn
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True
DeckHints:Ability$Token
Oracle:Flying\nWhenever Blim, Comedic Genius deals combat damage to a player, that player gains control of a permanent you control of your choice. Then each player loses life and discards cards equal to the number of permanents they control but don't own.

View File

@@ -0,0 +1,14 @@
Name:Breeches, Brazen Plunderer
ManaCost:3 R
Types:Legendary Creature Goblin Pirate
PT:3/3
K:Menace
K:Partner
T:Mode$ DamageAll | ValidSource$ Pirate.YouCtrl | ValidTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigExileTop | TriggerDescription$ Whenever one or more Pirates you control deal damage to your opponents, exile the top card of each of those opponents' libraries. You may play those cards this turn, and you may spend mana as though it were mana of any color to cast those spells.
SVar:TrigExileTop:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Defined$ TriggeredTargets | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ EffPlay | EffectOwner$ You | RememberObjects$ Remembered | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
SVar:EffPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreType$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play those cards this turn, and you may spend mana as though it were mana of any color to cast those spells.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHints:Type$Pirate
Oracle:Menace\nWhenever one or more Pirates you control deal damage to your opponents, exile the top card of each of those opponents' libraries. You may play those cards this turn, and you may spend mana as though it were mana of any color to cast those spells.\nPartner (You can have two commanders if both have partner.)

View File

@@ -0,0 +1,9 @@
Name:Brinelin, the Moon Kraken
ManaCost:6 U U
Types:Legendary Creature Kraken
PT:6/8
K:Partner
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigReturn | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield or whenever you cast a spell with converted mana cost 6 or greater, you may return target nonland permanent to its owner's hand.
T:Mode$ SpellCast | ValidCard$ Card.cmcGE6 | ValidActivatingPlayer$ You | Execute$ TrigReturn | TriggerZones$ Battlefield | OptionalDecider$ You | Secondary$ True | TriggerDescription$ When CARDNAME enters the battlefield or whenever you cast a spell with converted mana cost 6 or greater, you may return target nonland permanent to its owner's hand.
SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | Origin$ Battlefield | Destination$ Hand
Oracle:When Brinelin, the Moon Kraken enters the battlefield or whenever you cast a spell with converted mana cost 6 or greater, you may return target nonland permanent to its owner's hand.\nPartner (You can have two commanders if both have partner.)

View File

@@ -0,0 +1,13 @@
Name:Colfenor, the Last Yew
ManaCost:3 W B G
Types:Legendary Creature Treefolk Shaman
PT:3/7
K:Vigilance
K:Reach
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other+YouCtrl | RememberTriggeringCard$ True | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever CARDNAME or another creature you control dies, return up to one other target creature card with lesser toughness from your graveyard to your hand.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | Secondary$ True | TriggerController$ TriggeredCardController | TriggerDescription$ Whenever CARDNAME or another creature you control dies, return up to one other target creature card with lesser toughness from your graveyard to your hand.
SVar:TrigChange:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Creature.Other+IsNotRemembered+toughnessLTX+YouOwn | TgtPrompt$ Select up to one other target creature card with lesser toughness from your graveyard to return to your hand | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:TriggeredCard$CardToughness
DeckHas:Ability$Graveyard
Oracle:Vigilance, reach\nWhenever Colfenor, the Last Yew or another creature you control dies, return up to one other target creature card with lesser toughness from your graveyard to your hand.

View File

@@ -0,0 +1,11 @@
Name:Court of Ambition
ManaCost:2 B B
Types:Enchantment
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$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRepeat | TriggerDescription$ At the beginning of your upkeep, each opponent loses 3 life unless they discard a card. If youre the monarch, instead each opponent loses 6 life unless they discard two cards.
SVar:TrigRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBDrain
SVar:DBDrain:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X | References$ X,Y | UnlessCost$ Discard<Y/Card> | UnlessPayer$ Player.IsRemembered
SVar:X:Count$Monarch.6.3
SVar:Y:Count$Monarch.2.1
Oracle:When Court of Ambition enters the battlefield, you become the monarch. \n At the beginning of your upkeep, each opponent loses 3 life unless they discard a card. If youre the monarch, instead each opponent loses 6 life unless they discard two cards.

View File

@@ -0,0 +1,10 @@
Name:Court of Cunning
ManaCost:1 U U
Types:Enchantment
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$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigMill | TriggerDescription$ At the beginning of your upkeep, any number of target players each mill two cards. If you're the monarch, each of those players mills ten cards instead. (To mill a card, a player puts the top card of their library into their graveyard.)
SVar:TrigMill:DB$ Mill | ValidTgts$ Player | TgtPrompt$ Select target player | TargetMin$ 0 | TargetMax$ MaxTgt | NumCards$ X | References$ X,MaxTgt
SVar:X:Count$Monarch.10.2
SVar:MaxTgt:PlayerCountPlayers$Amount
Oracle:When Court of Cunning enters the battlefield, you become the monarch.\nAt the beginning of your upkeep, any number of target players each mill two cards. If you're the monarch, each of those players mills ten cards instead. (To mill a card, a player puts the top card of their library into their graveyard.)

View File

@@ -0,0 +1,12 @@
Name:Emberwilde Captain
ManaCost:3 R
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 | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidHand Card.RememberedPlayerCtrl
Oracle:When Emberwilde Captain enters the battlefield, you become the monarch.\nWhenever an opponent attacks you while you're the monarch, Emberwilde Captain deals damage to that player equal to the number of cards in their hand.

View File

@@ -0,0 +1,9 @@
Name:Eyeblight Cullers
ManaCost:4 B
Types:Creature Elf Warrior
PT:3/3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, create three 1/1 green Elf Warrior creature tokens, then mill three cards. (Put the top three cards of your library into your graveyard.)
SVar:TrigToken:DB$ Token | TokenAmount$ 3 | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | SubAbility$ DBMill
SVar:DBMill:DB$ Mill | NumCards$ 3 | Defined$ You
DeckHas:Ability$Graveyard & Ability$Token
Oracle:When Eyeblight Cullers dies, create three 1/1 green Elf Warrior creature tokens, then mill three cards. (Put the top three cards of your library into your graveyard.)

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