Merge branch 'token_scripts' into 'master'

Allow scripts to be written for Tokens

See merge request core-developers/forge!236
This commit is contained in:
austinio7116
2018-02-27 22:14:13 +00:00
16 changed files with 633 additions and 263 deletions

View File

@@ -43,7 +43,7 @@ import java.util.zip.ZipFile;
import forge.util.BuildInfo; import forge.util.BuildInfo;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import com.google.common.io.Files;
import forge.card.CardRules; import forge.card.CardRules;
import forge.util.FileUtil; import forge.util.FileUtil;
import forge.util.Localizer; import forge.util.Localizer;
@@ -71,6 +71,7 @@ public class CardStorageReader {
} }
private static final String CARD_FILE_DOT_EXTENSION = ".txt"; private static final String CARD_FILE_DOT_EXTENSION = ".txt";
private static final String UPCOMING = "upcoming";
/** Default charset when loading from files. */ /** Default charset when loading from files. */
public static final String DEFAULT_CHARSET_NAME = "UTF-8"; public static final String DEFAULT_CHARSET_NAME = "UTF-8";
@@ -80,6 +81,7 @@ public class CardStorageReader {
private final ProgressObserver progressObserver; private final ProgressObserver progressObserver;
private final boolean loadingTokens;
private transient File cardsfolder; private transient File cardsfolder;
private transient ZipFile zip; private transient ZipFile zip;
@@ -91,6 +93,9 @@ public class CardStorageReader {
public CardStorageReader(final String cardDataDir, final CardStorageReader.ProgressObserver progressObserver, boolean loadCardsLazily) { public CardStorageReader(final String cardDataDir, final CardStorageReader.ProgressObserver progressObserver, boolean loadCardsLazily) {
this.progressObserver = progressObserver != null ? progressObserver : CardStorageReader.ProgressObserver.emptyObserver; this.progressObserver = progressObserver != null ? progressObserver : CardStorageReader.ProgressObserver.emptyObserver;
this.cardsfolder = new File(cardDataDir); this.cardsfolder = new File(cardDataDir);
this.loadingTokens = cardDataDir.contains("token");
this.loadCardsLazily = loadCardsLazily; this.loadCardsLazily = loadCardsLazily;
// These read data for lightweight classes. // These read data for lightweight classes.
@@ -240,6 +245,9 @@ public class CardStorageReader {
final Set<CardRules> result = new TreeSet<>(new Comparator<CardRules>() { final Set<CardRules> result = new TreeSet<>(new Comparator<CardRules>() {
@Override @Override
public int compare(final CardRules o1, final CardRules o2) { public int compare(final CardRules o1, final CardRules o2) {
if (loadingTokens) {
return String.CASE_INSENSITIVE_ORDER.compare(o1.getNormalizedName(), o2.getNormalizedName());
}
return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()); return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName());
} }
}); });
@@ -252,7 +260,7 @@ public class CardStorageReader {
if (!allFiles.isEmpty()) { if (!allFiles.isEmpty()) {
int fileParts = zip == null ? NUMBER_OF_PARTS : 1 + NUMBER_OF_PARTS / 3; int fileParts = zip == null ? NUMBER_OF_PARTS : 1 + NUMBER_OF_PARTS / 3;
if (allFiles.size() < fileParts * 100) { if (allFiles.size() < fileParts * 100) {
fileParts = allFiles.size() / 100; // to avoid creation of many threads for a dozen of files fileParts = Math.max(1, allFiles.size() / 100); // to avoid creation of many threads for a dozen of files
} }
final CountDownLatch cdlFiles = new CountDownLatch(fileParts); final CountDownLatch cdlFiles = new CountDownLatch(fileParts);
final List<Callable<List<CardRules>>> taskFiles = makeTaskListForFiles(allFiles, cdlFiles); final List<Callable<List<CardRules>>> taskFiles = makeTaskListForFiles(allFiles, cdlFiles);
@@ -377,7 +385,7 @@ public class CardStorageReader {
continue; continue;
} }
if (filename.equalsIgnoreCase("upcoming") && !BuildInfo.isDevelopmentVersion()) { if (filename.equalsIgnoreCase(CardStorageReader.UPCOMING) && !BuildInfo.isDevelopmentVersion()) {
// If upcoming folder exits, only load these cards on development builds // If upcoming folder exits, only load these cards on development builds
continue; continue;
} }
@@ -402,7 +410,7 @@ public class CardStorageReader {
fileInputStream = new FileInputStream(file); fileInputStream = new FileInputStream(file);
reader.reset(); reader.reset();
final List<String> lines = readScript(fileInputStream); final List<String> lines = readScript(fileInputStream);
return reader.readCard(lines); return reader.readCard(lines, Files.getNameWithoutExtension(file.getName()));
} catch (final FileNotFoundException ex) { } catch (final FileNotFoundException ex) {
throw new RuntimeException("CardReader : run error -- file not found: " + file.getPath(), ex); throw new RuntimeException("CardReader : run error -- file not found: " + file.getPath(), ex);
} finally { } finally {
@@ -430,7 +438,7 @@ public class CardStorageReader {
zipInputStream = this.zip.getInputStream(entry); zipInputStream = this.zip.getInputStream(entry);
rulesReader.reset(); rulesReader.reset();
return rulesReader.readCard(readScript(zipInputStream)); return rulesReader.readCard(readScript(zipInputStream), Files.getNameWithoutExtension(entry.getName()));
} catch (final IOException exn) { } catch (final IOException exn) {
throw new RuntimeException(exn); throw new RuntimeException(exn);
// PM // PM

View File

@@ -9,6 +9,7 @@ import forge.item.BoosterBox;
import forge.item.FatPack; import forge.item.FatPack;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.item.SealedProduct; import forge.item.SealedProduct;
import forge.token.TokenDb;
import forge.util.storage.IStorage; import forge.util.storage.IStorage;
import forge.util.storage.StorageBase; import forge.util.storage.StorageBase;
@@ -22,10 +23,13 @@ import java.util.*;
* @author Max * @author Max
*/ */
public class StaticData { public class StaticData {
private final CardStorageReader reader; private final CardStorageReader cardReader;
private final CardStorageReader tokenReader;
private final String blockDataFolder; private final String blockDataFolder;
private final CardDb commonCards; private final CardDb commonCards;
private final CardDb variantCards; private final CardDb variantCards;
private final TokenDb allTokens;
private final CardEdition.Collection editions; private final CardEdition.Collection editions;
// Loaded lazily: // Loaded lazily:
@@ -38,16 +42,22 @@ public class StaticData {
private static StaticData lastInstance = null; private static StaticData lastInstance = null;
public StaticData(CardStorageReader reader, String editionFolder, String blockDataFolder) { public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) {
this.reader = reader; this(cardReader, null, editionFolder, blockDataFolder);
}
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) {
this.cardReader = cardReader;
this.tokenReader = tokenReader;
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder))); this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
this.blockDataFolder = blockDataFolder; this.blockDataFolder = blockDataFolder;
lastInstance = this; lastInstance = this;
{
final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (CardRules card : reader.loadCards()) { for (CardRules card : cardReader.loadCards()) {
if (null == card) continue; if (null == card) continue;
final String cardName = card.getName(); final String cardName = card.getName();
@@ -66,6 +76,18 @@ public class StaticData {
variantCards.initialize(false, false); variantCards.initialize(false, false);
} }
{
final Map<String, CardRules> tokens = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (CardRules card : tokenReader.loadCards()) {
if (null == card) continue;
tokens.put(card.getNormalizedName(), card);
}
allTokens = new TokenDb(tokens, editions);
}
}
public static StaticData instance() { public static StaticData instance() {
return lastInstance; return lastInstance;
} }
@@ -91,7 +113,7 @@ public class StaticData {
PaperCard card = commonCards.getCard(cardName, setCode, artIndex); PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
if (card == null) { if (card == null) {
attemptToLoadCard(cardName, setCode); attemptToLoadCard(cardName, setCode);
commonCards.getCard(cardName, setCode, artIndex); card = commonCards.getCard(cardName, setCode, artIndex);
} }
if (card == null) { if (card == null) {
card = commonCards.getCard(cardName, setCode, -1); card = commonCards.getCard(cardName, setCode, -1);
@@ -105,7 +127,7 @@ public class StaticData {
public void attemptToLoadCard(String encodedCardName, String setCode) { public void attemptToLoadCard(String encodedCardName, String setCode) {
CardDb.CardRequest r = CardRequest.fromString(encodedCardName); CardDb.CardRequest r = CardRequest.fromString(encodedCardName);
String cardName = r.cardName; String cardName = r.cardName;
CardRules rules = reader.attemptToLoadCard(cardName, setCode); CardRules rules = cardReader.attemptToLoadCard(cardName, setCode);
if (rules != null) { if (rules != null) {
if (rules.isVariant()) { if (rules.isVariant()) {
variantCards.loadCard(cardName, rules); variantCards.loadCard(cardName, rules);
@@ -162,6 +184,8 @@ public class StaticData {
return variantCards; return variantCards;
} }
public TokenDb getAllTokens() { return allTokens; }
public PaperCard getCardByEditionDate(PaperCard card, Date editionDate) { public PaperCard getCardByEditionDate(PaperCard card, Date editionDate) {
PaperCard c = this.getCommonCards().getCardFromEdition(card.getName(), editionDate, CardDb.SetPreference.LatestCoreExp, card.getArtIndex()); PaperCard c = this.getCommonCards().getCardFromEdition(card.getName(), editionDate, CardDb.SetPreference.LatestCoreExp, card.getArtIndex());

View File

@@ -74,6 +74,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// NO GETTERS/SETTERS HERE! // NO GETTERS/SETTERS HERE!
public static class CardRequest { public static class CardRequest {
// TODO Move Request to its own class
public String cardName; public String cardName;
public String edition; public String edition;
public int artIndex; public int artIndex;

View File

@@ -26,6 +26,7 @@ public enum CardRarity {
Rare("R", "Rare"), Rare("R", "Rare"),
MythicRare("M", "Mythic Rare"), MythicRare("M", "Mythic Rare"),
Special("S", "Special"), // Timeshifted Special("S", "Special"), // Timeshifted
None("N", "None"), // Tokens
Unknown("?", "Unknown"); // In development Unknown("?", "Unknown"); // In development
public static final CardRarity[] FILTER_OPTIONS = new CardRarity[] { public static final CardRarity[] FILTER_OPTIONS = new CardRarity[] {

View File

@@ -34,6 +34,7 @@ import java.util.StringTokenizer;
* @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $ * @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $
*/ */
public final class CardRules implements ICardCharacteristics { public final class CardRules implements ICardCharacteristics {
private String normalizedName;
private CardSplitType splitType; private CardSplitType splitType;
private ICardFace mainPart; private ICardFace mainPart;
private ICardFace otherPart; private ICardFace otherPart;
@@ -124,6 +125,9 @@ public final class CardRules implements ICardCharacteristics {
} }
} }
public String getNormalizedName() { return normalizedName; }
public void setNormalizedName(String filename) { normalizedName = filename; }
public CardAiHints getAiHints() { public CardAiHints getAiHints() {
return aiHints; return aiHints;
} }
@@ -207,12 +211,6 @@ public final class CardRules implements ICardCharacteristics {
return meldWith; return meldWith;
} }
// public Set<String> getSets() { return this.setsPrinted.keySet(); }
// public CardInSet getEditionInfo(final String setCode) {
// final CardInSet result = this.setsPrinted.get(setCode);
// return result; // if returns null, String.format("Card '%s' was never printed in set '%s'", this.getName(), setCode);
// }
// vanguard card fields, they don't use sides. // vanguard card fields, they don't use sides.
private int deltaHand; private int deltaHand;
private int deltaLife; private int deltaLife;
@@ -260,6 +258,7 @@ public final class CardRules implements ICardCharacteristics {
private CardSplitType altMode = CardSplitType.None; private CardSplitType altMode = CardSplitType.None;
private String meldWith = ""; private String meldWith = "";
private String handLife = null; private String handLife = null;
private String normalizedName = "";
// fields to build CardAiHints // fields to build CardAiHints
private boolean removedFromAIDecks = false; private boolean removedFromAIDecks = false;
@@ -287,6 +286,7 @@ public final class CardRules implements ICardCharacteristics {
this.hints = null; this.hints = null;
this.has = null; this.has = null;
this.meldWith = ""; this.meldWith = "";
this.normalizedName = "";
} }
/** /**
@@ -299,6 +299,8 @@ public final class CardRules implements ICardCharacteristics {
faces[0].assignMissingFields(); faces[0].assignMissingFields();
if (null != faces[1]) faces[1].assignMissingFields(); if (null != faces[1]) faces[1].assignMissingFields();
final CardRules result = new CardRules(faces, altMode, cah); final CardRules result = new CardRules(faces, altMode, cah);
result.setNormalizedName(this.normalizedName);
result.meldWith = this.meldWith; result.meldWith = this.meldWith;
result.setDlUrls(pictureUrl); result.setDlUrls(pictureUrl);
if (StringUtils.isNotBlank(handLife)) if (StringUtils.isNotBlank(handLife))
@@ -306,7 +308,7 @@ public final class CardRules implements ICardCharacteristics {
return result; return result;
} }
public final CardRules readCard(final Iterable<String> script) { public final CardRules readCard(final Iterable<String> script, String filename) {
this.reset(); this.reset();
for (String line : script) { for (String line : script) {
if (line.isEmpty() || line.charAt(0) == '#') { if (line.isEmpty() || line.charAt(0) == '#') {
@@ -314,9 +316,14 @@ public final class CardRules implements ICardCharacteristics {
} }
this.parseLine(line); this.parseLine(line);
} }
this.normalizedName = filename;
return this.getCard(); return this.getCard();
} }
public final CardRules readCard(final Iterable<String> script) {
return readCard(script, null);
}
/** /**
* Parses the line. * Parses the line.
* *

View File

@@ -4,10 +4,12 @@ import forge.ImageKeys;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.card.CardRarity; import forge.card.CardRarity;
import forge.card.CardRules; import forge.card.CardRules;
import forge.card.ColorSet;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
public class PaperToken implements InventoryItemFromSet, IPaperCard { public class PaperToken implements InventoryItemFromSet, IPaperCard {
private String name; private String name;
private CardEdition edition; private CardEdition edition;
@@ -29,21 +31,91 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return out.toString().toLowerCase(Locale.ENGLISH); return out.toString().toLowerCase(Locale.ENGLISH);
} }
public static String makeTokenFileName(String colors, int power, int toughness, String name) { public static String makeTokenFileName(String colors, String power, String toughness, String types) {
return makeTokenFileName(colors, String.valueOf(power), String.valueOf(toughness), name); return makeTokenFileName(null, colors, power, toughness, types);
} }
public static String makeTokenFileName(String colors, String power, String toughness, String name) { public static String makeTokenFileName(String name, String colors, String power, String toughness, String types) {
StringBuilder fileName = new StringBuilder(); ArrayList<String> build = new ArrayList<>();
fileName.append(colors).append('_').append(power).append('_').append(toughness).append('_').append(name); if (name != null) {
return makeTokenFileName(fileName.toString()); build.add(name);
} }
public PaperToken(final CardRules c, CardEdition edition0, final String imageFileName) { build.add(colors);
if (power != null && toughness != null) {
build.add(power);
build.add(toughness);
}
build.add(types);
String fileName = StringUtils.join(build, "_");
return makeTokenFileName(fileName);
}
public static String makeTokenFileName(final CardRules rules, CardEdition edition) {
ArrayList<String> build = new ArrayList<>();
String subtypes = StringUtils.join(rules.getType().getSubtypes(), " ");
if (!rules.getName().equals(subtypes)) {
return makeTokenFileName(rules.getName());
}
ColorSet colors = rules.getColor();
if (colors.isColorless()) {
build.add("C");
} else {
String color = "";
if (colors.hasWhite()) color += "W";
if (colors.hasBlue()) color += "U";
if (colors.hasBlack()) color += "B";
if (colors.hasRed()) color += "R";
if (colors.hasGreen()) color += "G";
build.add(color);
}
if (rules.getPower() != null && rules.getToughness() != null) {
build.add(rules.getPower());
build.add(rules.getToughness());
}
String cardTypes = "";
if (rules.getType().isArtifact()) cardTypes += "A";
if (rules.getType().isEnchantment()) cardTypes += "E";
if (!cardTypes.isEmpty()) {
build.add(cardTypes);
}
build.add(subtypes);
// Are these keywords sorted?
for(String keyword : rules.getMainPart().getKeywords()) {
build.add(keyword);
}
build.add(edition.getCode());
// Should future image file names be all lower case? Instead of Up case sets?
return StringUtils.join(build, "_").toLowerCase();
}
public PaperToken(final CardRules c) { this(c, null, null); }
public PaperToken(final CardRules c, final String fileName) { this(c, null, fileName); }
public PaperToken(final CardRules c, CardEdition edition) { this(c, edition, null); }
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
this.card = c; this.card = c;
this.name = c.getName(); this.name = c.getName();
this.edition = edition0; this.edition = edition0;
this.imageFileName = String.format("%s%s", null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode(), imageFileName);
if (imageFileName == null) {
this.imageFileName = makeTokenFileName(c, edition0);
} else {
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode();
this.imageFileName = String.format("%s%s", formatEdition, imageFileName);
}
} }
@Override public String getName() { return name; } @Override public String getName() { return name; }
@@ -54,7 +126,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
@Override public boolean isFoil() { return false; } @Override public boolean isFoil() { return false; }
@Override public CardRules getRules() { return card; } @Override public CardRules getRules() { return card; }
@Override public CardRarity getRarity() { return CardRarity.Common; } // They don't have rarity though! @Override public CardRarity getRarity() { return CardRarity.None; }
// Unfortunately this is a property of token, cannot move it outside of class // Unfortunately this is a property of token, cannot move it outside of class
public String getImageFilename() { return imageFileName; } public String getImageFilename() { return imageFileName; }

View File

@@ -0,0 +1,32 @@
package forge.token;
import com.google.common.base.Predicate;
import forge.card.CardDb;
import forge.item.PaperToken;
import java.util.Collection;
import java.util.Date;
import java.util.List;
public interface ITokenDatabase extends Iterable<PaperToken> {
PaperToken getToken(String tokenName);
PaperToken getToken(String tokenName, String edition);
PaperToken getToken(String tokenName, String edition, int artIndex);
PaperToken getTokenFromEdition(String tokenName, CardDb.SetPreference fromSet);
PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet);
PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet, int artIndex);
PaperToken getFoiled(PaperToken cpi);
int getPrintCount(String tokenName, String edition);
int getMaxPrintCount(String tokenName);
int getArtCount(String tokenName, String edition);
Collection<PaperToken> getUniqueTokens();
List<PaperToken> getAllTokens();
List<PaperToken> getAllTokens(String tokenName);
List<PaperToken> getAllTokens(Predicate<PaperToken> predicate);
Predicate<? super PaperToken> wasPrintedInSets(List<String> allowedSetCodes);
}

View File

@@ -0,0 +1,121 @@
package forge.token;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import forge.card.*;
import forge.item.PaperCard;
import forge.item.PaperToken;
import java.util.*;
public class TokenDb implements ITokenDatabase {
// Expected naming convention of scripts
// token_name
// minor_demon
// marit_lage
// gold
// colors_power_toughness_cardtypes_sub_types_keywords
// Some examples:
// c_3_3_a_wurm_lifelink
// w_2_2_knight_first_strike
// The image names should be the same as the script name + _set
// If that isn't found, consider falling back to the original token
private final Map<String, PaperToken> tokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final CardEdition.Collection editions;
private final Map<String, CardRules> rulesByName;
public TokenDb(Map<String, CardRules> rules, CardEdition.Collection editions) {
this.rulesByName = rules;
this.editions = editions;
}
@Override
public PaperToken getToken(String tokenName) {
return getToken(tokenName, CardEdition.UNKNOWN.getName());
}
@Override
public PaperToken getToken(String tokenName, String edition) {
try {
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
// TODO Cache the token after it's referenced
return pt;
} catch(Exception e) {
return null;
}
}
@Override
public PaperToken getToken(String tokenName, String edition, int artIndex) {
return null;
}
@Override
public PaperToken getTokenFromEdition(String tokenName, CardDb.SetPreference fromSet) {
return null;
}
@Override
public PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet) {
return null;
}
@Override
public PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet, int artIndex) {
return null;
}
@Override
public PaperToken getFoiled(PaperToken cpi) {
return null;
}
@Override
public int getPrintCount(String cardName, String edition) {
return 0;
}
@Override
public int getMaxPrintCount(String cardName) {
return 0;
}
@Override
public int getArtCount(String cardName, String edition) {
return 0;
}
@Override
public Collection<PaperToken> getUniqueTokens() {
return null;
}
@Override
public List<PaperToken> getAllTokens() {
return null;
}
@Override
public List<PaperToken> getAllTokens(String tokenName) {
return null;
}
@Override
public List<PaperToken> getAllTokens(Predicate<PaperToken> predicate) {
return null;
}
@Override
public Predicate<? super PaperToken> wasPrintedInSets(List<String> allowedSetCodes) {
return null;
}
@Override
public Iterator<PaperToken> iterator() {
return null;
}
}

View File

@@ -18,10 +18,13 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import forge.StaticData;
import forge.card.MagicColor;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -69,7 +72,23 @@ public class TokenEffect extends SpellAbilityEffect {
private String[] tokenKeywords; private String[] tokenKeywords;
private String[] tokenHiddenKeywords; private String[] tokenHiddenKeywords;
private void readParameters(final SpellAbility mapParams) { private void readMetaParams(final SpellAbility mapParams) {
this.tokenTapped = mapParams.getParamOrDefault("TokenTapped", "False").equalsIgnoreCase("True");
this.tokenAttacking = mapParams.getParamOrDefault("TokenAttacking", "False").equalsIgnoreCase("True");
this.tokenBlocking = mapParams.getParam("TokenBlocking");
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
this.tokenOwner = mapParams.getParamOrDefault("TokenOwner", "You");
}
private void readParameters(final SpellAbility mapParams, Card prototype) {
readMetaParams(mapParams);
if (prototype == null) {
readTokenParams(mapParams);
}
}
private void readTokenParams(final SpellAbility mapParams) {
String image; String image;
String[] keywords; String[] keywords;
@@ -99,9 +118,6 @@ public class TokenEffect extends SpellAbilityEffect {
this.tokenAltImages = null; this.tokenAltImages = null;
} }
this.tokenTapped = mapParams.hasParam("TokenTapped") && mapParams.getParam("TokenTapped").equals("True");
this.tokenAttacking = mapParams.hasParam("TokenAttacking") && mapParams.getParam("TokenAttacking").equals("True");
if (mapParams.hasParam("TokenAbilities")) { if (mapParams.hasParam("TokenAbilities")) {
this.tokenAbilities = mapParams.getParam("TokenAbilities").split(","); this.tokenAbilities = mapParams.getParam("TokenAbilities").split(",");
} else { } else {
@@ -122,8 +138,7 @@ public class TokenEffect extends SpellAbilityEffect {
} else { } else {
this.tokenStaticAbilities = null; this.tokenStaticAbilities = null;
} }
this.tokenBlocking = mapParams.getParam("TokenBlocking");
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
this.tokenPower = mapParams.getParam("TokenPower"); this.tokenPower = mapParams.getParam("TokenPower");
this.tokenToughness = mapParams.getParam("TokenToughness"); this.tokenToughness = mapParams.getParam("TokenToughness");
@@ -143,19 +158,19 @@ public class TokenEffect extends SpellAbilityEffect {
this.tokenKeywords = keywords; this.tokenKeywords = keywords;
this.tokenImage = image; this.tokenImage = image;
if (mapParams.hasParam("TokenOwner")) {
this.tokenOwner = mapParams.getParam("TokenOwner");
} else {
this.tokenOwner = "You";
}
} }
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
Card prototype = loadTokenPrototype(sa);
readParameters(sa); if (prototype != null) {
return sa.getDescription();
}
readParameters(sa, prototype);
final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa);
final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa);
@@ -181,11 +196,25 @@ public class TokenEffect extends SpellAbilityEffect {
return sb.toString(); return sb.toString();
} }
private Card loadTokenPrototype(SpellAbility sa) {
String script = sa.getParamOrDefault("TokenScript", null);
String edition = sa.getHostCard().getPaperCard().getEdition();
PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition);
if (token != null) {
tokenName = token.getName();
return Card.fromPaperCard(token, null, sa.getHostCard().getGame());
}
return null;
}
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame();
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
readParameters(sa);
// Cause of the Token Effect, in general it should be this // Cause of the Token Effect, in general it should be this
// but if its a Replacement Effect, it might be something else or null // but if its a Replacement Effect, it might be something else or null
@@ -194,30 +223,26 @@ public class TokenEffect extends SpellAbilityEffect {
cause = (SpellAbility)root.getReplacingObject("Cause"); cause = (SpellAbility)root.getReplacingObject("Cause");
} }
String cost = ""; final boolean remember = sa.hasParam("RememberTokens");
final boolean imprint = sa.hasParam("ImprintTokens");
final List<Card> allTokens = Lists.newArrayList();
// Construct original colors boolean combatChanged = false;
String originalColorDesc = ""; boolean inCombat = game.getPhaseHandler().inCombat();
for (final String col : this.tokenOriginalColors) {
if (col.equalsIgnoreCase("White")) { Card prototype = loadTokenPrototype(sa);
originalColorDesc += "W ";
} else if (col.equalsIgnoreCase("Blue")) { readParameters(sa, prototype);
originalColorDesc += "U "; final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa);
} else if (col.equalsIgnoreCase("Black")) {
originalColorDesc += "B "; TokenInfo tokenInfo;
} else if (col.equalsIgnoreCase("Red")) {
originalColorDesc += "R "; if (prototype == null) {
} else if (col.equalsIgnoreCase("Green")) { String originalColorDesc = parseColorForImage();
originalColorDesc += "G ";
} else if (col.equalsIgnoreCase("Colorless")) {
originalColorDesc = "C";
}
}
final List<String> imageNames = Lists.newArrayListWithCapacity(1); final List<String> imageNames = Lists.newArrayListWithCapacity(1);
if (this.tokenImage.equals("")) { if (this.tokenImage.equals("")) {
imageNames.add(PaperToken.makeTokenFileName(TextUtil.fastReplace(originalColorDesc, imageNames.add(PaperToken.makeTokenFileName(originalColorDesc, tokenPower, tokenToughness, tokenOriginalName));
" ", ""), tokenPower, tokenToughness, tokenOriginalName));
} else { } else {
imageNames.add(0, this.tokenImage); imageNames.add(0, this.tokenImage);
} }
@@ -225,38 +250,10 @@ public class TokenEffect extends SpellAbilityEffect {
imageNames.addAll(Arrays.asList(this.tokenAltImages)); imageNames.addAll(Arrays.asList(this.tokenAltImages));
} }
final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length); String cost = determineTokenColor(host);
for (int i = 0; i < substitutedColors.length; i++) {
if (substitutedColors[i].equals("ChosenColor")) {
// this currently only supports 1 chosen color
substitutedColors[i] = host.getChosenColor();
}
}
String colorDesc = "";
for (final String col : substitutedColors) {
if (col.equalsIgnoreCase("White")) {
colorDesc += "W ";
} else if (col.equalsIgnoreCase("Blue")) {
colorDesc += "U ";
} else if (col.equalsIgnoreCase("Black")) {
colorDesc += "B ";
} else if (col.equalsIgnoreCase("Red")) {
colorDesc += "R ";
} else if (col.equalsIgnoreCase("Green")) {
colorDesc += "G ";
} else if (col.equalsIgnoreCase("Colorless")) {
colorDesc = "C";
}
}
for (final char c : colorDesc.toCharArray()) {
cost += c + ' ';
}
cost = colorDesc.replace('C', '1').trim();
final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa);
final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa);
final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa);
final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length); final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length);
for (int i = 0; i < substitutedTypes.length; i++) { for (int i = 0; i < substitutedTypes.length; i++) {
@@ -266,120 +263,44 @@ public class TokenEffect extends SpellAbilityEffect {
} }
final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName;
final boolean remember = sa.hasParam("RememberTokens");
final boolean imprint = sa.hasParam("ImprintTokens");
final List<Card> allTokens = Lists.newArrayList();
for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) {
final Game game = controller.getGame();
for (int i = 0; i < finalAmount; i++) {
final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size()));
final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName, tokenInfo = new TokenInfo(substitutedName, imageName,
cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness); cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness);
final List<Card> tokens = tokenInfo.makeTokenWithMultiplier(controller, cause != null); } else {
tokenInfo = new TokenInfo(prototype);
// Grant rule changes
if (this.tokenHiddenKeywords != null) {
for (final String s : this.tokenHiddenKeywords) {
for (final Card c : tokens) {
c.addHiddenExtrinsicKeyword(s);
}
}
} }
// Grant SVars for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) {
if (this.tokenSVars != null) { List<Card> tokens;
for (final String s : this.tokenSVars) {
String actualSVar = AbilityUtils.getSVar(root, s);
String name = s;
if (actualSVar.startsWith("SVar")) {
actualSVar = actualSVar.split("SVar:")[1];
name = actualSVar.split(":")[0];
actualSVar = actualSVar.split(":")[1];
}
for (final Card c : tokens) {
c.setSVar(name, actualSVar);
}
}
}
// Grant abilities if (prototype == null) {
if (this.tokenAbilities != null) { tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null);
for (final String s : this.tokenAbilities) { grantHiddenKeywords(tokens);
final String actualAbility = AbilityUtils.getSVar(root, s); grantSvars(tokens, root);
for (final Card c : tokens) { grantAbilities(tokens, root);
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); grantTriggers(tokens, root);
// Set intrinsic, so that effects like Clone will copy these. grantStatics(tokens, root);
grantedAbility.setIntrinsic(true); } else {
c.addSpellAbility(grantedAbility); tokens = TokenInfo.makeTokensFromPrototype(prototype, controller, finalAmount, cause != null);
}
}
}
// Grant triggers
if (this.tokenTriggers != null) {
for (final String s : this.tokenTriggers) {
final String actualTrigger = AbilityUtils.getSVar(root, s);
for (final Card c : tokens) {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true);
final String ability = AbilityUtils.getSVar(root, parsedTrigger.getMapParams().get("Execute"));
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c));
c.addTrigger(parsedTrigger);
}
}
}
// Grant static abilities
if (this.tokenStaticAbilities != null) {
for (final String s : this.tokenStaticAbilities) {
final String actualAbility = AbilityUtils.getSVar(root, s);
for (final Card c : tokens) {
c.addStaticAbility(actualAbility);
}
}
} }
for (Card tok : tokens) { for (Card tok : tokens) {
if (this.tokenTapped) { if (this.tokenTapped) {
tok.setTapped(true); tok.setTapped(true);
} }
game.getAction().moveToPlay(tok, sa); // Should this be catching the Card that's returned?
} Card c = game.getAction().moveToPlay(tok, sa);
game.fireEvent(new GameEventTokenCreated());
boolean combatChanged = false;
for (final Card c : tokens) {
if (sa.hasParam("AtEOTTrig")) { if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c); addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
} }
// To show the Abilities and Trigger on the Token
if (inCombat) {
combatChanged = addTokenToCombat(game, c, controller, sa, host) || combatChanged;
}
c.updateStateForView(); c.updateStateForView();
if (this.tokenAttacking && game.getPhaseHandler().inCombat()) {
final Combat combat = game.getCombat();
// into battlefield attacking only should work if you are the attacking player
if (combat.getAttackingPlayer().equals(controller)) {
final FCollectionView<GameEntity> defs = combat.getDefenders();
final GameEntity defender = controller.getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false);
combat.addAttacker(c, defender);
combatChanged = true;
}
}
if (this.tokenBlocking != null && game.getPhaseHandler().inCombat()) {
final Combat combat = game.getPhaseHandler().getCombat();
final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null);
if (attacker != null) {
final boolean wasBlocked = combat.isBlocked(attacker);
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
// Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) {
combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c);
}
combatChanged = true;
}
}
if (remember) { if (remember) {
game.getCardState(sa.getHostCard()).addRemembered(c); game.getCardState(sa.getHostCard()).addRemembered(c);
} }
@@ -396,19 +317,154 @@ public class TokenEffect extends SpellAbilityEffect {
token.addRemembered(o); token.addRemembered(o);
} }
} }
allTokens.add(c);
} }
if (tokenName.equals("Clue")) { // investigate trigger if ("Clue".equals(tokenName)) { // investigate trigger
controller.addInvestigatedThisTurn(); controller.addInvestigatedThisTurn();
} }
}
game.fireEvent(new GameEventTokenCreated());
if (combatChanged) { if (combatChanged) {
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
allTokens.addAll(tokens);
}
}
if (sa.hasParam("AtEOT")) { if (sa.hasParam("AtEOT")) {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens); registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
} }
} }
private String parseColorForImage() {
String originalColorDesc = "";
for (final String col : this.tokenOriginalColors) {
originalColorDesc += MagicColor.toShortString(col);
if (originalColorDesc.equals("C")) {
return originalColorDesc;
}
}
return originalColorDesc;
}
private String determineTokenColor(Card host) {
Set<String> colorSet = new HashSet<>();
final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length);
for (int i = 0; i < substitutedColors.length; i++) {
if (substitutedColors[i].equals("ChosenColor")) {
// this currently only supports 1 chosen color
substitutedColors[i] = host.getChosenColor();
}
}
StringBuilder sb = new StringBuilder();
for (final String col : substitutedColors) {
String str = MagicColor.toShortString(col);
if (str.equals("C")) {
return "1";
}
sb.append(str).append(" ");
}
return sb.toString().trim();
}
private void grantHiddenKeywords(List<Card> tokens) {
// Grant rule changes
if (this.tokenHiddenKeywords != null) {
for (final String s : this.tokenHiddenKeywords) {
for (final Card c : tokens) {
c.addHiddenExtrinsicKeyword(s);
}
}
}
}
private void grantAbilities(List<Card> tokens, SpellAbility root) {
if (this.tokenAbilities != null) {
for (final String s : this.tokenAbilities) {
final String actualAbility = AbilityUtils.getSVar(root, s);
for (final Card c : tokens) {
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c);
// Set intrinsic, so that effects like Clone will copy these.
grantedAbility.setIntrinsic(true);
c.addSpellAbility(grantedAbility);
}
}
}
}
private void grantTriggers(List<Card> tokens, SpellAbility root) {
if (this.tokenTriggers != null) {
for (final String s : this.tokenTriggers) {
final String actualTrigger = AbilityUtils.getSVar(root, s);
for (final Card c : tokens) {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true);
final String ability = AbilityUtils.getSVar(root, parsedTrigger.getMapParams().get("Execute"));
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c));
c.addTrigger(parsedTrigger);
}
}
}
}
private void grantSvars(List<Card> tokens, SpellAbility root) {
if (this.tokenSVars != null) {
for (final String s : this.tokenSVars) {
String actualSVar = AbilityUtils.getSVar(root, s);
String name = s;
if (actualSVar.startsWith("SVar")) {
actualSVar = actualSVar.split("SVar:")[1];
name = actualSVar.split(":")[0];
actualSVar = actualSVar.split(":")[1];
}
for (final Card c : tokens) {
c.setSVar(name, actualSVar);
}
}
}
}
private void grantStatics(List<Card> tokens, SpellAbility root) {
if (this.tokenStaticAbilities != null) {
for (final String s : this.tokenStaticAbilities) {
final String actualAbility = AbilityUtils.getSVar(root, s);
for (final Card c : tokens) {
c.addStaticAbility(actualAbility);
}
}
}
}
private boolean addTokenToCombat(Game game, Card c, Player controller, SpellAbility sa, Card host) {
boolean combatChanged = false;
if (this.tokenAttacking) {
final Combat combat = game.getCombat();
// into battlefield attacking only should work if you are the attacking player
if (combat.getAttackingPlayer().equals(controller)) {
final FCollectionView<GameEntity> defs = combat.getDefenders();
final GameEntity defender = controller.getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false);
combat.addAttacker(c, defender);
combatChanged = true;
}
}
if (this.tokenBlocking != null) {
final Combat combat = game.getPhaseHandler().getCombat();
final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null);
if (attacker != null) {
final boolean wasBlocked = combat.isBlocked(attacker);
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
// Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) {
combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c);
}
combatChanged = true;
}
}
return combatChanged;
}
} }

View File

@@ -5405,6 +5405,9 @@ public class Card extends GameEntity implements Comparable<Card> {
public static Card fromPaperCard(IPaperCard pc, Player owner) { public static Card fromPaperCard(IPaperCard pc, Player owner) {
return CardFactory.getCard(pc, owner, owner == null ? null : owner.getGame()); return CardFactory.getCard(pc, owner, owner == null ? null : owner.getGame());
} }
public static Card fromPaperCard(IPaperCard pc, Player owner, Game game) {
return CardFactory.getCard(pc, owner, game);
}
private static final Map<PaperCard, Card> cp2card = Maps.newHashMap(); private static final Map<PaperCard, Card> cp2card = Maps.newHashMap();
public static Card getCardForUi(IPaperCard pc) { public static Card getCardForUi(IPaperCard pc) {

View File

@@ -8,6 +8,7 @@ import forge.ImageKeys;
import forge.card.CardType; import forge.card.CardType;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
@@ -133,10 +134,7 @@ public class TokenInfo {
return sb.toString(); return sb.toString();
} }
public List<Card> makeTokenWithMultiplier(final Player controller, final boolean applyMultiplier) { static private int calculateMultiplier(final Game game, final Player controller, final boolean applyMultiplier) {
final List<Card> list = Lists.newArrayList();
final Game game = controller.getGame();
int multiplier = 1; int multiplier = 1;
Player player = controller; Player player = controller;
@@ -155,12 +153,38 @@ public class TokenInfo {
break; break;
} }
default: default:
return 0;
}
return multiplier;
}
public List<Card> makeTokenWithMultiplier(final Player controller, int amount, final boolean applyMultiplier) {
final List<Card> list = Lists.newArrayList();
final Game game = controller.getGame();
int multiplier = calculateMultiplier(game, controller, applyMultiplier);
for (int i = 0; i < multiplier * amount; i++) {
list.add(makeOneToken(controller));
}
return list; return list;
} }
for (int i = 0; i < multiplier; i++) { static public List<Card> makeTokensFromPrototype(Card prototype, final Player controller, int amount, final boolean applyMultiplier) {
list.add(makeOneToken(player)); final List<Card> list = Lists.newArrayList();
final Game game = controller.getGame();
int multiplier = calculateMultiplier(game, controller, applyMultiplier);
long timestamp = game.getNextTimestamp();
prototype.setController(controller, timestamp);
for (int i = 0; i < multiplier * amount; i++) {
Card copy = CardFactory.copyCard(prototype, true);
copy.setTimestamp(timestamp);
copy.setOwner(controller);
copy.setToken(true);
list.add(copy);
} }
return list; return list;
} }

View File

@@ -0,0 +1,6 @@
Name:Wurm
ManaCost:no cost
Types:Artifact Creature Wurm
PT:3/3
K:Deathtouch
Oracle:Deathtouch

View File

@@ -0,0 +1,6 @@
Name:Wurm
ManaCost:no cost
Types:Artifact Creature Wurm
PT:3/3
K:Lifelink
Oracle:Lifelink

View File

@@ -0,0 +1,6 @@
Name:Voja
ManaCost:no cost
Types:Legendary Creature Wolf
Colors:white,green
PT:2/2
Oracle:

View File

@@ -145,7 +145,9 @@ public final class FModel {
final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge, final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge,
FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY)); FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
magicDb = new StaticData(reader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR); final CardStorageReader tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge,
FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
magicDb = new StaticData(reader, tokenReader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR);
//create profile dirs if they don't already exist //create profile dirs if they don't already exist
for (final String dname : ForgeConstants.PROFILE_DIRS) { for (final String dname : ForgeConstants.PROFILE_DIRS) {

View File

@@ -58,6 +58,7 @@ public final class ForgeConstants {
public static final String DRAFT_RANKINGS_FILE = DRAFT_DIR + "rankings.txt"; public static final String DRAFT_RANKINGS_FILE = DRAFT_DIR + "rankings.txt";
public static final String SEALED_DIR = RES_DIR + "sealed" + PATH_SEPARATOR; public static final String SEALED_DIR = RES_DIR + "sealed" + PATH_SEPARATOR;
public static final String CARD_DATA_DIR = RES_DIR + "cardsfolder" + PATH_SEPARATOR; public static final String CARD_DATA_DIR = RES_DIR + "cardsfolder" + PATH_SEPARATOR;
public static final String TOKEN_DATA_DIR = RES_DIR + "tokenscripts" + PATH_SEPARATOR;
public static final String EDITIONS_DIR = RES_DIR + "editions" + PATH_SEPARATOR; public static final String EDITIONS_DIR = RES_DIR + "editions" + PATH_SEPARATOR;
public static final String BLOCK_DATA_DIR = RES_DIR + "blockdata" + PATH_SEPARATOR; public static final String BLOCK_DATA_DIR = RES_DIR + "blockdata" + PATH_SEPARATOR;
public static final String DECK_CUBE_DIR = RES_DIR + "cube" + PATH_SEPARATOR; public static final String DECK_CUBE_DIR = RES_DIR + "cube" + PATH_SEPARATOR;