Merge remote-tracking branch 'upstream/master' into unicode_deck_name

This commit is contained in:
CCTV-1
2021-03-06 10:30:21 +08:00
7904 changed files with 142240 additions and 40359 deletions

View File

@@ -6,6 +6,8 @@ import forge.util.ImageUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
@@ -23,6 +25,7 @@ public final class ImageKeys {
public static final String HIDDEN_CARD = "hidden";
public static final String MORPH_IMAGE = "morph";
public static final String MANIFEST_IMAGE = "manifest";
public static final String FORETELL_IMAGE = "foretell";
public static final String BACKFACE_POSTFIX = "$alt";
@@ -113,7 +116,17 @@ public final class ImageKeys {
}
//try fullborder...
if (filename.contains(".full")) {
file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder"));
String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
file = findFile(dir, fullborderFile);
if (file != null) { return file; }
// if there's a 1st art variant try without it for .fullborder images
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
if (file != null) { return file; }
// if there's an art variant try without it for .full images
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
if (file != null) { return file; }
// if there's a 1st art variant try with it for .full images
file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
if (file != null) { return file; }
}
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
@@ -138,6 +151,9 @@ public final class ImageKeys {
// try with upper case set
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
if (file != null) { return file; }
// try with lower case set
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
if (file != null) { return file; }
// try without set name
file = findFile(dir, setlessFilename);
if (file != null) { return file; }
@@ -152,8 +168,12 @@ public final class ImageKeys {
file = findFile(dir, setlessFilename);
if (file != null) { return file; }
// try lowering the art index to the minimum for regular cards
if (setlessFilename.contains(".full")) {
//try fullborder
String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder");
file = findFile(dir, fullborderFile);
if (file != null) { return file; }
// try lowering the art index to the minimum for regular cards
file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
if (file != null) { return file; }
}

View File

@@ -54,11 +54,11 @@ public class StaticData {
private static StaticData lastInstance = null;
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) {
this(cardReader, null, editionFolder, blockDataFolder);
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards, boolean loadNonLegalCards) {
this(cardReader, null, editionFolder, blockDataFolder, enableUnknownCards, loadNonLegalCards);
}
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) {
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards, boolean loadNonLegalCards) {
this.cardReader = cardReader;
this.tokenReader = tokenReader;
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
@@ -84,8 +84,8 @@ public class StaticData {
variantCards = new CardDb(variantsCards, editions);
//must initialize after establish field values for the sake of card image logic
commonCards.initialize(false, false);
variantCards.initialize(false, false);
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
}
{
@@ -149,6 +149,7 @@ public class StaticData {
}
}
// TODO Remove these in favor of them being associated to the Edition
/** @return {@link forge.util.storage.IStorage}<{@link forge.item.SealedProduct.Template}> */
public IStorage<FatPack.Template> getFatPacks() {
if (fatPacks == null)
@@ -156,12 +157,6 @@ public class StaticData {
return fatPacks;
}
public IStorage<BoosterBox.Template> getBoosterBoxes() {
if (boosterBoxes == null)
boosterBoxes = new StorageBase<>("Booster boxes", new BoosterBox.Template.Reader(blockDataFolder + "boosterboxes.txt"));
return boosterBoxes;
}
/** @return {@link forge.util.storage.IStorage}<{@link forge.item.SealedProduct.Template}> */
public final IStorage<SealedProduct.Template> getTournamentPacks() {
if (tournaments == null)
@@ -184,7 +179,7 @@ public class StaticData {
public IStorage<PrintSheet> getPrintSheets() {
if (printSheets == null)
printSheets = new StorageBase<>("Special print runs", new PrintSheet.Reader(new File(blockDataFolder, "printsheets.txt")));
printSheets = PrintSheet.initializePrintSheets(new File(blockDataFolder, "printsheets.txt"), getEditions());
return printSheets;
}
@@ -215,7 +210,7 @@ public class StaticData {
public Predicate<PaperCard> getStandardPredicate() { return standardPredicate; }
public Predicate<PaperCard> getPioneerPredicate() { return pioneerPredicate; }
public Predicate<PaperCard> getModernPredicate() { return modernPredicate; }
public Predicate<PaperCard> getCommanderPredicate() { return commanderPredicate; }

View File

@@ -9,15 +9,17 @@ public class CardAiHints {
private final boolean isRemovedFromAIDecks;
private final boolean isRemovedFromRandomDecks;
private final boolean isRemovedFromNonCommanderDecks;
private final DeckHints deckHints;
private final DeckHints deckNeeds;
private final DeckHints deckHas;
public CardAiHints(boolean remAi, boolean remRandom, DeckHints dh, DeckHints dn, DeckHints has) {
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
isRemovedFromAIDecks = remAi;
isRemovedFromRandomDecks = remRandom;
isRemovedFromNonCommanderDecks = remUnlessCommander;
deckHints = dh;
deckNeeds = dn;
deckHas = has;
@@ -42,8 +44,17 @@ public class CardAiHints {
}
/**
* @return the deckHints
* Gets the rem random decks.
*
* @return the rem random decks
*/
public boolean getRemNonCommanderDecks() {
return this.isRemovedFromNonCommanderDecks;
}
/**
* @return the deckHints
*/
public DeckHints getDeckHints() {
return deckHints;
}

View File

@@ -50,8 +50,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = new HashMap<>();
private final List<PaperCard> allCards = new ArrayList<>();
private final List<PaperCard> roAllCards = Collections.unmodifiableList(allCards);
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
private final CardEdition.Collection editions;
public enum SetPreference {
@@ -153,9 +152,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public void loadCard(String cardName, CardRules cr) {
rulesByName.put(cardName, cr);
// This seems very unperformant. Does this get called often?
System.out.println("Inside loading card");
for (CardEdition e : editions) {
for (CardInSet cis : e.getCards()) {
for (CardInSet cis : e.getAllCardsInSet()) {
if (cis.name.equalsIgnoreCase(cardName)) {
addSetCard(e, cis, cr);
}
@@ -165,7 +166,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
reIndex();
}
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary) {
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards, boolean loadNonLegalCards) {
Set<String> allMissingCards = new LinkedHashSet<>();
List<String> missingCards = new ArrayList<>();
CardEdition upcomingSet = null;
@@ -174,15 +175,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
for (CardEdition e : editions.getOrderedEditions()) {
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
//todo sets with nonlegal cards should have tags in them so we don't need to specify the code here
boolean skip = !loadNonLegalCards && (e.getCode().equals("CMB1") || e.getBorderColor() == CardEdition.BorderColor.SILVER);
if (logMissingPerEdition && isCoreExpSet) {
System.out.print(e.getName() + " (" + e.getCards().length + " cards)");
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
}
if (coreOrExpSet && e.getDate().after(today)) {
upcomingSet = e;
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
if (skip)
upcomingSet = e;
}
for (CardEdition.CardInSet cis : e.getCards()) {
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
CardRules cr = rulesByName.get(cis.name);
if (cr != null && !cr.getType().isBasicLand() && skip)
continue;
if (cr != null) {
addSetCard(e, cis, cr);
}
@@ -195,7 +202,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
System.out.println(" ... 100% ");
}
else {
int missing = (e.getCards().length - missingCards.size()) * 10000 / e.getCards().length;
int missing = (e.getAllCardsInSet().size() - missingCards.size()) * 10000 / e.getAllCardsInSet().size();
System.out.printf(" ... %.2f%% (%s missing: %s)%n", missing * 0.01f, Lang.nounWithAmount(missingCards.size(), "card"), StringUtils.join(missingCards, " | "));
}
}
@@ -218,7 +225,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
if (!contains(cr.getName())) {
if (upcomingSet != null) {
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
} else {
} else if(enableUnknownCards) {
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
}
@@ -245,10 +252,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private void reIndex() {
uniqueCardsByName.clear();
allCards.clear();
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
allCards.addAll(kv.getValue());
}
}
@@ -312,17 +317,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return tryGetCard(request);
}
public int getCardCollectorNumber(String cardName, String reqEdition) {
public String getCardCollectorNumber(String cardName, String reqEdition, int artIndex) {
cardName = getName(cardName);
CardEdition edition = editions.get(reqEdition);
if (edition == null)
return -1;
for (CardInSet card : edition.getCards()) {
return null;
int numMatches = 0;
for (CardInSet card : edition.getAllCardsInSet()) {
if (card.name.equalsIgnoreCase(cardName)) {
return card.collectorNumber;
numMatches += 1;
if (numMatches == artIndex) {
return card.collectorNumber;
}
}
}
return -1;
return null;
}
private PaperCard tryGetCard(CardRequest request) {
@@ -531,7 +540,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
@Override
public List<PaperCard> getAllCards() {
public Collection<PaperCard> getAllCards() {
return roAllCards;
}
@@ -558,7 +567,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public List<PaperCard> getAllCardsFromEdition(CardEdition edition) {
List<PaperCard> cards = Lists.newArrayList();
for(CardInSet cis : edition.getCards()) {
for(CardInSet cis : edition.getAllCardsInSet()) {
PaperCard card = this.getCard(cis.name, edition.getCode());
if (card == null) {
// Just in case the card is listed in the edition file but Forge doesn't support it
@@ -650,7 +659,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// May iterate over editions and find out if there is any card named 'cardName' but not implemented with Forge script.
if (StringUtils.isBlank(request.edition)) {
for (CardEdition edition : editions) {
for (CardInSet cardInSet : edition.getCards()) {
for (CardInSet cardInSet : edition.getAllCardsInSet()) {
if (cardInSet.name.equals(request.cardName)) {
cardEdition = edition;
cardRarity = cardInSet.rarity;
@@ -664,7 +673,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} else {
cardEdition = editions.get(request.edition);
if (cardEdition != null) {
for (CardInSet cardInSet : cardEdition.getCards()) {
for (CardInSet cardInSet : cardEdition.getAllCardsInSet()) {
if (cardInSet.name.equals(request.cardName)) {
cardRarity = cardInSet.rarity;
break;
@@ -705,9 +714,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// 1. generate all paper cards from edition data we have (either explicit, or found in res/editions, or add to unknown edition)
List<PaperCard> paperCards = new ArrayList<>();
if (null == whenItWasPrinted || whenItWasPrinted.isEmpty()) {
// TODO Not performant Each time we "putCard" we loop through ALL CARDS IN ALL editions
for (CardEdition e : editions.getOrderedEditions()) {
int artIdx = 1;
for (CardInSet cis : e.getCards()) {
for (CardInSet cis : e.getAllCardsInSet()) {
if (!cis.name.equals(cardName)) {
continue;
}

View File

@@ -19,8 +19,7 @@ package forge.card;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.*;
import forge.StaticData;
import forge.card.CardDb.SetPreference;
import forge.deck.CardPool;
@@ -38,6 +37,8 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -64,7 +65,17 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
FROM_THE_VAULT,
OTHER,
THIRDPARTY // custom sets
THIRDPARTY; // custom sets
public String getBoosterBoxDefault() {
switch (this) {
case CORE:
case EXPANSION:
return "36";
default:
return "0";
}
}
}
public enum FoilType {
@@ -73,12 +84,51 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
MODERN // 8th Edition and newer
}
public enum BorderColor {
WHITE,
BLACK,
SILVER,
GOLD
}
// reserved names of sections inside edition files, that are not parsed as cards
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens");
// commonly used printsheets with collector number
public enum EditionSectionWithCollectorNumbers {
CARDS("cards"),
PRECON_PRODUCT("precon product"),
BORDERLESS("borderless"),
SHOWCASE("showcase"),
EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"),
BUY_A_BOX("buy a box"),
PROMO("promo");
private final String name;
EditionSectionWithCollectorNumbers(final String n) { this.name = n; }
public String getName() {
return name;
}
public static List<String> getNames() {
List<String> list = new ArrayList<>();
for (EditionSectionWithCollectorNumbers s : EditionSectionWithCollectorNumbers.values()) {
String sName = s.getName();
list.add(sName);
}
return list;
}
}
public static class CardInSet {
public final CardRarity rarity;
public final int collectorNumber;
public final String collectorNumber;
public final String name;
public CardInSet(final String name, final int collectorNumber, final CardRarity rarity) {
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity) {
this.name = name;
this.collectorNumber = collectorNumber;
this.rarity = rarity;
@@ -86,7 +136,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public String toString() {
StringBuilder sb = new StringBuilder();
if (collectorNumber != -1) {
if (collectorNumber != null) {
sb.append(collectorNumber);
sb.append(' ');
}
@@ -110,31 +160,45 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private Type type;
private String name;
private String alias = null;
private BorderColor borderColor = BorderColor.BLACK;
// SealedProduct
private String prerelease = null;
private boolean whiteBorder = false;
private int boosterBoxCount = 36;
// Booster/draft info
private boolean smallSetOverride = false;
private boolean foilAlwaysInCommonSlot = false;
private FoilType foilType = FoilType.NOT_SUPPORTED;
private double foilChanceInBooster = 0;
private boolean foilAlwaysInCommonSlot = false;
private double chanceReplaceCommonWith = 0;
private String slotReplaceCommonWith = "Common";
private String additionalSheetForFoils = "";
private String additionalUnlockSet = "";
private boolean smallSetOverride = false;
private String boosterMustContain = "";
private final CardInSet[] cards;
private String boosterReplaceSlotFromPrintSheet = "";
private String doublePickDuringDraft = "";
private String[] chaosDraftThemes = new String[0];
private final ListMultimap<String, CardInSet> cardMap;
private final Map<String, Integer> tokenNormalized;
// custom print sheets that will be loaded lazily
private final Map<String, List<String>> customPrintSheetsToParse;
private int boosterArts = 1;
private SealedProduct.Template boosterTpl = null;
private CardEdition(CardInSet[] cards) {
this.cards = cards;
tokenNormalized = new HashMap<>();
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
this.cardMap = cardMap;
this.tokenNormalized = tokens;
this.customPrintSheetsToParse = customPrintSheetsToParse;
}
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
this.cards = cards;
this.cardMap = ArrayListMultimap.create();
this.cardMap.replaceValues("cards", Arrays.asList(cards));
this.tokenNormalized = tokens;
this.customPrintSheetsToParse = new HashMap<>();
}
/**
@@ -152,7 +216,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
* @param cards the cards in the set
*/
private CardEdition(String date, String code, String code2, String mciCode, Type type, String name, FoilType foil, CardInSet[] cards) {
this(cards);
this(cards, new HashMap<>());
this.code = code;
this.code2 = code2;
this.mciCode = mciCode;
@@ -179,7 +243,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public Type getType() { return type; }
public String getName() { return name; }
public String getAlias() { return alias; }
public String getPrerelease() { return prerelease; }
public int getBoosterBoxCount() { return boosterBoxCount; }
public FoilType getFoilType() { return foilType; }
public double getFoilChanceInBooster() { return foilChanceInBooster; }
public boolean getFoilAlwaysInCommonSlot() { return foilAlwaysInCommonSlot; }
@@ -188,8 +255,17 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
public boolean getSmallSetOverride() { return smallSetOverride; }
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
public String getBoosterMustContain() { return boosterMustContain; }
public CardInSet[] getCards() { return cards; }
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
public List<CardInSet> getCards() { return cardMap.get("cards"); }
public List<CardInSet> getAllCardsInSet() {
return Lists.newArrayList(cardMap.values());
}
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
public Map<String, Integer> getTokens() { return tokenNormalized; }
@@ -234,12 +310,12 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
return this.name + " (" + this.code + ")";
}
public boolean isWhiteBorder() {
return whiteBorder;
public BorderColor getBorderColor() {
return borderColor;
}
public boolean isLargeSet() {
return cards.length > 200 && !smallSetOverride;
return getAllCardsInSet().size() > 200 && !smallSetOverride;
}
public int getCntBoosterPictures() {
@@ -254,6 +330,40 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
return boosterTpl != null;
}
public List<PrintSheet> getPrintSheetsBySection() {
final CardDb cardDb = StaticData.instance().getCommonCards();
Map<String, Integer> cardToIndex = new HashMap<>();
List<PrintSheet> sheets = Lists.newArrayList();
for(String sectionName : cardMap.keySet()) {
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
List<CardInSet> cards = cardMap.get(sectionName);
for(CardInSet card : cards) {
int index = 1;
if (cardToIndex.containsKey(card.name)) {
index = cardToIndex.get(card.name);
}
cardToIndex.put(card.name, index);
PaperCard pCard = cardDb.getCard(card.name, this.getCode(), index);
sheet.add(pCard);
}
sheets.add(sheet);
}
for(String sheetName : customPrintSheetsToParse.keySet()) {
List<String> sheetToParse = customPrintSheetsToParse.get(sheetName);
CardPool sheetPool = CardPool.fromCardList(sheetToParse);
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sheetName), sheetPool);
sheets.add(sheet);
}
return sheets;
}
public static class Reader extends StorageReaderFolder<CardEdition> {
public Reader(File path) {
super(path, CardEdition.FN_GET_CODE);
@@ -263,30 +373,60 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
protected CardEdition read(File file) {
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
final Pattern pattern = Pattern.compile(
/*
The following pattern will match the WAR Japanese art entries,
it should also match the Un-set and older alternate art cards
like Merseine from FEM (should the editions files ever be updated)
*/
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
/* Ideally we'd use the named group above, but Android 6 and
earlier don't appear to support named groups.
So, untill support for those devices is officially dropped,
we'll have to suffice with numbered groups.
We are looking for:
* cnum - grouping #2
* rarity - grouping #4
* name - grouping #5
*/
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
);
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
Map<String, Integer> tokenNormalized = new HashMap<>();
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
if (contents.containsKey("cards")) {
for(String line : contents.get("cards")) {
if (StringUtils.isBlank(line))
continue;
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
List<String> editionSectionsWithCollectorNumbers = EditionSectionWithCollectorNumbers.getNames();
// Optional collector number at the start.
String[] split = line.split(" ", 2);
int collectorNumber = -1;
if (split.length >= 2 && StringUtils.isNumeric(split[0])) {
collectorNumber = Integer.parseInt(split[0]);
line = split[1];
for (String sectionName : contents.keySet()) {
// skip reserved section names like 'metadata' and 'tokens' that are handled separately
if (reservedSectionNames.contains(sectionName)) {
continue;
}
// parse sections of the format "<collector number> <rarity> <name>"
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
for(String line : contents.get(sectionName)) {
Matcher matcher = pattern.matcher(line);
if (!matcher.matches()) {
continue;
}
String collectorNumber = matcher.group(2);
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
String cardName = matcher.group(5);
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
cardMap.put(sectionName, cis);
}
// You may omit rarity for early development
CardRarity r = CardRarity.smartValueOf(line.substring(0, 1));
boolean hadRarity = r != CardRarity.Unknown && line.charAt(1) == ' ';
String cardName = hadRarity ? line.substring(2) : line;
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
processedCards.add(cis);
}
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
else {
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
}
}
// parse tokens section
if (contents.containsKey("tokens")) {
for(String line : contents.get("tokens")) {
if (StringUtils.isBlank(line))
@@ -300,11 +440,9 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
}
}
CardEdition res = new CardEdition(
processedCards.toArray(new CardInSet[processedCards.size()]),
tokenNormalized
);
CardEdition res = new CardEdition(cardMap, tokenNormalized, customPrintSheetsToParse);
// parse metadata section
FileSection section = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
res.name = section.get("name");
res.date = parseDate(section.get("date"));
@@ -323,7 +461,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
res.boosterTpl = boosterDesc == null ? null : new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(boosterDesc));
res.alias = section.get("alias");
res.whiteBorder = "white".equalsIgnoreCase(section.get("border"));
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
String type = section.get("type");
Type enumType = Type.UNKNOWN;
if (null != type && !type.isEmpty()) {
@@ -336,6 +474,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
}
res.type = enumType;
res.prerelease = section.get("Prerelease", null);
res.boosterBoxCount = Integer.parseInt(section.get("BoosterBox", enumType.getBoosterBoxDefault()));
switch(section.get("foil", "newstyle").toLowerCase()) {
case "notsupported":
@@ -365,8 +504,13 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
res.additionalUnlockSet = section.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
res.smallSetOverride = section.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
res.doublePickDuringDraft = section.get("DoublePick", ""); // "FirstPick" or "Always"
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;
}
@@ -554,7 +698,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private static class CanMakeBoosterBox implements Predicate<CardEdition> {
@Override
public boolean apply(final CardEdition subject) {
return StaticData.instance().getBoosterBoxes().contains(subject.getCode());
return subject.getBoosterBoxCount() > 0;
}
}

View File

@@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics {
public boolean canBeBrawlCommander() {
CardType type = mainPart.getType();
return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker();
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
}
public boolean canBeTinyLeadersCommander() {
CardType type = mainPart.getType();
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
}
public String getMeldWith() {
@@ -286,6 +291,7 @@ public final class CardRules implements ICardCharacteristics {
// fields to build CardAiHints
private boolean removedFromAIDecks = false;
private boolean removedFromRandomDecks = false;
private boolean removedFromNonCommanderDecks = false;
private DeckHints hints = null;
private DeckHints needs = null;
private DeckHints has = null;
@@ -305,6 +311,7 @@ public final class CardRules implements ICardCharacteristics {
this.removedFromAIDecks = false;
this.removedFromRandomDecks = false;
this.removedFromNonCommanderDecks = false;
this.needs = null;
this.hints = null;
this.has = null;
@@ -319,7 +326,7 @@ public final class CardRules implements ICardCharacteristics {
* @return the card
*/
public final CardRules getCard() {
CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, hints, needs, has);
CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, removedFromNonCommanderDecks, hints, needs, has);
faces[0].assignMissingFields();
if (null != faces[1]) faces[1].assignMissingFields();
final CardRules result = new CardRules(faces, altMode, cah);
@@ -372,6 +379,7 @@ public final class CardRules implements ICardCharacteristics {
if ( "RemoveDeck".equals(variable) ) {
this.removedFromAIDecks = "All".equalsIgnoreCase(value);
this.removedFromRandomDecks = "Random".equalsIgnoreCase(value);
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
}
} else if ("AlternateMode".equals(key)) {
//System.out.println(faces[curFace].getName());
@@ -479,7 +487,7 @@ public final class CardRules implements ICardCharacteristics {
if ("T".equals(key)) {
this.faces[this.curFace].addTrigger(value);
} else if ("Types".equals(key)) {
this.faces[this.curFace].setType(CardType.parse(value));
this.faces[this.curFace].setType(CardType.parse(value, false));
} else if ("Text".equals(key) && !"no text".equals(value) && StringUtils.isNotBlank(value)) {
this.faces[this.curFace].setNonAbilityText(value);
}
@@ -526,12 +534,10 @@ public final class CardRules implements ICardCharacteristics {
public final ManaCostShard next() {
final String unparsed = st.nextToken();
// System.out.println(unparsed);
try {
int iVal = Integer.parseInt(unparsed);
this.genericCost += iVal;
if (StringUtils.isNumeric(unparsed)) {
this.genericCost += Integer.parseInt(unparsed);
return null;
}
catch (NumberFormatException nex) { }
return ManaCostShard.parseNonGeneric(unparsed);
}
@@ -548,10 +554,10 @@ public final class CardRules implements ICardCharacteristics {
}
public static CardRules getUnsupportedCardNamed(String name) {
CardAiHints cah = new CardAiHints(true, true, null, null, null);
CardAiHints cah = new CardAiHints(true, true, true, null, null, null);
CardFace[] faces = { new CardFace(name), null};
faces[0].setColor(ColorSet.fromMask(0));
faces[0].setType(CardType.parse(""));
faces[0].setType(CardType.parse("", false));
faces[0].setOracleText("This card is not supported by Forge. Whenever you start a game with this card, it will be bugged.");
faces[0].setNonAbilityText("This card is not supported by Forge.\nWhenever you start a game with this card, it will be bugged.");
faces[0].assignMissingFields();

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.util.CardTranslation;
import forge.util.ComparableOp;
import forge.util.PredicateString;
@@ -350,14 +351,18 @@ public final class CardRulesPredicates {
boolean shouldContain;
switch (this.field) {
case NAME:
return op(card.getName(), this.operand);
boolean otherName = false;
if (card.getOtherPart() != null) {
otherName = (op(CardTranslation.getTranslatedName(card.getOtherPart().getName()), this.operand) || op(card.getOtherPart().getName(), this.operand));
}
return otherName || (op(CardTranslation.getTranslatedName(card.getName()), this.operand) || op(card.getName(), this.operand));
case SUBTYPE:
shouldContain = (this.getOperator() == StringOp.CONTAINS) || (this.getOperator() == StringOp.EQUALS);
return shouldContain == card.getType().hasSubtype(this.operand);
case ORACLE_TEXT:
return op(card.getOracleText(), operand);
return (op(CardTranslation.getTranslatedOracle(card.getName()), operand) || op(card.getOracleText(), this.operand));
case JOINED_TYPE:
return op(card.getType().toString(), operand);
return (op(CardTranslation.getTranslatedType(card.getName(), card.getType().toString()), operand) || op(card.getType().toString(), operand));
case COST:
final String cost = card.getManaCost().toString();
return op(cost, operand);
@@ -594,8 +599,10 @@ public final class CardRulesPredicates {
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
public static final Predicate<CardRules> CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
/** The Constant IS_NON_CREATURE_SPELL. **/
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = com.google.common.base.Predicates

View File

@@ -9,7 +9,8 @@ public enum CardSplitType
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure);
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure),
Modal(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Modal);
CardSplitType(FaceSelectionMethod calcMode, CardStateName stateName) {
method = calcMode;

View File

@@ -10,6 +10,7 @@ public enum CardStateName {
LeftSplit,
RightSplit,
Adventure,
Modal
;

View File

@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
@@ -35,10 +36,8 @@ import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.util.EnumUtil;
import forge.util.Settable;
/**
@@ -47,34 +46,46 @@ import forge.util.Settable;
* </p>
*
* @author Forge
* @version $Id: java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardType implements Comparable<CardType>, CardTypeView {
private static final long serialVersionUID = 4629853583167022151L;
public static final CardTypeView EMPTY = new CardType();
public static final CardTypeView EMPTY = new CardType(false);
public static final String AllCreatureTypes = "AllCreatureTypes";
public enum CoreType {
Artifact(true),
Conspiracy(false),
Creature(true),
Emblem(false),
Enchantment(true),
Instant(false),
Land(true),
Phenomenon(false),
Plane(false),
Planeswalker(true),
Scheme(false),
Sorcery(false),
Tribal(false),
Vanguard(false);
Artifact(true, "artifacts"),
Conspiracy(false, "conspiracies"),
Creature(true, "creatures"),
Emblem(false, "emblems"),
Enchantment(true, "enchantments"),
Instant(false, "instants"),
Land(true, "lands"),
Phenomenon(false, "phenomenons"),
Plane(false, "planes"),
Planeswalker(true, "planeswalkers"),
Scheme(false, "schemes"),
Sorcery(false, "sorceries"),
Tribal(false, "tribals"),
Vanguard(false, "vanguards");
public final boolean isPermanent;
private static final ImmutableList<String> allCoreTypeNames = EnumUtil.getNames(CoreType.class);
public final String pluralName;
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
CoreType(final boolean permanent) {
public static CoreType getEnum(String name) {
return stringToCoreType.get(name);
}
public static boolean isValidEnum(String name) {
return stringToCoreType.containsKey(name);
}
CoreType(final boolean permanent, final String plural) {
isPermanent = permanent;
pluralName = plural;
}
}
@@ -86,29 +97,30 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
Ongoing,
World;
private static final ImmutableList<String> allSuperTypeNames = EnumUtil.getNames(Supertype.class);
}
private static Map<String, Supertype> stringToSupertype = EnumUtils.getEnumMap(Supertype.class);
private static final Set<String> allSuperTypeNames = stringToSupertype.keySet();
// This will be useful for faster parses
private static Map<String, CoreType> stringToCoreType = Maps.newHashMap();
private static Map<String, Supertype> stringToSupertype = Maps.newHashMap();
static {
for (final Supertype st : Supertype.values()) {
stringToSupertype.put(st.name(), st);
public static Supertype getEnum(String name) {
return stringToSupertype.get(name);
}
for (final CoreType ct : CoreType.values()) {
stringToCoreType.put(ct.name(), ct);
public static boolean isValidEnum(String name) {
return stringToSupertype.containsKey(name);
}
}
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
private final Set<Supertype> supertypes = EnumSet.noneOf(Supertype.class);
private final Set<String> subtypes = Sets.newLinkedHashSet();
private boolean incomplete = false;
private transient String calculatedType = null;
public CardType() {
public CardType(boolean incomplete) {
this.incomplete = incomplete;
}
public CardType(final Iterable<String> from0) {
public CardType(final Iterable<String> from0, boolean incomplete) {
this.incomplete = incomplete;
addAll(from0);
}
public CardType(final CardType from0) {
@@ -120,12 +132,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public boolean add(final String t) {
boolean changed;
final CoreType ct = stringToCoreType.get(t);
final CoreType ct = CoreType.getEnum(t);
if (ct != null) {
changed = coreTypes.add(ct);
}
else {
final Supertype st = stringToSupertype.get(t);
final Supertype st = Supertype.getEnum(t);
if (st != null) {
changed = supertypes.add(st);
}
@@ -147,6 +159,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
changed = true;
}
}
sanisfySubtypes();
return changed;
}
public boolean addAll(final CardType type) {
@@ -154,6 +167,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (coreTypes.addAll(type.coreTypes)) { changed = true; }
if (supertypes.addAll(type.supertypes)) { changed = true; }
if (subtypes.addAll(type.subtypes)) { changed = true; }
sanisfySubtypes();
return changed;
}
public boolean addAll(final CardTypeView type) {
@@ -161,6 +175,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (Iterables.addAll(coreTypes, type.getCoreTypes())) { changed = true; }
if (Iterables.addAll(supertypes, type.getSupertypes())) { changed = true; }
if (Iterables.addAll(subtypes, type.getSubtypes())) { changed = true; }
sanisfySubtypes();
return changed;
}
@@ -170,6 +185,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (supertypes.removeAll(type.supertypes)) { changed = true; }
if (subtypes.removeAll(type.subtypes)) { changed = true; }
if (changed) {
sanisfySubtypes();
calculatedType = null;
return true;
}
@@ -183,21 +199,30 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
subtypes.clear();
calculatedType = null;
}
public boolean remove(final Supertype st) {
return supertypes.remove(st);
}
public boolean remove(final String str) {
boolean changed = false;
if (CardType.isASupertype(str) && supertypes.remove(stringToSupertype.get(str))) {
changed = true;
} else if (CardType.isACardType(str) && coreTypes.remove(stringToCoreType.get(str))) {
changed = true;
} else if (subtypes.remove(str)) {
// try to remove sub type first if able
if (subtypes.remove(str)) {
changed = true;
} else {
Supertype st = Supertype.getEnum(str);
if (st != null && supertypes.remove(st)) {
changed = true;
}
CoreType ct = CoreType.getEnum(str);
if (ct != null && coreTypes.remove(ct)) {
changed = true;
}
}
if (changed) {
sanisfySubtypes();
calculatedType = null;
}
return changed;
@@ -210,7 +235,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when setting Creature Type
if (subtypes.remove("AllCreatureTypes")) {
if (subtypes.remove(AllCreatureTypes)) {
changed = true;
}
subtypes.addAll(ctypes);
@@ -239,7 +264,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
final Set<String> creatureTypes = Sets.newHashSet();
if (isCreature() || isTribal()) {
for (final String t : subtypes) {
if (isACreatureType(t) || t.equals("AllCreatureTypes")) {
if (isACreatureType(t) || t.equals(AllCreatureTypes)) {
creatureTypes.add(t);
}
}
@@ -267,15 +292,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (hasSubtype(t)) {
return true;
}
final char firstChar = t.charAt(0);
if (Character.isLowerCase(firstChar)) {
t = Character.toUpperCase(firstChar) + t.substring(1); //ensure string is proper case for enum types
}
final CoreType type = stringToCoreType.get(t);
t = StringUtils.capitalize(t);
final CoreType type = CoreType.getEnum(t);
if (type != null) {
return hasType(type);
}
final Supertype supertype = stringToSupertype.get(t);
final Supertype supertype = Supertype.getEnum(t);
if (supertype != null) {
return hasSupertype(supertype);
}
@@ -291,7 +314,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
@Override
public boolean hasSubtype(final String subtype) {
if (isACreatureType(subtype) && subtypes.contains("AllCreatureTypes")) {
if (isACreatureType(subtype) && subtypes.contains(AllCreatureTypes)) {
return true;
}
return subtypes.contains(subtype);
@@ -304,21 +327,21 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
creatureType = toMixedCase(creatureType);
if (!isACreatureType(creatureType)) { return false; }
return subtypes.contains(creatureType) || subtypes.contains("AllCreatureTypes");
return subtypes.contains(creatureType) || subtypes.contains(AllCreatureTypes);
}
private static String toMixedCase(final String s) {
if (s.equals("")) {
if (s.isEmpty()) {
return s;
}
final StringBuilder sb = new StringBuilder();
// to handle hyphenated Types
// TODO checkout WordUtils for this
final String[] types = s.split("-");
for (int i = 0; i < types.length; i++) {
if (i != 0) {
sb.append("-");
}
sb.append(types[i].substring(0, 1).toUpperCase());
sb.append(types[i].substring(1).toLowerCase());
sb.append(StringUtils.capitalize(types[i]));
}
return sb.toString();
}
@@ -474,7 +497,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (ct.isRemoveCreatureTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when removing creature Types
newType.subtypes.remove("AllCreatureTypes");
newType.subtypes.remove(AllCreatureTypes);
}
if (ct.isRemoveArtifactTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
@@ -492,29 +515,37 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
// sanisfy subtypes
if (newType != null && !newType.subtypes.isEmpty()) {
if (!newType.isCreature() && !newType.isTribal()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
newType.subtypes.remove("AllCreatureTypes");
}
if (!newType.isLand()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
}
if (!newType.isArtifact()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
}
if (!newType.isEnchantment()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
}
if (!newType.isInstant() && !newType.isSorcery()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_SPELL_TYPE);
}
if (!newType.isPlaneswalker() && !newType.isEmblem()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_WALKER_TYPE);
}
newType.sanisfySubtypes();
}
return newType == null ? this : newType;
}
public void sanisfySubtypes() {
// incomplete types are used for changing effects
if (this.incomplete) {
return;
}
if (!isCreature() && !isTribal()) {
Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
subtypes.remove(AllCreatureTypes);
}
if (!isLand()) {
Iterables.removeIf(subtypes, Predicates.IS_LAND_TYPE);
}
if (!isArtifact()) {
Iterables.removeIf(subtypes, Predicates.IS_ARTIFACT_TYPE);
}
if (!isEnchantment()) {
Iterables.removeIf(subtypes, Predicates.IS_ENCHANTMENT_TYPE);
}
if (!isInstant() && !isSorcery()) {
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
}
if (!isPlaneswalker() && !isEmblem()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
}
}
@Override
public Iterator<String> iterator() {
final Iterator<CoreType> coreTypeIterator = coreTypes.iterator();
@@ -549,7 +580,69 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return toString().compareTo(o.toString());
}
public boolean sharesSubtypeWith(final CardType ctOther) {
public boolean sharesCreaturetypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
if (this.subtypes.contains(AllCreatureTypes) && ctOther.hasSubtype(AllCreatureTypes)) {
return true;
}
for (final String type : getCreatureTypes()) {
if (ctOther.hasCreatureType(type)) {
return true;
}
}
for (final String type : ctOther.getCreatureTypes()) {
if (this.hasCreatureType(type)) {
return true;
}
}
return false;
}
public boolean sharesLandTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final String type : getLandTypes()) {
if (ctOther.hasSubtype(type)) {
return true;
}
}
return false;
}
public boolean sharesPermanentTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (type.isPermanent && ctOther.hasType(type)) {
return true;
}
}
return false;
}
public boolean sharesCardTypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final CoreType type : getCoreTypes()) {
if (ctOther.hasType(type)) {
return true;
}
}
return false;
}
public boolean sharesSubtypeWith(final CardTypeView ctOther) {
if (ctOther == null) {
return false;
}
for (final String t : ctOther.getSubtypes()) {
if (hasSubtype(t)) {
return true;
@@ -558,11 +651,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return false;
}
public static CardType parse(final String typeText) {
public static CardType parse(final String typeText, boolean incomplete) {
// Most types and subtypes, except "Serra's Realm" and
// "Bolas's Meditation Realm" consist of only one word
final char space = ' ';
final CardType result = new CardType();
final CardType result = new CardType(incomplete);
int iTypeStart = 0;
int iSpace = typeText.indexOf(space);
@@ -582,7 +675,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
public static CardType combine(final CardType a, final CardType b) {
final CardType result = new CardType();
final CardType result = new CardType(false);
result.supertypes.addAll(a.supertypes);
result.supertypes.addAll(b.supertypes);
result.coreTypes.addAll(a.coreTypes);
@@ -613,11 +706,17 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet();
public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet();
public static final Set<String> WALKER_TYPES = Sets.newHashSet();
// singular -> plural
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
// plural -> singular
public static final BiMap<String,String> singularTypes = pluralTypes.inverse();
static {
for (CoreType c : CoreType.values()) {
pluralTypes.put(c.name(), c.pluralName);
}
}
}
public static class Predicates {
public static Predicate<String> IS_LAND_TYPE = new Predicate<String>() {
@@ -662,14 +761,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
};
}
///////// Utility methods
public static boolean isACardType(final String cardType) {
return getAllCardTypes().contains(cardType);
return CoreType.isValidEnum(cardType);
}
public static ImmutableList<String> getAllCardTypes() {
public static Set<String> getAllCardTypes() {
return CoreType.allCoreTypeNames;
}
@@ -714,7 +813,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}
public static boolean isASupertype(final String cardType) {
return (Supertype.allSuperTypeNames.contains(cardType));
return Supertype.isValidEnum(cardType);
}
public static boolean isASubType(final String cardType) {
@@ -740,7 +839,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public static boolean isABasicLandType(final String cardType) {
return (Constant.BASIC_TYPES.contains(cardType));
}
public static boolean isAnEnchantmentType(final String cardType) {
return (Constant.ENCHANTMENT_TYPES.contains(cardType));
}

View File

@@ -19,6 +19,12 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean hasSupertype(Supertype supertype);
boolean hasSubtype(String subtype);
boolean hasCreatureType(String creatureType);
public boolean sharesCreaturetypeWith(final CardTypeView ctOther);
public boolean sharesLandTypeWith(final CardTypeView ctOther);
public boolean sharesPermanentTypeWith(final CardTypeView ctOther);
public boolean sharesCardTypeWith(final CardTypeView ctOther);
boolean isPermanent();
boolean isCreature();
boolean isPlaneswalker();

View File

@@ -41,7 +41,6 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
private static final long serialVersionUID = 794691267379929080L;
private final byte myColor;
public byte getMyColor() { return myColor; }
private final float orderWeight;
private static final ColorSet[] cache = new ColorSet[32];

View File

@@ -24,9 +24,9 @@ public interface ICardDatabase extends Iterable<PaperCard> {
int getArtCount(String cardName, String edition);
Collection<PaperCard> getUniqueCards();
List<PaperCard> getAllCards();
List<PaperCard> getAllCards(String cardName);
List<PaperCard> getAllCards(Predicate<PaperCard> predicate);
Collection<PaperCard> getAllCards();
Collection<PaperCard> getAllCards(String cardName);
Collection<PaperCard> getAllCards(Predicate<PaperCard> predicate);
List<PaperCard> getAllCardsFromEdition(CardEdition edition);

View File

@@ -139,16 +139,22 @@ public final class MagicColor {
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
public static final 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.

View File

@@ -5,6 +5,8 @@ import forge.deck.CardPool;
import forge.item.PaperCard;
import forge.util.ItemPool;
import forge.util.MyRandom;
import forge.util.storage.IStorage;
import forge.util.storage.StorageExtendable;
import forge.util.storage.StorageReaderFileSections;
import java.io.File;
@@ -23,6 +25,18 @@ public class PrintSheet {
@Override public final String apply(PrintSheet sheet) { return sheet.name; }
};
public static final IStorage<PrintSheet> initializePrintSheets(File sheetsFile, CardEdition.Collection editions) {
IStorage<PrintSheet> sheets = new StorageExtendable<>("Special print runs", new PrintSheet.Reader(sheetsFile));
for(CardEdition edition : editions) {
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
System.out.println(ps.name);
sheets.add(ps.name, ps);
}
}
return sheets;
}
private final ItemPool<PaperCard> cardsWithWeights;
@@ -78,6 +92,16 @@ public class PrintSheet {
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
}
public List<PaperCard> all() {
List<PaperCard> result = new ArrayList<>();
for(Entry<PaperCard, Integer> kv : cardsWithWeights) {
for(int i = 0; i < kv.getValue(); i++) {
result.add(kv.getKey());
}
}
return result;
}
public List<PaperCard> random(int number, boolean wantUnique) {
List<PaperCard> result = new ArrayList<>();

View File

@@ -345,13 +345,7 @@ public final class ManaCost implements Comparable<ManaCost>, Iterable<ManaCostSh
* @return
*/
public int countX() {
int iX = 0;
for (ManaCostShard shard : shards) {
if (shard == ManaCostShard.X) {
iX++;
}
}
return iX;
return getShardCount(ManaCostShard.X);
}
/**

View File

@@ -289,13 +289,20 @@ public enum ManaCostShard {
return BinaryUtil.bitCount(this.shard & COLORS_SUPERPOSITION) == 2;
}
public boolean isGeneric() {
return isOfKind(ManaAtom.GENERIC)|| isOfKind(ManaAtom.IS_X) || this.isSnow() || this.isOr2Generic();
}
public boolean isOr2Generic() {
return isOfKind(ManaAtom.OR_2_GENERIC);
}
public boolean isColor(byte colorCode) {
return (colorCode & this.shard) > 0;
}
public boolean canBePaidWithManaOfColor(byte colorCode) {
return this.isOr2Generic() || ((COLORS_SUPERPOSITION | ManaAtom.COLORLESS) & this.shard) == 0 ||
(colorCode & this.shard) > 0;
this.isColor(colorCode);
}
public boolean isOfKind(int atom) {

View File

@@ -73,7 +73,7 @@ public enum DeckFormat {
private final Set<String> bannedCards = ImmutableSet.of(
"Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star",
"Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop",
"Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister",
"Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Najeela, the Blade Blossom", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister",
"Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will");
@Override
@@ -375,9 +375,10 @@ public enum DeckFormat {
public static Integer canHaveSpecificNumberInDeck(final IPaperCard card) {
// Ideally, this would be parsed during card parsing and set this value
if (Iterables.contains(card.getRules().getMainPart().getKeywords(),
"A deck can have up to seven cards named CARDNAME.")) {
if (card.getRules().hasKeyword("A deck can have up to seven cards named CARDNAME.")) {
return 7;
} else if (card.getRules().hasKeyword("Megalegendary")) {
return 1;
}
return null;
@@ -463,6 +464,9 @@ public enum DeckFormat {
if (this.equals(DeckFormat.Brawl)) {
return rules.canBeBrawlCommander();
}
if (this.equals(DeckFormat.TinyLeaders)) {
return rules.canBeTinyLeadersCommander();
}
return rules.canBeCommander();
}

View File

@@ -69,7 +69,8 @@ public class DeckGenerator5Color extends DeckGeneratorBase {
*/
public DeckGenerator5Color(IDeckGenPool pool0, DeckFormat format0, Predicate<PaperCard> formatFilter0) {
super(pool0, format0, formatFilter0);
format0.adjustCMCLevels(cmcLevels);
colors = ColorSet.fromMask(0).inverse();
}
public DeckGenerator5Color(IDeckGenPool pool0, DeckFormat format0) {

View File

@@ -290,10 +290,18 @@ public abstract class DeckGeneratorBase {
// remove cards that generated decks don't like
Predicate<CardRules> canPlay = forAi ? AI_CAN_PLAY : HUMAN_CAN_PLAY;
Predicate<CardRules> hasColor = new MatchColorIdentity(colors);
Predicate<CardRules> canUseInFormat = new Predicate<CardRules>() {
@Override
public boolean apply(CardRules c) {
// FIXME: should this be limited to AI only (!forAi) or should it be generally applied to all random generated decks?
return !c.getAiHints().getRemNonCommanderDecks() || format.hasCommander();
}
};
if (useArtifacts) {
hasColor = Predicates.or(hasColor, COLORLESS_CARDS);
}
return Iterables.filter(pool.getAllCards(),Predicates.compose(Predicates.and(canPlay, hasColor), PaperCard.FN_GET_RULES));
return Iterables.filter(pool.getAllCards(),Predicates.compose(Predicates.and(canPlay, hasColor, canUseInFormat), PaperCard.FN_GET_RULES));
}
protected static Map<String, Integer> countLands(ItemPool<PaperCard> outList) {

View File

@@ -18,25 +18,22 @@
package forge.item;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.CardEdition;
import forge.util.TextUtil;
import forge.util.storage.StorageReaderFile;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
public class BoosterBox extends BoxedProduct {
public static final Function<CardEdition, BoosterBox> FN_FROM_SET = new Function<CardEdition, BoosterBox>() {
@Override
public BoosterBox apply(final CardEdition arg1) {
BoosterBox.Template d = StaticData.instance().getBoosterBoxes().get(arg1.getCode());
if (arg1.getBoosterBoxCount() <= 0) {
return null;
}
BoosterBox.Template d = new Template(arg1);
if (d == null) { return null; }
return new BoosterBox(arg1.getName(), d, d.cntBoosters);
}
@@ -72,40 +69,13 @@ public class BoosterBox extends BoxedProduct {
public static class Template extends SealedProduct.Template {
private final int cntBoosters;
public int getCntBoosters() { return cntBoosters; }
private Template(String edition, int boosters, Iterable<Pair<String, Integer>> itrSlots)
{
super(edition, itrSlots);
cntBoosters = boosters;
private Template(CardEdition edition) {
super(edition.getCode(), new ArrayList<>());
cntBoosters = edition.getBoosterBoxCount();
}
public static final class Reader extends StorageReaderFile<Template> {
public Reader(String pathname) {
super(pathname, FN_GET_NAME);
}
@Override
protected Template read(String line, int i) {
String[] headAndData = TextUtil.split(line, ':', 2);
final String edition = headAndData[0];
final String[] data = TextUtil.splitWithParenthesis(headAndData[1], ',');
int nBoosters = 6;
List<Pair<String, Integer>> slots = new ArrayList<>();
for(String slotDesc : data) {
String[] kv = TextUtil.split(slotDesc, ' ', 2);
if (kv[1].startsWith("Booster"))
nBoosters = Integer.parseInt(kv[0]);
else
slots.add(ImmutablePair.of(kv[1], Integer.parseInt(kv[0])));
}
return new BoosterBox.Template(edition, nBoosters, slots);
}
}
@Override
public String toString() {
if (0 >= cntBoosters) {

View File

@@ -6,6 +6,7 @@ import forge.card.CardRarity;
import forge.card.CardRules;
import forge.card.CardType.CoreType;
import forge.card.MagicColor;
import forge.util.PredicateCard;
import forge.util.PredicateString;
import org.apache.commons.lang3.StringUtils;
@@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem {
return new PredicateNames(what);
}
public static PredicateCards cards(final List<PaperCard> what) { return new PredicateCards(what); }
private static final class PredicateColor implements Predicate<PaperCard> {
private final byte operand;
@@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem {
}
}
private static final class PredicateCards extends PredicateCard<PaperCard> {
private final List<PaperCard> operand;
@Override
public boolean apply(final PaperCard card) {
for (final PaperCard element : this.operand) {
if (this.op(card, element)) {
return true;
}
}
return false;
}
private PredicateCards(final List<PaperCard> operand) {
super(StringOp.EQUALS);
this.operand = operand;
}
}
/**
* Pre-built predicates are stored here to allow their re-usage and
* easier access from code.

View File

@@ -28,6 +28,7 @@ import forge.StaticData;
import forge.card.CardDb;
import forge.card.CardRarity;
import forge.card.CardRules;
import forge.util.CardTranslation;
import forge.util.Localizer;
import forge.util.TextUtil;
@@ -187,7 +188,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
*/
@Override
public String toString() {
return name;
return CardTranslation.getTranslatedName(name);
// cannot still decide, if this "name|set" format is needed anymore
// return String.format("%s|%s", name, cardSet);
}

View File

@@ -57,7 +57,9 @@ public abstract class SealedProduct implements InventoryItemFromSet {
public SealedProduct(String name0, Template boosterData) {
if (null == name0) { throw new IllegalArgumentException("name0 must not be null"); }
if (null == boosterData) { throw new IllegalArgumentException("boosterData must not be null"); }
if (null == boosterData) {
throw new IllegalArgumentException("boosterData must not be null");
}
contents = boosterData;
name = name0;
hash = name.hashCode() ^ getClass().hashCode() ^ contents.hashCode();

View File

@@ -229,6 +229,13 @@ public class BoosterGenerator {
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
: slotType.trim();
if (sheetKey.startsWith("wholeSheet")) {
PrintSheet ps = getPrintSheet(sheetKey);
result.addAll(ps.all());
sheetsUsed.add(ps);
continue;
}
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
@@ -332,6 +339,11 @@ public class BoosterGenerator {
if (!boosterMustContain.isEmpty()) {
ensureGuaranteedCardInBooster(result, template, boosterMustContain);
}
String boosterReplaceSlotFromPrintSheet = edition.getBoosterReplaceSlotFromPrintSheet();
if(!boosterReplaceSlotFromPrintSheet.isEmpty()) {
replaceCardFromExtraSheet(result, boosterReplaceSlotFromPrintSheet);
}
}
return result;
@@ -383,27 +395,69 @@ public class BoosterGenerator {
if (!possibleCards.isEmpty()) {
PaperCard toAdd = Aggregates.random(possibleCards);
PaperCard toRepl = null;
CardRarity tgtRarity = toAdd.getRarity();
// remove the first card of the same rarity, replace it with toAdd. Keep the foil state.
for (PaperCard repl : result) {
if (repl.getRarity() == tgtRarity) {
toRepl = repl;
break;
}
}
if (toRepl != null) {
if (toRepl.isFoil()) {
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
}
result.remove(toRepl);
result.add(toAdd);
}
BoosterGenerator.replaceCard(result, toAdd);
}
}
}
/**
* Replaces an already present card in the booster with a card from the supplied print sheet.
* Nothing is replaced if there is no matching rarity found.
* @param booster in which a card gets replaced
* @param printSheetKey
*/
public static void replaceCardFromExtraSheet(List<PaperCard> booster, String printSheetKey) {
PrintSheet replacementSheet = StaticData.instance().getPrintSheets().get(printSheetKey);
PaperCard toAdd = replacementSheet.random(1, false).get(0);
BoosterGenerator.replaceCard(booster, toAdd);
}
/**
* Replaces an already present card with the supplied card of the same (or similar in case or rare/mythic)
* rarity in the supplied booster. Nothing is replaced if there is no matching rarity found.
* @param booster in which a card gets replaced
* @param toAdd new card which replaces a card in the booster
*/
public static void replaceCard(List<PaperCard> booster, PaperCard toAdd) {
Predicate<PaperCard> rarityPredicate = null;
switch(toAdd.getRarity()){
case BasicLand:
rarityPredicate = Presets.IS_BASIC_LAND;
break;
case Common:
rarityPredicate = Presets.IS_COMMON;
break;
case Uncommon:
rarityPredicate = Presets.IS_UNCOMMON;
break;
case Rare:
case MythicRare:
rarityPredicate = Presets.IS_RARE_OR_MYTHIC;
break;
default:
rarityPredicate = Presets.IS_SPECIAL;
}
PaperCard toReplace = null;
// Find first card in booster that matches the rarity
for (PaperCard card : booster) {
if(rarityPredicate.apply(card)) {
toReplace = card;
break;
}
}
// Replace card if match is found
if (toReplace != null) {
// Keep the foil state
if (toReplace.isFoil()) {
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
}
booster.remove(toReplace);
booster.add(toAdd);
}
}
public static void addCardsFromExtraSheet(List<PaperCard> dest, String printSheetKey) {
PrintSheet extraSheet = getPrintSheet(printSheetKey);
@@ -438,9 +492,10 @@ public class BoosterGenerator {
String mainCode = itMod.next();
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9)) { // custom print sheet
String sheetName = StringUtils.strip(mainCode.substring(9), "()\" ");
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9) ||
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
) { // custom print sheet
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
setPred = Predicates.alwaysTrue();
@@ -524,7 +579,12 @@ public class BoosterGenerator {
Predicate<PaperCard> toAdd = null;
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
toAdd = Predicates.compose(Predicates.or(CardRulesPredicates.splitType(CardSplitType.Transform), CardRulesPredicates.splitType(CardSplitType.Meld)),
toAdd = Predicates.compose(
Predicates.or(
CardRulesPredicates.splitType(CardSplitType.Transform),
CardRulesPredicates.splitType(CardSplitType.Meld),
CardRulesPredicates.splitType(CardSplitType.Modal)
),
PaperCard.FN_GET_RULES);
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
@@ -566,12 +626,8 @@ public class BoosterGenerator {
toAdd = IPaperCard.Predicates.printedInSets(sets);
} else if (operator.startsWith("fromSheet(") && invert) {
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
List<String> cardNames = Lists.newArrayList();
for (PaperCard card : src) {
cardNames.add(card.getName());
}
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
Iterable<PaperCard> cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards));
}
if (toAdd == null) {

View File

@@ -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();
}

View File

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

View File

@@ -1,17 +1,20 @@
package forge.util;
import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public class CardTranslation {
private static Map <String, String> translatednames;
private static Map <String, String> translatedtypes;
private static Map <String, String> translatedoracles;
private static Map <String, List <Pair <String, String> > > oracleMappings;
private static Map <String, String> translatedCaches;
private static String languageSelected = "en-US";
private static void readTranslationFile(String language, String languagesDirectory) {
@@ -27,7 +30,7 @@ public class CardTranslation {
translatedtypes.put(matches[0], matches[2]);
}
if (matches.length >= 4) {
translatedoracles.put(matches[0], matches[3].replace("\\n", "\n\n"));
translatedoracles.put(matches[0], matches[3].replace("\\n", "\r\n\r\n"));
}
}
} catch (IOException e) {
@@ -37,8 +40,14 @@ public class CardTranslation {
public static String getTranslatedName(String name) {
if (needsTranslation()) {
if (name.contains(" // ")) {
int splitIndex = name.indexOf(" // ");
String leftname = name.substring(0, splitIndex);
String rightname = name.substring(splitIndex + 4, name.length());
return translatednames.get(leftname) + " // " + translatednames.get(rightname);
}
String tname = translatednames.get(name);
return tname == null ? name : tname;
return (tname == null || tname.isEmpty()) ? name : tname;
}
return name;
@@ -63,6 +72,7 @@ public class CardTranslation {
}
public static HashMap<String, String> getTranslationTexts(String cardname, String altcardname) {
if (!needsTranslation()) return null;
HashMap<String, String> translations = new HashMap<>();
translations.put("name", getTranslatedName(cardname));
translations.put("oracle", getTranslatedOracle(cardname));
@@ -82,7 +92,101 @@ public class CardTranslation {
translatednames = new HashMap<>();
translatedtypes = new HashMap<>();
translatedoracles = new HashMap<>();
oracleMappings = new HashMap<>();
translatedCaches = new HashMap<>();
readTranslationFile(languageSelected, languagesDirectory);
}
}
}
private static String replaceCardName(String language, String name, String toracle) {
String nickName = language.equals("en-US") ? Lang.getEnglishInstance().getNickName(name) : Lang.getInstance().getNickName(name);
String result = TextUtil.fastReplace(toracle, name, "CARDNAME");
if (!nickName.equals(name)) {
result = TextUtil.fastReplace(result, nickName, "NICKNAME");
}
return result;
}
public static void buildOracleMapping(String faceName, String oracleText) {
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
String translatedName = getTranslatedName(faceName);
String translatedText = getTranslatedOracle(faceName);
List <Pair <String, String> > mapping = new ArrayList<>();
String [] splitOracleText = oracleText.split("\\\\n");
String [] splitTranslatedText = translatedText.split("\r\n\r\n");
for (int i = 0; i < splitOracleText.length && i < splitTranslatedText.length; i++) {
String toracle = replaceCardName("en-US", faceName, splitOracleText[i]);
String ttranslated = replaceCardName(languageSelected, translatedName, splitTranslatedText[i]);
// Remove reminder text in English oracle text unless entire line is reminder text
if (!toracle.startsWith("(")) {
toracle = toracle.replaceAll("\\(.*\\)", "");
}
mapping.add(Pair.of(toracle, ttranslated));
}
oracleMappings.put(faceName, mapping);
}
public static String translateMultipleDescriptionText(String descText, String cardName) {
if (!needsTranslation()) return descText;
String [] splitDescText = descText.split("\r\n");
String result = descText;
for (String text : splitDescText) {
if (text.isEmpty()) continue;
String translated = translateSingleDescriptionText(text, cardName);
if (!text.equals(translated)) {
result = TextUtil.fastReplace(result, text, translated);
} else {
// keywords maybe combined into one line, split them and try translate again
String [] splitKeywords = text.split(", ");
if (splitKeywords.length <= 1) continue;
for (String keyword : splitKeywords) {
if (keyword.contains(" ")) continue;
translated = translateSingleDescriptionText(keyword, cardName);
if (!keyword.equals(translated)) {
result = TextUtil.fastReplace(result, keyword, translated);
}
}
}
}
return result;
}
public static String translateSingleDescriptionText(String descText, String cardName) {
if (!needsTranslation()) return descText;
if (translatedCaches.containsKey(descText)) return translatedCaches.get(descText);
List <Pair <String, String> > mapping = oracleMappings.get(cardName);
if (mapping == null) return descText;
String result = descText;
if (!mapping.isEmpty()) {
result = translateSingleIngameText(descText, mapping);
}
translatedCaches.put(descText, result);
return result;
}
private static String translateSingleIngameText(String descText, List <Pair <String, String> > mapping) {
String tcompare = descText.startsWith("(") ? descText : descText.replaceAll("\\(.*\\)", "");
// Use Levenshtein Distance to find matching oracle text and replace it with translated text
int candidateIndex = mapping.size();
int minDistance = tcompare.length();
for (int i = 0; i < mapping.size(); i++) {
String toracle = mapping.get(i).getLeft();
int threshold = Math.min(toracle.length(), tcompare.length()) / 3;
int distance = StringUtils.getLevenshteinDistance(toracle, tcompare, threshold);
if (distance != -1 && distance < minDistance) {
minDistance = distance;
candidateIndex = i;
}
}
if (candidateIndex < mapping.size()) {
return mapping.get(candidateIndex).getRight();
}
return descText;
}
}

View File

@@ -286,4 +286,11 @@ public final class FileUtil {
}, 5000); //abort reading file if it takes longer than 5 seconds
return lines;
}
public static String getParent(final String resourcePath) {
File f = new File(resourcePath);
if (f.getParentFile().getName() != null)
return f.getParentFile().getName();
return "";
}
}

View File

@@ -81,7 +81,7 @@ public class ImageUtil {
public static boolean hasBackFacePicture(PaperCard cp) {
CardSplitType cst = cp.getRules().getSplitType();
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld;
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld || cst == CardSplitType.Modal;
}
public static String getNameToUse(PaperCard cp, boolean backFace) {

View File

@@ -24,6 +24,7 @@ import forge.item.InventoryItem;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map.Entry;
/**
@@ -60,7 +61,7 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
};
public ItemPool(final Class<T> cls) {
this(new LinkedHashMap<>(), cls);
this(new ConcurrentHashMap<>(), cls);
}
@SuppressWarnings("unchecked")
@@ -95,7 +96,7 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
items = items0;
}
else {
items = new HashMap<>(); //prevent items being null
items = new ConcurrentHashMap<>();
}
myClass = cls;
}

View File

@@ -6,6 +6,8 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import forge.util.lang.*;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -13,12 +15,49 @@ import com.google.common.collect.Lists;
/**
* Static library containing language-related utility methods.
*/
public final class Lang {
public abstract class Lang {
/**
* Private constructor to prevent instantiation.
*/
private Lang() {
private static Lang instance;
private static Lang englishInstance;
protected String languageCode;
protected String countryCode;
public static void createInstance(String localeID) {
String[] splitLocale = localeID.split("-");
String language = splitLocale[0];
String country = splitLocale[1];
if (language.equals("de")) {
instance = new LangGerman();
} else if (language.equals("es")) {
instance = new LangSpanish();
} else if (language.equals("it")) {
instance = new LangItalian();
} else if (language.equals("zh")) {
instance = new LangChinese();
} else if (language.equals("ja")) {
instance = new LangJapanese();
} else { // default is English
instance = new LangEnglish();
}
instance.languageCode = language;
instance.countryCode = country;
// Create english instance for internal usage
englishInstance = new LangEnglish();
englishInstance.languageCode = "en";
englishInstance.countryCode = "US";
}
public static Lang getInstance() {
return instance;
}
public static Lang getEnglishInstance() {
return englishInstance;
}
protected Lang() {
}
/**
@@ -29,17 +68,7 @@ public final class Lang {
* the number to get the ordinal suffix for.
* @return a string containing two characters.
*/
public static String getOrdinal(final int position) {
final String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
switch (position % 100) {
case 11:
case 12:
case 13:
return position + "th";
default:
return position + sufixes[position % 10];
}
}
public abstract String getOrdinal(final int position);
public static String joinHomogenous(final String s1, final String s2) {
final boolean has1 = StringUtils.isNotBlank(s1);
@@ -121,16 +150,8 @@ public final class Lang {
}
}
public static String getPossesive(final String name) {
if ("You".equalsIgnoreCase(name)) {
return name + "r"; // to get "your"
}
return name.endsWith("s") ? name + "'" : name + "'s";
}
public static String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + " " + object;
}
public abstract String getPossesive(final String name);
public abstract String getPossessedObject(final String owner, final String object);
public static boolean startsWithVowel(final String word) {
return isVowel(word.trim().charAt(0));
@@ -159,4 +180,6 @@ public final class Lang {
}
return Integer.toString(n);
}
public abstract String getNickName(final String name);
}

View File

@@ -56,7 +56,7 @@ public class Localizer {
public String getMessage(final String key, final Object... messageArguments) {
MessageFormat formatter = null;
try {
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
formatter = new MessageFormat(resourceBundle.getString(key), locale);
@@ -72,15 +72,29 @@ public class Localizer {
formatter.setLocale(locale);
String formattedMessage = "CHAR ENCODING ERROR";
final String[] charsets = { "ISO-8859-1", "UTF-8" };
//Support non-English-standard characters
String detectedCharset = charset(new String(formatter.format(messageArguments)), new String[] { "ISO-8859-1", "UTF-8" });
String detectedCharset = charset(resourceBundle.getString(key), charsets);
final int argLength = messageArguments.length;
Object[] syncEncodingMessageArguments = new Object[argLength];
//when messageArguments encoding not equal resourceBundle.getString(key),convert to equal
//avoid convert to a have two encoding content formattedMessage string.
for (int i = 0; i < argLength; i++) {
String objCharset = charset(messageArguments[i].toString(), charsets);
try {
syncEncodingMessageArguments[i] = convert(messageArguments[i].toString(), objCharset, detectedCharset);
} catch (UnsupportedEncodingException ignored) {
System.err.println("Cannot Convert '" + messageArguments[i].toString() + "' from '" + objCharset + "' To '" + detectedCharset + "'");
return "encoding '" + key + "' translate string failure";
}
}
try {
formattedMessage = new String(formatter.format(messageArguments).getBytes(detectedCharset), StandardCharsets.UTF_8);
formattedMessage = new String(formatter.format(syncEncodingMessageArguments).getBytes(detectedCharset), StandardCharsets.UTF_8);
} catch(UnsupportedEncodingException ignored) {}
return formattedMessage;
}
public void setLanguage(final String languageRegionID, final String languagesDirectory) {

View File

@@ -0,0 +1,83 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2020 Jamin W. Collins
*
* This program is free software: you can redistribute it and/or modify
* 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/>.
*/
package forge.util;
import com.google.common.base.Predicate;
import forge.item.PaperCard;
/**
* Special predicate class to perform string operations.
*
* @param <T>
* the generic type
*/
public abstract class PredicateCard<T> implements Predicate<T> {
/** Possible operators for string operands. */
public enum StringOp {
/** The EQUALS. */
EQUALS,
}
/** The operator. */
private final StringOp operator;
/**
* Op.
*
* @param op1
* the op1
* @param op2
* the op2
* @return true, if successful
*/
protected final boolean op(final PaperCard op1, final PaperCard op2) {
switch (this.getOperator()) {
case EQUALS:
return op1.equals(op2);
default:
return false;
}
}
/**
* Instantiates a new predicate string.
*
* @param operator
* the operator
*/
public PredicateCard(final StringOp operator) {
this.operator = operator;
}
/**
* @return the operator
*/
public StringOp getOperator() {
return operator;
}
public static PredicateCard<PaperCard> equals(final PaperCard what) {
return new PredicateCard<PaperCard>(StringOp.EQUALS) {
@Override
public boolean apply(PaperCard subject) {
return op(subject, what);
}
};
}
}

View File

@@ -5,6 +5,8 @@ import forge.item.PaperCard;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableSortedMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -17,6 +19,22 @@ import java.util.Map.Entry;
*/
public class TextUtil {
static ImmutableSortedMap<Integer,String> romanMap = ImmutableSortedMap.<Integer,String>naturalOrder()
.put(1000, "M").put(900, "CM")
.put(500, "D").put(400, "CD")
.put(100, "C").put(90, "XC")
.put(50, "L").put(40, "XL")
.put(10, "X").put(9, "IX")
.put(5, "V").put(4, "IV").put(1, "I").build();
public final static String toRoman(int number) {
if (number <= 0) {
return "";
}
int l = romanMap.floorKey(number);
return romanMap.get(l) + toRoman(number-l);
}
/**
* Safely converts an object to a String.
*
@@ -282,4 +300,10 @@ public class TextUtil {
sb.append( str, idx1, str.length() );
return sb.toString();
}
//Convert to Mana String
public static String toManaString(String ManaProduced){
if (ManaProduced == "mana"|| ManaProduced.contains("Combo")|| ManaProduced.contains("Any"))
return "mana";//fix manamorphose stack description and probably others..
return "{"+TextUtil.fastReplace(ManaProduced," ","}{")+"}";
}
}

View File

@@ -0,0 +1,27 @@
package forge.util.lang;
import forge.util.Lang;
public class LangChinese extends Lang {
@Override
public String getOrdinal(final int position) {
return "" + position;
}
@Override
public String getPossesive(final String name) {
return name + "";
}
@Override
public String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + object;
}
@Override
public String getNickName(final String name) {
return name;
}
}

View File

@@ -0,0 +1,37 @@
package forge.util.lang;
import forge.util.Lang;
public class LangEnglish extends Lang {
@Override
public String getOrdinal(final int position) {
final String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
switch (position % 100) {
case 11:
case 12:
case 13:
return position + "th";
default:
return position + sufixes[position % 10];
}
}
@Override
public String getPossesive(final String name) {
if ("You".equalsIgnoreCase(name)) {
return name + "r"; // to get "your"
}
return name.endsWith("s") ? name + "'" : name + "'s";
}
@Override
public String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + " " + object;
}
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
}

View File

@@ -0,0 +1,35 @@
package forge.util.lang;
import forge.util.Lang;
public class LangGerman extends Lang {
@Override
public String getOrdinal(final int position) {
if (position < 20) {
return position + "te";
}
return position + "ste";
}
// TODO: Please update this when you modified lblYou in de-DE.properties
@Override
public String getPossesive(final String name) {
if ("You".equalsIgnoreCase(name)) {
return name + "r"; // to get "your"
}
return name.endsWith("s") ? name + "'" : name + "'s";
}
@Override
public String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + " " + object;
}
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
}

View File

@@ -0,0 +1,31 @@
package forge.util.lang;
import forge.util.Lang;
public class LangItalian extends Lang {
@Override
public String getOrdinal(final int position) {
return position + "º";
}
// TODO: Please update this when you modified lblYou in it-IT.properties
@Override
public String getPossesive(final String name) {
if ("You".equalsIgnoreCase(name)) {
return name + "r"; // to get "your"
}
return name.endsWith("s") ? name + "'" : name + "'s";
}
@Override
public String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + " " + object;
}
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
}

View File

@@ -0,0 +1,29 @@
package forge.util.lang;
import forge.util.Lang;
public class LangJapanese extends Lang {
@Override
public String getOrdinal(final int position) {
return position + "";
}
@Override
public String getPossesive(final String name) {
return name + "";
}
@Override
public String getPossessedObject(final String owner, final String object) {
return getPossesive(owner) + object;
}
@Override
public String getNickName(final String name) {
String [] splitName = name.split("");
if (splitName.length > 1) return splitName[1];
return name;
}
}

View File

@@ -0,0 +1,33 @@
package forge.util.lang;
import forge.util.Lang;
public class LangSpanish extends Lang {
@Override
public String getOrdinal(final int position) {
return position + "º";
}
@Override
public String getPossesive(final String name) {
if ("Tu".equalsIgnoreCase(name)) {
return name;
}
return "de " + name;
}
@Override
public String getPossessedObject(final String owner, final String object) {
if ("Tu".equalsIgnoreCase(owner)) {
return getPossesive(owner) + " " + object;
}
return object + " " + getPossesive(owner);
}
@Override
public String getNickName(final String name) {
return name.split(", ")[0];
}
}

View File

@@ -31,6 +31,7 @@ public interface IStorage<T> extends Iterable<T>, IHasName {
boolean contains(String name);
int size();
void add(T item);
void add(String name, T item);
void delete(String deckName);
IStorage<IStorage<T>> getFolders();
IStorage<T> tryGetFolder(String path);

View File

@@ -78,6 +78,11 @@ public class StorageBase<T> implements IStorage<T> {
return Iterables.tryFind(map.values(), condition).orNull();
}
@Override
public void add(String name, T item) {
throw new UnsupportedOperationException("This is a read-only storage");
}
@Override
public void add(T item) {
throw new UnsupportedOperationException("This is a read-only storage");

View File

@@ -0,0 +1,47 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* 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/>.
*/
package forge.util.storage;
import forge.util.IItemReader;
import java.util.*;
/**
* <p>
* StorageBase class.
* </p>
*
* @param <T> the generic type
* @author Forge
* @version $Id: StorageBase.java 13590 2012-01-27 20:46:27Z Max mtg $
*/
public class StorageExtendable<T> extends StorageBase<T> {
public StorageExtendable(String name0, IItemReader<T> io) {
super(name0, io);
}
public StorageExtendable(final String name0, final String fullPath0, final Map<String, T> map0) {
super(name0, fullPath0, map0);
}
@Override
public void add(String name, T item) {
map.put(name, item);
}
}