mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Merge remote-tracking branch 'core-developers/master'
This commit is contained in:
@@ -75,6 +75,7 @@ public enum AiProps { /** */
|
||||
ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||
ACTIVELY_PROTECT_VS_CURSE_AURAS("false"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||
|
||||
@@ -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()));
|
||||
return threatened;
|
||||
|
||||
@@ -340,17 +340,19 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// select the worst of the best
|
||||
final Card worst = ComputerUtilCard.getWorstAI(maxList);
|
||||
if (worst.isLand()) {
|
||||
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
|
||||
this.holdAnimatedTillMain2(ai, worst);
|
||||
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
|
||||
this.releaseHeldTillMain2(ai, worst);
|
||||
return false;
|
||||
if (worst != null) {
|
||||
if (worst.isLand()) {
|
||||
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
|
||||
this.holdAnimatedTillMain2(ai, worst);
|
||||
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
|
||||
this.releaseHeldTillMain2(ai, worst);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.rememberAnimatedThisTurn(ai, worst);
|
||||
sa.getTargets().add(worst);
|
||||
}
|
||||
this.rememberAnimatedThisTurn(ai, worst);
|
||||
sa.getTargets().add(worst);
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||
|
||||
@@ -124,6 +124,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
private boolean smallSetOverride = false;
|
||||
private String boosterMustContain = "";
|
||||
private String boosterReplaceSlotFromPrintSheet = "";
|
||||
private String[] chaosDraftThemes = new String[0];
|
||||
private boolean doublePickToStartRound = false;
|
||||
private final CardInSet[] cards;
|
||||
private final Map<String, Integer> tokenNormalized;
|
||||
@@ -195,6 +196,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
public boolean getDoublePickToStartRound() { return doublePickToStartRound; }
|
||||
public String getBoosterMustContain() { return boosterMustContain; }
|
||||
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
|
||||
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
|
||||
|
||||
@@ -385,6 +387,9 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
|
||||
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.chaosDraftThemes = section.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||
.put("ManaColorConversion", "Additive")
|
||||
.put("WhiteConversion", "All")
|
||||
.put("BlueConversion", "All")
|
||||
.put("BlackConversion", "All")
|
||||
.put("RedConversion", "All")
|
||||
.put("GreenConversion", "All")
|
||||
.put("WhiteConversion", "Color")
|
||||
.put("BlueConversion", "Color")
|
||||
.put("BlackConversion", "Color")
|
||||
.put("RedConversion", "Color")
|
||||
.put("GreenConversion", "Color")
|
||||
.put("ColorlessConversion", "Color")
|
||||
.build();
|
||||
|
||||
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||
.putAll(ANY_COLOR_CONVERSION)
|
||||
.put("ColorlessConversion", "All")
|
||||
.put("ManaColorConversion", "Additive")
|
||||
.put("WhiteConversion", "Type")
|
||||
.put("BlueConversion", "Type")
|
||||
.put("BlackConversion", "Type")
|
||||
.put("RedConversion", "Type")
|
||||
.put("GreenConversion", "Type")
|
||||
.put("ColorlessConversion", "Type")
|
||||
.build();
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
|
||||
@@ -3,23 +3,20 @@ package forge.item.generation;
|
||||
import forge.card.CardEdition;
|
||||
import forge.item.BoosterPack;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.BagRandomizer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChaosBoosterSupplier implements IUnOpenedProduct {
|
||||
private List<CardEdition> sets;
|
||||
private BagRandomizer<CardEdition> randomizer;
|
||||
|
||||
public ChaosBoosterSupplier(List<CardEdition> sets) {
|
||||
this.sets = sets;
|
||||
public ChaosBoosterSupplier(Iterable<CardEdition> sets) throws IllegalArgumentException {
|
||||
randomizer = new BagRandomizer<>(sets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> get() {
|
||||
if (sets.size() == 0) {
|
||||
System.out.println("No chaos boosters left to supply.");
|
||||
return null;
|
||||
}
|
||||
final CardEdition set = sets.remove(0);
|
||||
final CardEdition set = randomizer.getNextItem();
|
||||
final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate());
|
||||
return pack.getCards();
|
||||
}
|
||||
|
||||
76
forge-core/src/main/java/forge/util/BagRandomizer.java
Normal file
76
forge-core/src/main/java/forge/util/BagRandomizer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ import java.util.Map.Entry;
|
||||
* <p>
|
||||
* GameInfo class.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
* @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> wonCards;
|
||||
|
||||
|
||||
private AnteResult(List<PaperCard> cards, boolean won) {
|
||||
// Need empty lists for other results for addition of change ownership cards
|
||||
if (won) {
|
||||
@@ -67,15 +67,20 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
|
||||
this.lostCards.addAll(cards);
|
||||
}
|
||||
|
||||
public static AnteResult won(List<PaperCard> cards) { return new AnteResult(cards, true); }
|
||||
public static AnteResult lost(List<PaperCard> cards) { return new AnteResult(cards, false); }
|
||||
public static AnteResult won(List<PaperCard> cards) {
|
||||
return new AnteResult(cards, true);
|
||||
}
|
||||
|
||||
public static AnteResult lost(List<PaperCard> cards) {
|
||||
return new AnteResult(cards, false);
|
||||
}
|
||||
}
|
||||
|
||||
private int lastTurnNumber = 0;
|
||||
private int lifeDelta = 0;
|
||||
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<>();
|
||||
|
||||
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) {
|
||||
winCondition = reason;
|
||||
calculateLifeDelta(players);
|
||||
|
||||
int winnersHealth = 0;
|
||||
int opponentsHealth = 0;
|
||||
|
||||
for (final Player p : players) {
|
||||
this.playerRating.put(p.getRegisteredPlayer(), p.getStats());
|
||||
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.
|
||||
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) {
|
||||
if (p.getTeam() == winningTeam) {
|
||||
winnersHealth += p.getLife();
|
||||
@@ -106,22 +112,22 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
|
||||
}
|
||||
}
|
||||
|
||||
calculateLifeDelta(players);
|
||||
lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
|
||||
}
|
||||
|
||||
private void calculateLifeDelta(Iterable<Player> players) {
|
||||
int opponentsHealth = 0;
|
||||
int winnersHealth = 0;
|
||||
|
||||
|
||||
for (Player p : players) {
|
||||
if (p.getOutcome().hasWon()) {
|
||||
winnersHealth += p.getLife();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
opponentsHealth += p.getLife();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
|
||||
}
|
||||
|
||||
@@ -150,7 +156,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
|
||||
|
||||
/**
|
||||
* Gets the winner.
|
||||
*
|
||||
*
|
||||
* @return the winner
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
public RegisteredPlayer getWinningPlayer() {
|
||||
for(Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) {
|
||||
for (Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) {
|
||||
if (pair.getValue().getOutcome().hasWon()) {
|
||||
return pair.getKey();
|
||||
}
|
||||
@@ -196,7 +202,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
|
||||
|
||||
/**
|
||||
* Gets the win spell effect.
|
||||
*
|
||||
*
|
||||
* @return the win spell effect
|
||||
*/
|
||||
public String getWinSpellEffect() {
|
||||
@@ -227,7 +233,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
|
||||
|
||||
public List<String> getOutcomeStrings() {
|
||||
List<String> outcomes = Lists.newArrayList();
|
||||
for(RegisteredPlayer player : playerNames.keySet()) {
|
||||
for (RegisteredPlayer player : playerNames.keySet()) {
|
||||
outcomes.add(getOutcomeString(player));
|
||||
}
|
||||
return outcomes;
|
||||
|
||||
@@ -116,6 +116,14 @@ public class GameView extends TrackableObject {
|
||||
public boolean isMatchOver() {
|
||||
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() {
|
||||
return get(TrackableProperty.WinningPlayerName);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class AbilityUtils {
|
||||
|
||||
else if (defined.equals("Enchanted")) {
|
||||
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").isEmpty()) {
|
||||
c = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard();
|
||||
@@ -168,8 +168,8 @@ public class AbilityUtils {
|
||||
|
||||
if (crd instanceof Card) {
|
||||
c = game.getCardState((Card) crd);
|
||||
} else if (crd instanceof List<?>) {
|
||||
cards.addAll((CardCollection) crd);
|
||||
} else if (crd instanceof Iterable<?>) {
|
||||
cards.addAll(Iterables.filter((Iterable<?>) crd, Card.class));
|
||||
}
|
||||
}
|
||||
else if (defined.equals("Remembered") || defined.equals("RememberedCard")) {
|
||||
@@ -657,9 +657,7 @@ public class AbilityUtils {
|
||||
if (calcX[0].startsWith("TriggeredPlayers")) {
|
||||
key = "Triggered" + key.substring(16);
|
||||
}
|
||||
final List<Player> players = new ArrayList<>();
|
||||
Iterables.addAll(players, getDefinedPlayers(card, key, sa));
|
||||
return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier;
|
||||
return CardFactoryUtil.playerXCount(getDefinedPlayers(card, key, sa), calcX[1], card) * multiplier;
|
||||
}
|
||||
if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
@@ -1078,20 +1076,10 @@ public class AbilityUtils {
|
||||
}
|
||||
if (o != null) {
|
||||
if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
if (!players.contains(p)) {
|
||||
players.add(p);
|
||||
}
|
||||
players.add((Player) o);
|
||||
}
|
||||
if (o instanceof List) {
|
||||
final List<?> pList = (List<?>)o;
|
||||
if (!pList.isEmpty()) {
|
||||
for (final Object p : pList) {
|
||||
if (p instanceof Player && !players.contains(p)) {
|
||||
players.add((Player) p);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (o instanceof Iterable) {
|
||||
players.addAll(Iterables.filter((Iterable<?>)o, Player.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1802,9 +1790,12 @@ public class AbilityUtils {
|
||||
if (params.containsKey(key)) {
|
||||
String convertTo = params.get(key);
|
||||
byte convertByte = 0;
|
||||
if ("All".equals(convertTo)) {
|
||||
if ("Type".equals(convertTo)) {
|
||||
// IMPORTANT! We need to use Mana Color here not Card Color.
|
||||
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 {
|
||||
for (final String convertColor : convertTo.split(",")) {
|
||||
convertByte |= ManaAtom.fromName(convertColor);
|
||||
|
||||
@@ -10,6 +10,7 @@ import forge.GameCommand;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -431,6 +432,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = player.getGame();
|
||||
final CardCollection commandCards = new CardCollection();
|
||||
|
||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final List<ZoneType> origin = Lists.newArrayList();
|
||||
@@ -664,6 +666,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
tgtC.setExiledWith(host);
|
||||
}
|
||||
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 (sa.hasParam("Fizzle")) {
|
||||
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);
|
||||
|
||||
// for things like Gaea's Blessing
|
||||
|
||||
@@ -56,8 +56,9 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
|
||||
final int num;
|
||||
// hotfix for Vindictive Lich when using getCardForUi
|
||||
if (source.getController() == null && sa.getParamOrDefault("CharmNum", "1").contains("MaxUniqueOpponents")) {
|
||||
boolean additionalDesc = sa.hasParam("AdditionalDescription");
|
||||
// hotfix for complex cards when using getCardForUi
|
||||
if (source.getController() == null && additionalDesc) {
|
||||
// using getCardForUi game is not set, so can't guess max charm
|
||||
num = Integer.MAX_VALUE;
|
||||
} else {
|
||||
@@ -73,8 +74,8 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
sb.append(sa.getCostDescription());
|
||||
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
||||
|
||||
if (num == min) {
|
||||
sb.append(Lang.getNumeral(num));
|
||||
if (num == min || num == Integer.MAX_VALUE) {
|
||||
sb.append(Lang.getNumeral(min));
|
||||
} else if (min == 0) {
|
||||
sb.append("up to ").append(Lang.getNumeral(num));
|
||||
} else {
|
||||
@@ -97,7 +98,6 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
sb.append(". You may choose the same mode more than once.");
|
||||
}
|
||||
|
||||
boolean additionalDesc = sa.hasParam("AdditionalDescription");
|
||||
if (additionalDesc) {
|
||||
sb.append(" ").append(sa.getParam("AdditionalDescription").trim());
|
||||
}
|
||||
|
||||
@@ -163,6 +163,11 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
host.addRemembered(one);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("ImprintRevealed") && hasRevealed) {
|
||||
for (final Card one : top) {
|
||||
host.addImprintedCard(one);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("Choser")) {
|
||||
final FCollectionView<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Choser"), sa);
|
||||
if (!choosers.isEmpty()) {
|
||||
|
||||
@@ -62,6 +62,11 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
|
||||
final int life1 = p1.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()) {
|
||||
final int diff = life1 - life2;
|
||||
p1.loseLife(diff);
|
||||
|
||||
@@ -58,6 +58,7 @@ public class SetStateEffect extends SpellAbilityEffect {
|
||||
final boolean manifestUp = sa.hasParam("ManifestUp");
|
||||
final boolean hiddenAgenda = sa.hasParam("HiddenAgenda");
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
final CardCollection transformedCards = new CardCollection();
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
@@ -130,8 +131,12 @@ public class SetStateEffect extends SpellAbilityEffect {
|
||||
if (remChanged) {
|
||||
host.addRemembered(tgt);
|
||||
}
|
||||
transformedCards.add(tgt);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
if (!transformedCards.isEmpty()) {
|
||||
game.getAction().reveal(transformedCards, p, true, "Transformed cards in ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1823,7 +1823,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|
||||
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|
||||
|| 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
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
|
||||
@@ -25,14 +25,14 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create();
|
||||
|
||||
public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) {
|
||||
this.putAll(damageMap);
|
||||
putAll(damageMap);
|
||||
}
|
||||
|
||||
public CardDamageMap() {
|
||||
}
|
||||
|
||||
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;
|
||||
for (final int i : e.getValue().values()) {
|
||||
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) {
|
||||
// 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();
|
||||
int sum = 0;
|
||||
for (final Integer i : e.getValue().values()) {
|
||||
@@ -71,7 +71,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
for (final int i : e.getValue().values()) {
|
||||
sum += i;
|
||||
@@ -106,9 +106,16 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) {
|
||||
public int totalAmount() {
|
||||
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<GameEntity> filteredTarget = null;
|
||||
if (validSource != null) {
|
||||
@@ -125,7 +132,8 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) {
|
||||
continue;
|
||||
}
|
||||
result += c.getValue();
|
||||
|
||||
result.put(c.getRowKey(), c.getColumnKey(), c.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1156,6 +1156,9 @@ public class CardFactoryUtil {
|
||||
if (sq[0].contains("Landfall")) {
|
||||
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")) {
|
||||
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 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"
|
||||
+ " | TriggerDescription$ Exile the tokens at end of combat.";
|
||||
@@ -4344,6 +4347,52 @@ public class CardFactoryUtil {
|
||||
sa.setIntrinsic(intrinsic);
|
||||
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")) {
|
||||
final String[] k = keyword.split(":");
|
||||
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.");
|
||||
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();
|
||||
@@ -4686,7 +4737,7 @@ public class CardFactoryUtil {
|
||||
altCostSA.setRestrictions(restriction);
|
||||
|
||||
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."));
|
||||
}
|
||||
|
||||
@@ -4758,6 +4809,16 @@ public class CardFactoryUtil {
|
||||
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
|
||||
String operand = part.substring(kwLength + 2);
|
||||
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)) {
|
||||
if (creatures != null && CardType.isACreatureType(creatures)) { // e.g. Kor Castigator
|
||||
creatures = StringUtils.capitalize(Lang.getPlural(part)) + creatures;
|
||||
|
||||
@@ -253,6 +253,13 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
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) {
|
||||
set(TrackableProperty.Counters, c.getCounters());
|
||||
updateLethalDamage(c);
|
||||
@@ -691,6 +698,20 @@ public class CardView extends GameEntityView {
|
||||
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() {
|
||||
return get(TrackableProperty.HasBackSide);
|
||||
}
|
||||
@@ -731,9 +752,8 @@ public class CardView extends GameEntityView {
|
||||
|
||||
CardState currentState = c.getCurrentState();
|
||||
if (isSplitCard) {
|
||||
if (c.getCurrentStateName() != CardStateName.LeftSplit && c.getCurrentStateName() != CardStateName.RightSplit && c.getCurrentStateName() != CardStateName.FaceDown) {
|
||||
currentState = c.getState(CardStateName.LeftSplit);
|
||||
}
|
||||
set(TrackableProperty.LeftSplitState, c.getState(CardStateName.LeftSplit).getView());
|
||||
set(TrackableProperty.RightSplitState, c.getState(CardStateName.RightSplit).getView());
|
||||
}
|
||||
|
||||
CardStateView currentStateView = currentState.getView();
|
||||
@@ -901,6 +921,12 @@ public class CardView extends GameEntityView {
|
||||
public ColorSet getOriginalColors() {
|
||||
return get(TrackableProperty.OriginalColors);
|
||||
}
|
||||
public ColorSet getLeftSplitColors() {
|
||||
return get(TrackableProperty.LeftSplitColors);
|
||||
}
|
||||
public ColorSet getRightSplitColors() {
|
||||
return get(TrackableProperty.RightSplitColors);
|
||||
}
|
||||
void updateColors(Card c) {
|
||||
set(TrackableProperty.Colors, c.determineColor());
|
||||
}
|
||||
@@ -909,6 +935,10 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void setOriginalColors(Card c) {
|
||||
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) {
|
||||
set(TrackableProperty.HasChangedColors, hasChangeColor);
|
||||
@@ -1082,6 +1112,7 @@ public class CardView extends GameEntityView {
|
||||
public String getProtectionKey() { return get(TrackableProperty.ProtectionKey); }
|
||||
public String getHexproofKey() { return get(TrackableProperty.HexproofKey); }
|
||||
public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); }
|
||||
public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); }
|
||||
public boolean hasDefender() { return get(TrackableProperty.HasDefender); }
|
||||
public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); }
|
||||
public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); }
|
||||
@@ -1118,6 +1149,7 @@ public class CardView extends GameEntityView {
|
||||
void updateKeywords(Card c, CardState state) {
|
||||
c.updateKeywordsCache(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.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state));
|
||||
set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state));
|
||||
|
||||
@@ -337,15 +337,12 @@ public class CostAdjustment {
|
||||
return;
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
if (StringUtils.isNumeric(amount)) {
|
||||
value = Integer.parseInt(amount);
|
||||
} else {
|
||||
if ("Min3".equals(amount)) {
|
||||
int cmc = manaCost.getConvertedManaCost();
|
||||
if (cmc < 3) {
|
||||
value = 3 - cmc;
|
||||
}
|
||||
int value = Integer.parseInt(amount);
|
||||
|
||||
if (staticAbility.hasParam("RaiseTo")) {
|
||||
int cmc = manaCost.getConvertedManaCost();
|
||||
if (cmc < value) {
|
||||
value = Integer.parseInt(amount) - cmc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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."),
|
||||
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."),
|
||||
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."),
|
||||
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."),
|
||||
|
||||
@@ -581,7 +581,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (infect) {
|
||||
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
|
||||
// of the damage
|
||||
if (hasKeyword("DamageLifeThreshold:7") && life - 7 <= amount) {
|
||||
@@ -741,6 +741,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
restDamage = 0;
|
||||
}
|
||||
}
|
||||
} else if (c.getName().equals("Obosh, the Preypiercer")) {
|
||||
if (c.getController().equals(source.getController()) && source.getCMC() % 2 != 0) {
|
||||
restDamage *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -439,6 +439,12 @@ public class PlayerView extends GameEntityView {
|
||||
return types.size();
|
||||
}
|
||||
|
||||
public boolean hasDelirium() {
|
||||
if (get(TrackableProperty.HasDelirium) == null)
|
||||
return false;
|
||||
return get(TrackableProperty.HasDelirium);
|
||||
}
|
||||
|
||||
private static TrackableProperty getZoneProp(final ZoneType zone) {
|
||||
switch (zone) {
|
||||
case Ante:
|
||||
@@ -466,6 +472,10 @@ public class PlayerView extends GameEntityView {
|
||||
if (prop == null) { return; }
|
||||
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
|
||||
switch (zone.getZoneType()) {
|
||||
case Command:
|
||||
|
||||
@@ -38,6 +38,7 @@ public class StackItemView extends TrackableObject implements IHasCardView {
|
||||
updateAbility(si);
|
||||
updateOptionalTrigger(si);
|
||||
updateSubInstance(si);
|
||||
updateOptionalCost(si);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
@@ -47,6 +48,55 @@ public class StackItemView extends TrackableObject implements IHasCardView {
|
||||
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() {
|
||||
return get(TrackableProperty.SourceTrigger);
|
||||
}
|
||||
|
||||
@@ -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 (controller.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -29,24 +29,25 @@ public class TriggerDamageAll extends Trigger {
|
||||
}
|
||||
}
|
||||
final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap);
|
||||
return filterTable(table) > 0;
|
||||
return !table.filteredMap(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public String getImportantStackObjects(SpellAbility sa) {
|
||||
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));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int filterTable(CardDamageMap table) {
|
||||
return table.filteredAmount(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,6 +573,11 @@ public class TriggerHandler {
|
||||
host.addRemembered(sa.getActivatingPlayer());
|
||||
}
|
||||
|
||||
if (regtrig.hasParam("RememberTriggeringCard")) {
|
||||
Card triggeredCard = ((Card) sa.getTriggeringObject(AbilityKey.Card));
|
||||
host.addRemembered(triggeredCard);
|
||||
}
|
||||
|
||||
sa.setStackDescription(sa.toString());
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
// need to be set for demonic pact to look for chosen modes
|
||||
|
||||
@@ -101,10 +101,10 @@ public class TriggerTapsForMana extends Trigger {
|
||||
if (!this.getHostCard().hasChosenColor() || !produced.contains(MagicColor.toShortString(this.getHostCard().getChosenColor()))) {
|
||||
return false;
|
||||
}
|
||||
if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) {
|
||||
}
|
||||
if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -69,6 +69,8 @@ public enum TrackableProperty {
|
||||
PairedWith(TrackableTypes.CardViewType),
|
||||
CurrentState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
|
||||
AlternateState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
|
||||
LeftSplitState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
|
||||
RightSplitState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
|
||||
HiddenId(TrackableTypes.IntegerType),
|
||||
ExertedThisTurn(TrackableTypes.BooleanType),
|
||||
|
||||
@@ -76,6 +78,8 @@ public enum TrackableProperty {
|
||||
Name(TrackableTypes.StringType),
|
||||
Colors(TrackableTypes.ColorSetType),
|
||||
OriginalColors(TrackableTypes.ColorSetType),
|
||||
LeftSplitColors(TrackableTypes.ColorSetType),
|
||||
RightSplitColors(TrackableTypes.ColorSetType),
|
||||
ImageKey(TrackableTypes.StringType),
|
||||
Type(TrackableTypes.CardTypeViewType),
|
||||
ManaCost(TrackableTypes.ManaCostType),
|
||||
@@ -92,6 +96,7 @@ public enum TrackableProperty {
|
||||
|
||||
KeywordKey(TrackableTypes.StringType),
|
||||
HasDeathtouch(TrackableTypes.BooleanType),
|
||||
HasDevoid(TrackableTypes.BooleanType),
|
||||
HasDefender(TrackableTypes.BooleanType),
|
||||
HasDoubleStrike(TrackableTypes.BooleanType),
|
||||
HasFirstStrike(TrackableTypes.BooleanType),
|
||||
@@ -160,6 +165,7 @@ public enum TrackableProperty {
|
||||
IsExtraTurn(TrackableTypes.BooleanType),
|
||||
ExtraTurnCount(TrackableTypes.IntegerType),
|
||||
HasPriority(TrackableTypes.BooleanType),
|
||||
HasDelirium(TrackableTypes.BooleanType),
|
||||
|
||||
//SpellAbility
|
||||
HostCard(TrackableTypes.CardViewType),
|
||||
@@ -181,6 +187,7 @@ public enum TrackableProperty {
|
||||
SubInstance(TrackableTypes.StackItemViewType),
|
||||
Ability(TrackableTypes.BooleanType),
|
||||
OptionalTrigger(TrackableTypes.BooleanType),
|
||||
OptionalCosts(TrackableTypes.StringType),
|
||||
|
||||
//Combat
|
||||
AttackersWithDefenders(TrackableTypes.GenericMapType, FreezeMode.IgnoresFreeze),
|
||||
@@ -199,6 +206,7 @@ public enum TrackableProperty {
|
||||
WinningPlayerName(TrackableTypes.StringType),
|
||||
WinningTeam(TrackableTypes.IntegerType),
|
||||
MatchOver(TrackableTypes.BooleanType),
|
||||
Mulligan(TrackableTypes.BooleanType),
|
||||
NumGamesInMatch(TrackableTypes.IntegerType),
|
||||
NumPlayedGamesInMatch(TrackableTypes.IntegerType),
|
||||
Stack(TrackableTypes.StackItemViewListType),
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-backend-android</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ public class CardDetailPanel extends SkinnedPanel {
|
||||
} else {
|
||||
final String manaCost;
|
||||
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 {
|
||||
manaCost = state.getManaCost().toString();
|
||||
}
|
||||
@@ -256,7 +256,7 @@ public class CardDetailPanel extends SkinnedPanel {
|
||||
idLabel.setText(mayView ? CardDetailUtil.formatCardId(state) : "");
|
||||
|
||||
// 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() {
|
||||
@Override public void run() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import net.miginfocom.swing.MigLayout;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.game.GameView;
|
||||
import forge.gauntlet.GauntletWinLoseController;
|
||||
import forge.util.Localizer;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
@@ -50,7 +51,7 @@ public class GauntletWinLose extends ControlWinLose {
|
||||
controller = new GauntletWinLoseController(view0, game0) {
|
||||
@Override
|
||||
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();
|
||||
|
||||
final JPanel pnlResults = new JPanel();
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
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.FilenameFilter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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 static void simulate(String[] args) {
|
||||
FModel.initialize(null, null);
|
||||
|
||||
System.out.println("Simulation mode");
|
||||
if(args.length < 4) {
|
||||
if (args.length < 4) {
|
||||
argumentHelp();
|
||||
return;
|
||||
}
|
||||
@@ -49,11 +48,9 @@ public class SimulateMatch {
|
||||
|
||||
options = new ArrayList<>();
|
||||
params.put(a.substring(1), options);
|
||||
}
|
||||
else if (options != null) {
|
||||
} else if (options != null) {
|
||||
options.add(a);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
System.err.println("Illegal parameter usage");
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +94,7 @@ public class SimulateMatch {
|
||||
int i = 1;
|
||||
|
||||
if (params.containsKey("d")) {
|
||||
for(String deck : params.get("d")) {
|
||||
for (String deck : params.get("d")) {
|
||||
Deck d = deckFromCommandLineParameter(deck, type);
|
||||
if (d == null) {
|
||||
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
|
||||
@@ -130,7 +127,7 @@ public class SimulateMatch {
|
||||
|
||||
if (matchSize != 0) {
|
||||
int iGame = 0;
|
||||
while(!mc.isMatchOver()) {
|
||||
while (!mc.isMatchOver()) {
|
||||
// play games until the match ends
|
||||
simulateSingleMatch(mc, iGame, outputGamelog);
|
||||
iGame++;
|
||||
@@ -159,38 +156,26 @@ public class SimulateMatch {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
|
||||
final StopWatch sw = new StopWatch();
|
||||
sw.start();
|
||||
|
||||
final Game g1 = mc.createGame();
|
||||
// will run match in the same thread
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mc.startGame(g1);
|
||||
sw.stop();
|
||||
}
|
||||
TimeLimitedCodeBlock.runWithTimeout(() -> {
|
||||
mc.startGame(g1);
|
||||
sw.stop();
|
||||
}, 120, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
} catch (TimeoutException e) {
|
||||
System.out.println("Stopping slow match as draw");
|
||||
g1.setGameOver(GameEndReason.Draw);
|
||||
sw.stop();
|
||||
}catch (Exception e){
|
||||
} catch (Exception | StackOverflowError e) {
|
||||
e.printStackTrace();
|
||||
g1.setGameOver(GameEndReason.Draw);
|
||||
sw.stop();
|
||||
}catch(StackOverflowError e){
|
||||
} finally {
|
||||
g1.setGameOver(GameEndReason.Draw);
|
||||
sw.stop();
|
||||
}
|
||||
|
||||
|
||||
List<GameLogEntry> log;
|
||||
if (outputGamelog) {
|
||||
log = g1.getGameLog().getLogEntries(null);
|
||||
@@ -198,15 +183,15 @@ public class SimulateMatch {
|
||||
log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS);
|
||||
}
|
||||
Collections.reverse(log);
|
||||
for(GameLogEntry l : log) {
|
||||
for (GameLogEntry l : log) {
|
||||
System.out.println(l);
|
||||
}
|
||||
|
||||
// If both players life totals to 0 in a single turn, the game should end in a draw
|
||||
if(g1.getOutcome().isDraw()){
|
||||
System.out.println(String.format("Game %d ended in a Draw! Took %d ms.", 1+iGame, sw.getTime()));
|
||||
if (g1.getOutcome().isDraw()) {
|
||||
System.out.printf("\nGame Result: Game %d ended in a Draw! Took %d ms.%n", 1 + iGame, sw.getTime());
|
||||
} 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<>();
|
||||
int numPlayers = 0;
|
||||
if (params.containsKey("d")) {
|
||||
for(String deck : params.get("d")) {
|
||||
for (String deck : params.get("d")) {
|
||||
Deck d = deckFromCommandLineParameter(deck, rules.getGameType());
|
||||
if (d == null) {
|
||||
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
|
||||
@@ -239,7 +224,7 @@ public class SimulateMatch {
|
||||
if (!folder.isDirectory()) {
|
||||
System.out.println("Directory not found - " + foldName);
|
||||
} else {
|
||||
for(File deck : folder.listFiles(new FilenameFilter() {
|
||||
for (File deck : folder.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.endsWith(".dck");
|
||||
@@ -281,16 +266,16 @@ public class SimulateMatch {
|
||||
System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ",
|
||||
String.valueOf(numPlayers), " players over ",
|
||||
String.valueOf(tourney.getTotalRounds()), " rounds"));
|
||||
while(!tourney.isTournamentOver()) {
|
||||
while (!tourney.isTournamentOver()) {
|
||||
if (tourney.getActiveRound() != curRound) {
|
||||
if (curRound != 0) {
|
||||
System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound)));
|
||||
}
|
||||
curRound = tourney.getActiveRound();
|
||||
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();
|
||||
@@ -311,10 +296,10 @@ public class SimulateMatch {
|
||||
int iGame = 0;
|
||||
while (!mc.isMatchOver()) {
|
||||
// play games until the match ends
|
||||
try{
|
||||
try {
|
||||
simulateSingleMatch(mc, iGame, outputGamelog);
|
||||
iGame++;
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
exceptions++;
|
||||
System.out.println(e.toString());
|
||||
if (exceptions > 5) {
|
||||
@@ -349,10 +334,10 @@ public class SimulateMatch {
|
||||
|
||||
private static Deck deckFromCommandLineParameter(String deckname, GameType type) {
|
||||
int dotpos = deckname.lastIndexOf('.');
|
||||
if(dotpos > 0 && dotpos == deckname.length()-4) {
|
||||
if (dotpos > 0 && dotpos == deckname.length() - 4) {
|
||||
String baseDir = type.equals(GameType.Commander) ?
|
||||
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;
|
||||
|
||||
@@ -462,7 +462,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-backend-robovm</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -60,18 +60,18 @@
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-backend-lwjgl</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-platform</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>1.9.11</version>
|
||||
<classifier>natives-desktop</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-freetype-platform</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
<classifier>natives-desktop</classifier>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
|
||||
|
||||
@@ -63,12 +63,12 @@
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-freetype</artifactId>
|
||||
<version>1.9.10</version>
|
||||
<version>1.9.11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ public class Forge implements ApplicationListener {
|
||||
public static float heigtModifier = 0.0f;
|
||||
private static boolean isloadingaMatch = false;
|
||||
public static boolean showFPS = false;
|
||||
public static boolean altPlayerLayout = false;
|
||||
public static boolean enableUIMask = false;
|
||||
public static boolean enablePreloadExtendedArt = false;
|
||||
public static String locale = "en-US";
|
||||
@@ -123,6 +124,7 @@ public class Forge implements ApplicationListener {
|
||||
|
||||
textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
|
||||
showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS);
|
||||
altPlayerLayout = prefs.getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
|
||||
enableUIMask = prefs.getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING);
|
||||
enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
|
||||
locale = prefs.getPref(FPref.UI_LANGUAGE);
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
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.ShapeType;
|
||||
import com.badlogic.gdx.math.Matrix4;
|
||||
@@ -37,8 +38,75 @@ public class Graphics {
|
||||
private int failedClipCount;
|
||||
private float alphaComposite = 1;
|
||||
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() {
|
||||
ShaderProgram.pedantic = false;
|
||||
}
|
||||
|
||||
public void begin(float regionWidth0, float regionHeight0) {
|
||||
@@ -60,6 +128,7 @@ public class Graphics {
|
||||
public void dispose() {
|
||||
batch.dispose();
|
||||
shapeRenderer.dispose();
|
||||
shaderOutline.dispose();
|
||||
}
|
||||
|
||||
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) {
|
||||
float oldalpha = alphaComposite;
|
||||
if(tint){
|
||||
if(tint && !tintColor.equals(borderColor)){
|
||||
drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12);
|
||||
fillRoundRect(tintColor, x, y, w, h, (h-w)/12);
|
||||
} else {
|
||||
@@ -604,6 +673,56 @@ public class Graphics {
|
||||
public void drawImage(TextureRegion image, float x, float y, float w, float 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) {
|
||||
if (startClip(x, y, w, h)) { //only render if clip successful, otherwise it will escape bounds
|
||||
|
||||
@@ -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.PixmapPacker.Page;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
// 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(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))
|
||||
|
||||
@@ -30,7 +30,8 @@ public class FSkin {
|
||||
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> 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 FileHandle preferredDir;
|
||||
@@ -196,6 +197,7 @@ public class FSkin {
|
||||
final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE);
|
||||
final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE);
|
||||
final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE);
|
||||
final FileHandle f13 = getDefaultSkinFile(ForgeConstants.SPRITE_DECKBOX_FILE);
|
||||
|
||||
try {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
//borders
|
||||
Texture bordersBW = new Texture(f10);
|
||||
FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 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();
|
||||
pxDefaultAvatars.dispose();
|
||||
@@ -430,5 +442,9 @@ public class FSkin {
|
||||
return borders;
|
||||
}
|
||||
|
||||
public static Map<Integer, TextureRegion> getDeckbox() {
|
||||
return deckbox;
|
||||
}
|
||||
|
||||
public static boolean isLoaded() { return loaded; }
|
||||
}
|
||||
|
||||
@@ -288,15 +288,18 @@ public class ImageCache {
|
||||
public static FImage getBorderImage(String textureString) {
|
||||
return getBorder(textureString);
|
||||
}
|
||||
public static Color getTint(CardView c) {
|
||||
public static Color getTint(CardView c, Texture t) {
|
||||
if (c == null)
|
||||
return Color.CLEAR;
|
||||
return borderColor(t);
|
||||
if (c.isFaceDown())
|
||||
return Color.CLEAR;
|
||||
return Color.valueOf("#171717");
|
||||
|
||||
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");
|
||||
}
|
||||
else if (state.getColors().isMonoColor()) {
|
||||
if (state.getColors().hasBlack())
|
||||
return Color.valueOf("#48494a");
|
||||
@@ -311,6 +314,7 @@ public class ImageCache {
|
||||
}
|
||||
else if (state.getColors().isMulticolor())
|
||||
return Color.valueOf("#F9E084");
|
||||
return Color.CLEAR;
|
||||
|
||||
return borderColor(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +190,17 @@ public class CardFaceSymbols {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (final ManaCostShard s : colorSet.getOrderedShards()) {
|
||||
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
|
||||
x += dx;
|
||||
if (!vertical)
|
||||
x += dx;
|
||||
else
|
||||
y += dx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ public class CardImageRenderer {
|
||||
ManaCost mainManaCost = state.getManaCost();
|
||||
if (card.isSplitCard() && card.getAlternateState() != null) {
|
||||
//handle rendering both parts of split card
|
||||
mainManaCost = state.getManaCost();
|
||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
||||
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);
|
||||
@@ -459,7 +459,7 @@ public class CardImageRenderer {
|
||||
|
||||
y += cardNameBoxHeight + innerBorderThickness;
|
||||
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;
|
||||
Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT);
|
||||
@@ -506,7 +506,7 @@ public class CardImageRenderer {
|
||||
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
|
||||
//handle rendering both parts of split card
|
||||
mainManaCost = state.getManaCost();
|
||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
||||
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);
|
||||
|
||||
@@ -370,6 +370,7 @@ public class CardRenderer {
|
||||
ManaCost mainManaCost = card.getCurrentState().getManaCost();
|
||||
if (card.isSplitCard()) {
|
||||
//handle rendering both parts of split card
|
||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
||||
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + MANA_COST_PADDING;
|
||||
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);
|
||||
else {
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
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);
|
||||
float oldAlpha = g.getfloatAlphaComposite();
|
||||
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
|
||||
@@ -560,7 +561,7 @@ public class CardRenderer {
|
||||
h -= 2 * padding;
|
||||
|
||||
// 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 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);
|
||||
@@ -932,8 +933,8 @@ public class CardRenderer {
|
||||
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.getCurrentState().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.getLeftSplitState().getManaCost(), x - padding, y + dy, w + 2 * padding, h, manaSymbolSize);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -63,6 +63,7 @@ public class FDeckChooser extends FScreen {
|
||||
private Callback<Deck> callback;
|
||||
private NetDeckCategory netDeckCategory;
|
||||
private boolean refreshingDeckType;
|
||||
private boolean firstactivation = true;
|
||||
|
||||
private final DeckManager lstDecks;
|
||||
private final FButton btnNewDeck = new FButton(Localizer.getInstance().getMessage("lblNewDeck"));
|
||||
@@ -226,6 +227,11 @@ public class FDeckChooser extends FScreen {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
needRefreshOnActivate = false;
|
||||
refreshDecksList(selectedDeckType, true, null);
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
package forge.itemmanager.views;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import forge.Forge;
|
||||
import forge.Forge.KeyInputAdapter;
|
||||
import forge.Graphics;
|
||||
import forge.assets.FImage;
|
||||
import forge.assets.FImageComplex;
|
||||
import forge.assets.FSkin;
|
||||
import forge.assets.FSkinColor;
|
||||
import forge.assets.FSkinImage;
|
||||
import forge.assets.FSkinColor.Colors;
|
||||
import forge.assets.FSkinFont;
|
||||
import forge.assets.ImageCache;
|
||||
import forge.card.CardFaceSymbols;
|
||||
import forge.card.CardRenderer;
|
||||
import forge.card.CardRenderer.CardStackPosition;
|
||||
import forge.card.CardZoom;
|
||||
import forge.card.ColorSet;
|
||||
import forge.deck.ArchetypeDeckGenerator;
|
||||
import forge.deck.CardThemedDeckGenerator;
|
||||
import forge.deck.CommanderDeckGenerator;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.deck.FDeckViewer;
|
||||
import forge.deck.io.DeckPreferences;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ColumnDef;
|
||||
@@ -27,6 +33,7 @@ import forge.itemmanager.ItemManagerConfig;
|
||||
import forge.itemmanager.ItemManagerModel;
|
||||
import forge.itemmanager.SItemManagerUtil;
|
||||
import forge.itemmanager.filters.ItemFilter;
|
||||
import forge.planarconquest.ConquestCommander;
|
||||
import forge.toolbox.FCardPanel;
|
||||
import forge.toolbox.FComboBox;
|
||||
import forge.toolbox.FDisplayObject;
|
||||
@@ -36,6 +43,7 @@ import forge.toolbox.FEvent.FEventHandler;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FScrollPane;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.Utils;
|
||||
|
||||
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_LINE_THICKNESS = 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 final List<Integer> selectedIndices = new ArrayList<>();
|
||||
@@ -550,7 +558,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
}
|
||||
else if (count == 2) {
|
||||
if (item != null && item.selected) {
|
||||
itemManager.activateSelectedItems();
|
||||
if (!(item.getKey() instanceof DeckProxy))
|
||||
itemManager.activateSelectedItems();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -854,6 +863,19 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
isCollapsed = !isCollapsed;
|
||||
btnExpandCollapseAll.updateIsAllCollapsed();
|
||||
@@ -869,7 +891,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
|
||||
if (item != null) {
|
||||
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());
|
||||
return true;
|
||||
}
|
||||
@@ -922,6 +944,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
private int index;
|
||||
private CardStackPosition pos;
|
||||
private boolean selected;
|
||||
private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE;
|
||||
|
||||
private ItemInfo(T item0, Group group0) {
|
||||
item = item0;
|
||||
@@ -954,30 +977,100 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
final float y = getTop() - group.getTop() - getScrollValue();
|
||||
final float w = getWidth();
|
||||
final float h = getHeight();
|
||||
|
||||
if (selected) { //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);
|
||||
Texture dpImg = null;
|
||||
boolean deckSelectMode = false;
|
||||
if (item instanceof DeckProxy) {
|
||||
dpImg = ImageCache.getImage(item);
|
||||
deckSelectMode = true;
|
||||
}
|
||||
if (selected) {
|
||||
if (!deckSelectMode) {
|
||||
//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) {
|
||||
CardRenderer.drawCard(g, (PaperCard)item, x, y, w, h, pos);
|
||||
}
|
||||
else {
|
||||
CardRenderer.drawCard(g, (PaperCard) item, x, y, w, h, pos);
|
||||
} else if (item instanceof ConquestCommander) {
|
||||
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);
|
||||
if (img != null) {
|
||||
g.drawImage(img, x, y, w, h);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -628,7 +628,8 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
|
||||
}
|
||||
if (Forge.gameInProgress) {
|
||||
/*preload deck to cache*/
|
||||
ImageCache.preloadCache(decks[i]);
|
||||
if(slot.getType() == LobbySlotType.LOCAL)
|
||||
ImageCache.preloadCache(decks[i]);
|
||||
}
|
||||
Gdx.graphics.requestRendering();
|
||||
}
|
||||
|
||||
@@ -358,10 +358,10 @@ public class MatchScreen extends FScreen {
|
||||
if(gameMenu!=null) {
|
||||
if(gameMenu.getChildCount()>3){
|
||||
if(viewWinLose == null) {
|
||||
gameMenu.getChildAt(0).setEnabled(true);
|
||||
gameMenu.getChildAt(1).setEnabled(true);
|
||||
gameMenu.getChildAt(2).setEnabled(true);
|
||||
gameMenu.getChildAt(3).setEnabled(true);
|
||||
gameMenu.getChildAt(0).setEnabled(!game.isMulligan());
|
||||
gameMenu.getChildAt(1).setEnabled(!game.isMulligan());
|
||||
gameMenu.getChildAt(2).setEnabled(!game.isMulligan());
|
||||
gameMenu.getChildAt(3).setEnabled(!game.isMulligan());
|
||||
gameMenu.getChildAt(4).setEnabled(false);
|
||||
} else {
|
||||
gameMenu.getChildAt(0).setEnabled(false);
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
|
||||
import forge.Forge;
|
||||
import forge.Graphics;
|
||||
import forge.assets.FImage;
|
||||
import forge.assets.FSkinFont;
|
||||
@@ -69,6 +70,9 @@ public class VAvatar extends FDisplayObject {
|
||||
float h = getHeight();
|
||||
g.drawImage(image, 0, 0, w, h);
|
||||
|
||||
if (Forge.altPlayerLayout && Forge.isLandscapeMode())
|
||||
return;
|
||||
|
||||
//display XP in lower right corner of avatar
|
||||
int xp = player.getCounters(CounterEnumType.EXPERIENCE);
|
||||
if (xp > 0) {
|
||||
|
||||
@@ -114,6 +114,7 @@ public class VField extends FContainer {
|
||||
if (!c.hasCardAttachments() &&
|
||||
cardName.equals(c.getCurrentState().getName()) &&
|
||||
card.hasSameCounters(c) &&
|
||||
card.hasSamePT(c) && //don't stack token with different PT
|
||||
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
|
||||
card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens
|
||||
card.isSick() == c.isSick() && //don't stack sick tokens on non sick
|
||||
|
||||
@@ -50,6 +50,12 @@ public class VGameMenu extends FDropDownMenu {
|
||||
addItem(new FMenuItem(localizer.getMessage("lblDeckList"), FSkinImage.DECKLIST, new FEventHandler() {
|
||||
@Override
|
||||
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();
|
||||
if (player != null) {
|
||||
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() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
|
||||
import forge.Forge;
|
||||
@@ -30,8 +31,10 @@ import forge.util.Utils;
|
||||
public class VPlayerPanel extends FContainer {
|
||||
private static final FSkinFont LIFE_FONT = FSkinFont.get(18);
|
||||
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 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_Y = Utils.scale(2);
|
||||
|
||||
@@ -48,6 +51,7 @@ public class VPlayerPanel extends FContainer {
|
||||
private float avatarHeight = VAvatar.HEIGHT;
|
||||
private float displayAreaHeightFactor = 1.0f;
|
||||
private boolean forMultiPlayer = false;
|
||||
public int adjustHeight = 1;
|
||||
|
||||
public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) {
|
||||
player = player0;
|
||||
@@ -284,8 +288,14 @@ public class VPlayerPanel extends FContainer {
|
||||
float y = 0;
|
||||
avatar.setPosition(x, y);
|
||||
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 infoTabHeight = (height - y) / tabs.size();
|
||||
for (InfoTab tab : tabs) {
|
||||
@@ -361,6 +371,7 @@ public class VPlayerPanel extends FContainer {
|
||||
private int life = player.getLife();
|
||||
private int poisonCounters = player.getCounters(CounterEnumType.POISON);
|
||||
private int energyCounters = player.getCounters(CounterEnumType.ENERGY);
|
||||
private int experienceCounters = player.getCounters(CounterEnumType.EXPERIENCE);
|
||||
private String lifeStr = String.valueOf(life);
|
||||
|
||||
private LifeLabel() {
|
||||
@@ -388,6 +399,7 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
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
|
||||
if (vibrateDuration > 0 && MatchController.instance.isLocalPlayer(player) &&
|
||||
@@ -405,22 +417,50 @@ public class VPlayerPanel extends FContainer {
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g) {
|
||||
if (poisonCounters == 0 && energyCounters == 0) {
|
||||
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);
|
||||
adjustHeight = 1;
|
||||
if(Forge.altPlayerLayout && Forge.isLandscapeMode()) {
|
||||
if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0) {
|
||||
g.drawOutlinedText(lifeStr, INFO2_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, 0, 0, getWidth(), getHeight(), false, Align.left, false);
|
||||
} else {
|
||||
float halfHeight = getHeight() / 2;
|
||||
float textStart = halfHeight + Utils.scale(1);
|
||||
float textWidth = getWidth() - textStart;
|
||||
int mod = 1;
|
||||
g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight);
|
||||
g.drawOutlinedText(lifeStr, INFO_FONT, INFO_FORE_COLOR.getColor(), Color.BLACK, textStart, 0, textWidth, halfHeight, false, Align.left, false);
|
||||
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 {
|
||||
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);
|
||||
} else {
|
||||
if (poisonCounters == 0 && energyCounters == 0) {
|
||||
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;
|
||||
y--;
|
||||
h++;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
h -= INFO_TAB_PADDING_Y;
|
||||
yAcross = h;
|
||||
y--;
|
||||
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 (isFlipped()) { //use clip to ensure all corners connect
|
||||
g.startClip(-1, y, w + 2, h);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
g.startClip(-1, y, w + 2, yAcross - y);
|
||||
}
|
||||
if (forMultiPlayer) {
|
||||
@@ -533,8 +576,7 @@ public class VPlayerPanel extends FContainer {
|
||||
if (lblLife.getRotate180()) {
|
||||
g.endTransform();
|
||||
}
|
||||
}
|
||||
else { //show image above text if taller than wide
|
||||
} else { //show image above text if taller than wide
|
||||
if (lblLife.getRotate180()) {
|
||||
g.startRotateTransform(getWidth() / 2, getHeight() / 2, 180);
|
||||
}
|
||||
|
||||
@@ -364,6 +364,7 @@ public class VStack extends FDropDown {
|
||||
float y = 0;
|
||||
float w = getWidth();
|
||||
float h = preferredHeight;
|
||||
CardView sourceCard = stackInstance.getSourceCard();
|
||||
|
||||
boolean needAlpha = (activeStackInstance != stackInstance);
|
||||
if (needAlpha) { //use alpha for non-active items on stack
|
||||
@@ -382,16 +383,17 @@ public class VStack extends FDropDown {
|
||||
|
||||
x += 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;
|
||||
w -= x + PADDING - BORDER_THICKNESS;
|
||||
h -= y + PADDING - BORDER_THICKNESS;
|
||||
|
||||
String name = stackInstance.getSourceCard().getName();
|
||||
String name = sourceCard.getName();
|
||||
int index = text.indexOf(name);
|
||||
String newtext = "";
|
||||
String cId = "(" + stackInstance.getSourceCard().getId() + ")";
|
||||
String cId = "(" + sourceCard.getId() + ")";
|
||||
String optionalCostString = !stackInstance.getOptionalCostString().equals("") ? " ("+ stackInstance.getOptionalCostString() + ")" : "";
|
||||
|
||||
if (index == -1) {
|
||||
newtext = TextUtil.fastReplace(TextUtil.fastReplace(text.trim(),"--","-"),"- -","-");
|
||||
@@ -405,7 +407,7 @@ public class VStack extends FDropDown {
|
||||
newtext = TextUtil.fastReplace(trimSecond, " "+cId, 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 {
|
||||
newtext = TextUtil.fastReplace(TextUtil.fastReplace(newtext,name+" -","-"), "\n ", "\n");
|
||||
newtext = "\n"+ TextUtil.fastReplace(newtext.trim(),"--","-");
|
||||
|
||||
@@ -21,6 +21,7 @@ import forge.FThreads;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.game.GameView;
|
||||
import forge.gauntlet.GauntletWinLoseController;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.gui.SOptionPane;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,17 +101,22 @@ public class ConquestRewardDialog extends FScrollPane {
|
||||
|
||||
float startX = x;
|
||||
int cardCount = cardRevealers.size();
|
||||
cardRevealers.get(0).setBounds(x, y, cardWidth, cardHeight);
|
||||
for (int i = 1; i < cardCount; i++) {
|
||||
if (i % columnCount == 0) {
|
||||
x = startX;
|
||||
y += cardHeight + PADDING;
|
||||
try {
|
||||
cardRevealers.get(0).setBounds(x, y, cardWidth, cardHeight);
|
||||
for (int i = 1; i < cardCount; i++) {
|
||||
if (i % columnCount == 0) {
|
||||
x = startX;
|
||||
y += cardHeight + PADDING;
|
||||
}
|
||||
else {
|
||||
x += cardWidth + PADDING;
|
||||
}
|
||||
cardRevealers.get(i).setBounds(x, y, cardWidth, cardHeight);
|
||||
}
|
||||
else {
|
||||
x += cardWidth + PADDING;
|
||||
}
|
||||
cardRevealers.get(i).setBounds(x, y, cardWidth, cardHeight);
|
||||
} catch (Exception ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
|
||||
return new ScrollBounds(visibleWidth, y + cardHeight + PADDING);
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,16 @@ public class SettingsPage extends TabPage<SettingsScreen> {
|
||||
localizer.getMessage("cbEscapeEndsTurn"),
|
||||
localizer.getMessage("nlEscapeEndsTurn")),
|
||||
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
|
||||
lstSettings.addItem(new BooleanSetting(FPref.DECKGEN_NOSMALL,
|
||||
|
||||
@@ -13,10 +13,13 @@ import forge.assets.FSkinProp;
|
||||
import forge.assets.IHasSkinProp;
|
||||
import forge.assets.TextRenderer;
|
||||
import forge.assets.FSkinColor.Colors;
|
||||
import forge.card.CardFaceSymbols;
|
||||
import forge.card.CardRenderer;
|
||||
import forge.card.CardZoom;
|
||||
import forge.card.CardRenderer.CardStackPosition;
|
||||
import forge.card.CardZoom.ActivateHandler;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.IHasCardView;
|
||||
import forge.game.player.PlayerView;
|
||||
@@ -29,8 +32,11 @@ import forge.itemmanager.filters.ItemFilter;
|
||||
import forge.screens.match.MatchController;
|
||||
import forge.screens.match.views.VAvatar;
|
||||
import forge.screens.match.views.VStack;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.Utils;
|
||||
|
||||
import static forge.card.CardRenderer.MANA_SYMBOL_SIZE;
|
||||
|
||||
public class FChoiceList<T> extends FList<T> implements ActivateHandler {
|
||||
public static final FSkinColor ITEM_COLOR = FSkinColor.get(Colors.CLR_ZEBRA);
|
||||
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
|
||||
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 {
|
||||
@@ -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) {
|
||||
CardView cv = ((IHasCardView)value).getCardView();
|
||||
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;
|
||||
x += dx;
|
||||
|
||||
@@ -16,6 +16,7 @@ import forge.screens.match.views.VPrompt;
|
||||
import forge.toolbox.FButton.Corner;
|
||||
import forge.toolbox.FEvent.FEventHandler;
|
||||
import forge.util.PhysicsObject;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.Utils;
|
||||
|
||||
public abstract class FDialog extends FOverlay {
|
||||
@@ -48,7 +49,7 @@ public abstract class FDialog extends FOverlay {
|
||||
buttonCount = buttonCount0;
|
||||
prompt = add(new VPrompt("", "", null, null));
|
||||
if (buttonCount < 3) {
|
||||
title0 = title0.replaceAll(" - ", "\n"); //breakup into lines
|
||||
title0 = TextUtil.fastReplace(title0," - ", "\n"); //breakup into lines
|
||||
btnMiddle = null;
|
||||
prompt.setMessage(title0); //only put title in message if no third button
|
||||
}
|
||||
|
||||
@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=500
|
||||
PREDICT_SPELLS_FOR_MAIN2=true
|
||||
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
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=400
|
||||
PREDICT_SPELLS_FOR_MAIN2=true
|
||||
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
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=350
|
||||
PREDICT_SPELLS_FOR_MAIN2=true
|
||||
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
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD=300
|
||||
PREDICT_SPELLS_FOR_MAIN2=true
|
||||
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
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true
|
||||
|
||||
|
||||
10
forge-gui/res/blockdata/chaosdraftthemes.txt
Normal file
10
forge-gui/res/blockdata/chaosdraftthemes.txt
Normal 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
|
||||
@@ -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$ 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$ 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.
|
||||
SVar:NonStackingEffect:True
|
||||
AI:RemoveDeck:Random
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Chromatic Orrery
|
||||
ManaCost:7
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -4,11 +4,9 @@ Types:Creature Djinn
|
||||
PT:4/4
|
||||
K:Flying
|
||||
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.
|
||||
SVar:DBPlayIT:DB$ Play | Defined$ Remembered | Controller$ You | WithoutManaCost$ True | Optional$ True | RememberPlayed$ True | SubAbility$ DBExileIfNotPlayed
|
||||
SVar:DBExileIfNotPlayed:DB$ ChangeZone | Origin$ Library | Destination$ Exile | Defined$ Remembered | DefinedPlayer$ You | ConditionCheckSVar$ DjinnX | ConditionSVarCompare$ EQ1 | SubAbility$ DBDjinnCleanup | References$ DjinnX
|
||||
SVar:DBDjinnCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:DjinnX:Remembered$Valid Card.IsRemembered
|
||||
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: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$ Imprinted | DefinedPlayer$ You | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup | StackDescription$ None
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
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.
|
||||
|
||||
@@ -5,8 +5,7 @@ A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ FDRep | StaticAbilities$ FDManaCo
|
||||
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: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:Random
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Inscription of Abundance
|
||||
ManaCost:1 G
|
||||
Types:Instant
|
||||
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: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.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Inscription of Insight
|
||||
ManaCost:3 U
|
||||
Types:Sorcery
|
||||
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:DBScry:DB$ Scry | ScryNum$ 2 | SubAbility$ DBDraw | SpellDescription$ Scry 2, then draw two cards.
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 2
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Inscription of Ruin
|
||||
ManaCost:2 B
|
||||
Types:Sorcery
|
||||
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: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.
|
||||
|
||||
@@ -3,12 +3,12 @@ ManaCost:1 U U
|
||||
Types:Legendary Planeswalker Jace
|
||||
Loyalty:4
|
||||
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
|
||||
A:AB$ Scry | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ScryNum$ 1 | SpellDescription$ Scry 1.
|
||||
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$ Scry | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ScryNum$ 2 | SpellDescription$ Scry 2.
|
||||
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:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:Remembered$CardManaCost
|
||||
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.
|
||||
|
||||
@@ -3,10 +3,10 @@ ManaCost:2 W
|
||||
Types:Enchantment Aura
|
||||
K:Enchant 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."
|
||||
SVar:LightOfPromiseTrig:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ LightOfPromisePutCounter | TriggerDescription$ Whenever you gain life, put that many +1/+1 counters on CARDNAME.
|
||||
SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | References$ X
|
||||
SVar:X:TriggerCount$LifeAmount
|
||||
DeckHints:Ability$LifeGain
|
||||
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 this creature.
|
||||
SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ LightOfPromiseAmount | References$ LightOfPromiseAmount
|
||||
SVar:LightOfPromiseAmount:TriggerCount$LifeAmount
|
||||
DeckNeeds:Ability$LifeGain
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:Enchant creature\nEnchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:6
|
||||
Types:Artifact
|
||||
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$ 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
|
||||
AI:RemoveDeck:Random
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/mycosynth_lattice.jpg
|
||||
|
||||
@@ -12,4 +12,5 @@ SVar:X:Count$xPaid
|
||||
SVar:Y:Count$RememberedSize
|
||||
SVar:XLands:Number$0
|
||||
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.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Kor Cleric
|
||||
PT:3/3
|
||||
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 | 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:X:TriggeredCard$CardManaCost
|
||||
DeckHas:Ability$Graveyard
|
||||
|
||||
@@ -2,9 +2,8 @@ Name:Rushing River
|
||||
ManaCost:2 U
|
||||
Types:Instant
|
||||
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:NeedsToPlayKickedVar:Z GE2
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Trinisphere
|
||||
ManaCost:3
|
||||
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
|
||||
AI:RemoveDeck:Random
|
||||
SVar:NonStackingEffect:True
|
||||
|
||||
8
forge-gui/res/cardsfolder/upcoming/amareth_the_lustrous.txt
Executable file
8
forge-gui/res/cardsfolder/upcoming/amareth_the_lustrous.txt
Executable 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.
|
||||
9
forge-gui/res/cardsfolder/upcoming/amphin_mutineer.txt
Executable file
9
forge-gui/res/cardsfolder/upcoming/amphin_mutineer.txt
Executable 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.)
|
||||
10
forge-gui/res/cardsfolder/upcoming/apex_devastator.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/apex_devastator.txt
Normal 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.)
|
||||
9
forge-gui/res/cardsfolder/upcoming/archon_of_coronation.txt
Executable file
9
forge-gui/res/cardsfolder/upcoming/archon_of_coronation.txt
Executable 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.
|
||||
9
forge-gui/res/cardsfolder/upcoming/aurora_phoenix.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/aurora_phoenix.txt
Normal 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
16
forge-gui/res/cardsfolder/upcoming/blim_comedic_genius.txt
Normal file
16
forge-gui/res/cardsfolder/upcoming/blim_comedic_genius.txt
Normal 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.
|
||||
@@ -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.)
|
||||
|
||||
@@ -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.)
|
||||
13
forge-gui/res/cardsfolder/upcoming/colfenor_the_last_yew.txt
Executable file
13
forge-gui/res/cardsfolder/upcoming/colfenor_the_last_yew.txt
Executable 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.
|
||||
11
forge-gui/res/cardsfolder/upcoming/court_of_ambition.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/court_of_ambition.txt
Normal 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 you’re 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 you’re the monarch, instead each opponent loses 6 life unless they discard two cards.
|
||||
10
forge-gui/res/cardsfolder/upcoming/court_of_cunning.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/court_of_cunning.txt
Normal 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.)
|
||||
12
forge-gui/res/cardsfolder/upcoming/emberwilde_captain.txt
Normal file
12
forge-gui/res/cardsfolder/upcoming/emberwilde_captain.txt
Normal 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.
|
||||
9
forge-gui/res/cardsfolder/upcoming/eyeblight_cullers.txt
Executable file
9
forge-gui/res/cardsfolder/upcoming/eyeblight_cullers.txt
Executable 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
Reference in New Issue
Block a user