Merge branch 'master' into setEventCommand

# Conflicts:
#	forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java
#	forge-gui-mobile/src/forge/adventure/stage/MapStage.java
#	forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java
This commit is contained in:
Jetz
2025-10-15 09:19:44 -04:00
1763 changed files with 15352 additions and 9255 deletions

View File

@@ -31,6 +31,8 @@ public final class ImageKeys {
public static final String MONARCH_IMAGE = "monarch";
public static final String THE_RING_IMAGE = "the_ring";
public static final String RADIATION_IMAGE = "radiation";
public static final String SPEED_IMAGE = "speed";
public static final String MAX_SPEED_IMAGE = "max_speed";
public static final String BACKFACE_POSTFIX = "$alt";
public static final String SPECFACE_W = "$wspec";

View File

@@ -29,8 +29,6 @@ import java.util.stream.Collectors;
public class StaticData {
private final CardStorageReader cardReader;
private final CardStorageReader tokenReader;
private final CardStorageReader customCardReader;
private final String blockDataFolder;
private final CardDb commonCards;
private final CardDb variantCards;
@@ -79,7 +77,6 @@ public class StaticData {
this.tokenReader = tokenReader;
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
this.blockDataFolder = blockDataFolder;
this.customCardReader = customCardReader;
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
this.loadNonLegalCards = loadNonLegalCards;
@@ -881,7 +878,7 @@ public class StaticData {
}
}
}
// stream().toList() causes crash on Android, use Collectors.toList()
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
List<String> CNI = new ArrayList<>(CNI_Q).stream().sorted().collect(Collectors.toList());
List<String> TOK = new ArrayList<>(TOKEN_Q).stream().sorted().collect(Collectors.toList());

View File

@@ -45,8 +45,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public final static char NameSetSeparator = '|';
public final static String FlagPrefix = "#";
public static final String FlagSeparator = "\t";
private final String exlcudedCardName = "Concentrate";
private final String exlcudedCardSet = "DS0";
// need this to obtain cardReference by name+set+artindex
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
@@ -303,7 +301,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// create faces list from rules
for (final CardRules rule : rules.values()) {
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
if (filteredCards.contains(rule.getName()))
continue;
for (ICardFace face : rule.getAllFaces()) {
addFaceToDbNames(face);
@@ -501,8 +499,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public void addCard(PaperCard paperCard) {
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
if (filtered.contains(paperCard.getName())) {
return;
}
allCardsByName.put(paperCard.getName(), paperCard);
@@ -523,17 +522,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
}
private boolean excludeCard(String cardName, String cardEdition) {
if (filtered.isEmpty())
return false;
if (filtered.contains(cardName)) {
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
return true;
else return !exlcudedCardName.equalsIgnoreCase(cardName);
}
return false;
}
private void reIndex() {
uniqueCardsByName.clear();
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {

View File

@@ -52,6 +52,14 @@ import java.util.stream.Collectors;
*/
public final class CardEdition implements Comparable<CardEdition> {
public DraftOptions getDraftOptions() {
return draftOptions;
}
public void setDraftOptions(DraftOptions draftOptions) {
this.draftOptions = draftOptions;
}
// immutable
public enum Type {
UNKNOWN,
@@ -275,18 +283,22 @@ public final class CardEdition implements Comparable<CardEdition> {
// Booster/draft info
private List<BoosterSlot> boosterSlots = null;
private boolean smallSetOverride = false;
private boolean foilAlwaysInCommonSlot = false;
private String additionalUnlockSet = "";
private FoilType foilType = FoilType.NOT_SUPPORTED;
// Replace all of these things with booster slots
private boolean foilAlwaysInCommonSlot = false;
private double foilChanceInBooster = 0;
private double chanceReplaceCommonWith = 0;
private String slotReplaceCommonWith = "Common";
private String additionalSheetForFoils = "";
private String additionalUnlockSet = "";
private String boosterMustContain = "";
private String boosterReplaceSlotFromPrintSheet = "";
private String sheetReplaceCardFromSheet = "";
private String sheetReplaceCardFromSheet2 = "";
private String doublePickDuringDraft = "";
// Draft options
private DraftOptions draftOptions = null;
private String[] chaosDraftThemes = new String[0];
private final ListMultimap<String, EditionEntry> cardMap;
@@ -373,7 +385,6 @@ public final class CardEdition implements Comparable<CardEdition> {
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
public String getBoosterMustContain() { return boosterMustContain; }
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
@@ -619,7 +630,7 @@ public final class CardEdition implements Comparable<CardEdition> {
* functional variant name - grouping #9
*/
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
"(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
);
final Pattern tokenPattern = Pattern.compile(
@@ -628,7 +639,7 @@ public final class CardEdition implements Comparable<CardEdition> {
* name - grouping #3
* artist name - grouping #5
*/
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
);
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
@@ -649,31 +660,37 @@ public final class CardEdition implements Comparable<CardEdition> {
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);
String artistName = matcher.group(7);
String functionalVariantName = matcher.group(9);
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
cardMap.put(sectionName, cis);
}
} else if (boosterSlotsToParse.contains(sectionName)) {
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
if (sectionName.endsWith("Types")) {
CardType.Helper.parseTypes(sectionName, contents.get(sectionName));
} else {
// 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)
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
// Parse cards
// 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);
String artistName = matcher.group(7);
String functionalVariantName = matcher.group(9);
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
cardMap.put(sectionName, cis);
}
} else if (boosterSlotsToParse.contains(sectionName)) {
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
} else {
// 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)
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
}
}
}
@@ -802,7 +819,6 @@ public final class CardEdition implements Comparable<CardEdition> {
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
res.smallSetOverride = metadata.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
res.doublePickDuringDraft = metadata.get("DoublePick", ""); // "FirstPick" or "Always"
res.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
@@ -810,6 +826,23 @@ public final class CardEdition implements Comparable<CardEdition> {
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
// Draft options
String doublePick = metadata.get("DoublePick", "Never");
int maxPodSize = metadata.getInt("MaxPodSize", 8);
int recommendedPodSize = metadata.getInt("RecommendedPodSize", 8);
int maxMatchPlayers = metadata.getInt("MaxMatchPlayers", 2);
String deckType = metadata.get("DeckType", "Normal");
String freeCommander = metadata.get("FreeCommander", "");
res.draftOptions = new DraftOptions(
doublePick,
maxPodSize,
recommendedPodSize,
maxMatchPlayers,
deckType,
freeCommander
);
return res;
}
@@ -840,7 +873,7 @@ public final class CardEdition implements Comparable<CardEdition> {
@Override
public void add(CardEdition item) { //Even though we want it to be read only, make an exception for custom content.
if(lock) throw new UnsupportedOperationException("This is a read-only storage");
else map.put(item.getName(), item);
else map.put(item.getCode(), item);
}
public void append(CardEdition.Collection C){ //Append custom editions
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
@@ -985,16 +1018,13 @@ public final class CardEdition implements Comparable<CardEdition> {
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
@Deprecated //Use CardEdition::hasBasicLands and a nonnull test.
public static final Predicate<CardEdition> hasBasicLands = ed -> {
if (ed == null) {
// Happens for new sets with "???" code
return false;
}
for(String landName : MagicColor.Constant.BASIC_LANDS) {
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
return false;
}
return true;
return ed.hasBasicLands();
};
}
@@ -1015,7 +1045,7 @@ public final class CardEdition implements Comparable<CardEdition> {
public boolean hasBasicLands() {
for(String landName : MagicColor.Constant.BASIC_LANDS) {
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
if (this.getCardInSet(landName).isEmpty())
return false;
}
return true;

View File

@@ -53,6 +53,7 @@ public final class CardRules implements ICardCharacteristics {
private boolean addsWildCardColor;
private int setColorID;
private boolean custom;
private boolean unsupported;
private String path;
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
@@ -167,21 +168,7 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean isTransformable() {
if (CardSplitType.Transform == getSplitType()) {
return true;
}
if (CardSplitType.Modal != getSplitType()) {
return false;
}
for (ICardFace face : getAllFaces()) {
for (String spell : face.getAbilities()) {
if (spell.contains("AB$ SetState") && spell.contains("Mode$ Transform")) {
return true;
}
}
// TODO check keywords if needed
}
return false;
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
}
public ICardFace getWSpecialize() {
@@ -220,7 +207,9 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean isCustom() { return custom; }
public void setCustom() { custom = true; }
public void setCustom() { custom = true; }
public boolean isUnsupported() { return unsupported; }
@Override
public CardType getType() {
@@ -335,6 +324,15 @@ public final class CardRules implements ICardCharacteristics {
}
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
legal = true; // Stranger Things Secret Lair gimmick partner commander
}
if (hasKeyword("Partner - Survivors") && b.hasKeyword("Partner - Survivors")) {
legal = true; // The Last of Us Secret Lair gimmick partner commander
}
if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) {
legal = true; // God of War Secret Lair gimmick partner commander
}
if (hasKeyword("Partner - Character select") && b.hasKeyword("Partner - Character select")) {
legal = true; // TMNT Commander deck gimmick partner commander
}
if (hasKeyword("Choose a Background") && b.canBeBackground()
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
@@ -353,6 +351,7 @@ public final class CardRules implements ICardCharacteristics {
}
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") || hasKeyword("Partner - Character select") ||
hasKeyword("Doctor's companion") || isDoctor());
}
@@ -373,6 +372,9 @@ public final class CardRules implements ICardCharacteristics {
public boolean canBeOathbreaker() {
CardType type = mainPart.getType();
if (mainPart.getOracleText().contains("can be your commander")) {
return true;
}
return type.isPlaneswalker();
}
@@ -825,6 +827,8 @@ public final class CardRules implements ICardCharacteristics {
faces[0].assignMissingFields();
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
result.unsupported = true;
return result;
}

View File

@@ -196,6 +196,31 @@ public final class CardRulesPredicates {
return card -> card.getSplitType().equals(type);
}
/**
* @return a Predicate that matches cards that are vanilla.
*/
public static Predicate<CardRules> isVanilla() {
return card -> {
if (!(card.getType().isCreature() || card.getType().isLand()) ||
card.getSplitType() != CardSplitType.None ||
card.hasFunctionalVariants()) {
return false;
}
ICardFace mainPart = card.getMainPart();
boolean hasAny =
mainPart.getKeywords().iterator().hasNext() ||
mainPart.getAbilities().iterator().hasNext() ||
mainPart.getStaticAbilities().iterator().hasNext() ||
mainPart.getTriggers().iterator().hasNext() ||
(mainPart.getDraftActions() != null && mainPart.getDraftActions().iterator().hasNext()) ||
mainPart.getReplacements().iterator().hasNext();
return !hasAny;
};
}
/**
* Checks for color.
*

View File

@@ -1066,4 +1066,74 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return type;
}
public static class Helper {
public static final void parseTypes(String sectionName, List<String> content) {
Set<String> addToSection = null;
switch (sectionName) {
case "BasicTypes":
addToSection = CardType.Constant.BASIC_TYPES;
break;
case "LandTypes":
addToSection = CardType.Constant.LAND_TYPES;
break;
case "CreatureTypes":
addToSection = CardType.Constant.CREATURE_TYPES;
break;
case "SpellTypes":
addToSection = CardType.Constant.SPELL_TYPES;
break;
case "EnchantmentTypes":
addToSection = CardType.Constant.ENCHANTMENT_TYPES;
break;
case "ArtifactTypes":
addToSection = CardType.Constant.ARTIFACT_TYPES;
break;
case "WalkerTypes":
addToSection = CardType.Constant.WALKER_TYPES;
break;
case "DungeonTypes":
addToSection = CardType.Constant.DUNGEON_TYPES;
break;
case "BattleTypes":
addToSection = CardType.Constant.BATTLE_TYPES;
break;
case "PlanarTypes":
addToSection = CardType.Constant.PLANAR_TYPES;
break;
}
if (addToSection == null) {
return;
}
for(String line : content) {
if (line.length() == 0) continue;
if (line.contains(":")) {
String[] k = line.split(":");
if (addToSection.contains(k[0])) {
continue;
}
addToSection.add(k[0]);
CardType.Constant.pluralTypes.put(k[0], k[1]);
if (k[0].contains(" ")) {
CardType.Constant.MultiwordTypes.add(k[0]);
}
} else {
if (addToSection.contains(line)) {
continue;
}
addToSection.add(line);
if (line.contains(" ")) {
CardType.Constant.MultiwordTypes.add(line);
}
}
}
}
}
}

View File

@@ -17,15 +17,12 @@
*/
package forge.card;
import com.google.common.collect.UnmodifiableIterator;
import forge.card.MagicColor.Color;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.util.BinaryUtil;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -38,28 +35,64 @@ import java.util.stream.Stream;
*
*
*/
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
private static final long serialVersionUID = 794691267379929080L;
public enum ColorSet implements Iterable<Color>, Serializable {
private final byte myColor;
C(Color.COLORLESS),
W(Color.WHITE),
U(Color.BLUE),
WU(Color.WHITE, Color.BLUE),
B(Color.BLACK),
WB(Color.WHITE, Color.BLACK),
UB(Color.BLUE, Color.BLACK),
WUB(Color.WHITE, Color.BLUE, Color.BLACK),
R(Color.RED),
RW(Color.RED, Color.WHITE),
UR(Color.BLUE, Color.RED),
URW(Color.BLUE, Color.RED, Color.WHITE),
BR(Color.BLACK, Color.RED),
RWB(Color.RED, Color.WHITE, Color.BLACK),
UBR(Color.BLUE, Color.BLACK, Color.RED),
WUBR(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED),
G(Color.GREEN),
GW(Color.GREEN, Color.WHITE),
GU(Color.GREEN, Color.BLUE),
GWU(Color.GREEN, Color.WHITE, Color.BLUE),
BG(Color.BLACK, Color.GREEN),
WBG(Color.WHITE, Color.BLACK, Color.GREEN),
BGU(Color.BLACK, Color.GREEN, Color.BLUE),
GWUB(Color.GREEN, Color.WHITE, Color.BLUE, Color.BLACK),
RG(Color.RED, Color.GREEN),
RGW(Color.RED, Color.GREEN, Color.WHITE),
GUR(Color.GREEN, Color.BLUE, Color.RED),
RGWU(Color.RED, Color.GREEN, Color.WHITE, Color.BLUE),
BRG(Color.BLACK, Color.RED, Color.GREEN),
BRGW(Color.BLACK, Color.RED, Color.GREEN, Color.WHITE),
UBRG(Color.BLUE, Color.BLACK, Color.RED, Color.GREEN),
WUBRG(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED, Color.GREEN)
;
private static final long serialVersionUID = 794691267379929080L;
// needs to be before other static
private final Collection<Color> orderedShards;
private final float orderWeight;
private static final ColorSet[] cache = new ColorSet[32];
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
private ColorSet(final byte mask) {
this.myColor = mask;
this.orderWeight = this.getOrderWeight();
private ColorSet(final Color... ordered) {
this.orderedShards = Arrays.asList(ordered);
this.orderWeight = this.calcOrderWeight();
}
public static ColorSet fromMask(final int mask) {
final int mask32 = mask & MagicColor.ALL_COLORS;
if (cache[mask32] == null) {
cache[mask32] = new ColorSet((byte) mask32);
return values()[mask32];
}
public static ColorSet fromEnums(final Color... colors) {
byte mask = 0;
for (Color e : colors) {
mask |= e.getColorMask();
}
return cache[mask32];
return fromMask(mask);
}
public static ColorSet fromNames(final String... colors) {
@@ -98,7 +131,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if successful
*/
public boolean hasAnyColor(final int colormask) {
return (this.myColor & colormask) != 0;
return (this.ordinal() & colormask) != 0;
}
public boolean hasAnyColor(final Color c) {
return this.orderedShards.contains(c);
}
/**
@@ -109,12 +145,12 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if successful
*/
public boolean hasAllColors(final int colormask) {
return (this.myColor & colormask) == colormask;
return (this.ordinal() & colormask) == colormask;
}
/** this has exactly the colors defined by operand. */
public boolean hasExactlyColor(final int colormask) {
return this.myColor == colormask;
return this.ordinal() == colormask;
}
/** this has no other colors except defined by operand. */
@@ -124,17 +160,17 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
/** this has no other colors except defined by operand. */
public boolean hasNoColorsExcept(final int colormask) {
return (this.myColor & ~colormask) == 0;
return (this.ordinal() & ~colormask) == 0;
}
/** This returns the colors that colormask contains that are not in color */
public ColorSet getMissingColors(final byte colormask) {
return fromMask(this.myColor & ~colormask);
return fromMask(this.ordinal() & ~colormask);
}
/** Operand has no other colors except defined by this. */
public boolean containsAllColorsFrom(final int colorProfile) {
return (~this.myColor & colorProfile) == 0;
return (~this.ordinal() & colorProfile) == 0;
}
/**
@@ -143,7 +179,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return the int
*/
public int countColors() {
return BinaryUtil.bitCount(this.myColor);
return BinaryUtil.bitCount(this.ordinal());
} // bit count
// order has to be: W U B R G multi colorless - same as cards numbering
@@ -153,7 +189,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
*
* @return the order weight
*/
private float getOrderWeight() {
private float calcOrderWeight() {
float res = this.countColors();
if (hasWhite()) {
res += 0.0005f;
@@ -172,6 +208,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
}
return res;
}
public float getOrderWeight()
{
return orderWeight;
}
/**
* Checks if is colorless.
@@ -179,7 +219,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if is colorless
*/
public boolean isColorless() {
return this.myColor == 0;
return this == C;
}
/**
@@ -197,7 +237,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if is all colors
*/
public boolean isAllColors() {
return this == ALL_COLORS;
return this == WUBRG;
}
/**
@@ -217,17 +257,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if is equal
*/
public boolean isEqual(final byte color) {
return color == this.myColor;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(final ColorSet other) {
return Float.compare(this.orderWeight, other.orderWeight);
return color == this.ordinal();
}
// Presets
@@ -277,33 +307,13 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
}
public ColorSet inverse() {
byte mask = this.myColor;
byte mask = (byte)this.ordinal();
mask ^= MagicColor.ALL_COLORS;
return fromMask(mask);
}
public byte getColor() {
return myColor;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final ManaCostShard[] orderedShards = getOrderedShards();
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
}
/**
* Gets the null color.
*
* @return the nullColor
*/
public static ColorSet getNullColor() {
return NO_COLORS;
return (byte)ordinal();
}
/**
@@ -313,7 +323,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
* @return true, if successful
*/
public boolean sharesColorWith(final ColorSet ccOther) {
return (this.myColor & ccOther.myColor) != 0;
return (this.ordinal() & ccOther.ordinal()) != 0;
}
public ColorSet getSharedColors(final ColorSet ccOther) {
@@ -321,123 +331,24 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
}
public ColorSet getOffColors(final ColorSet ccOther) {
return fromMask(~this.myColor & ccOther.myColor);
return fromMask(~this.ordinal() & ccOther.ordinal());
}
public Set<Color> toEnumSet() {
if (isColorless()) {
return EnumSet.of(Color.COLORLESS);
}
List<Color> list = new ArrayList<>();
for (Color c : Color.values()) {
if (hasAnyColor(c.getColormask())) {
list.add(c);
}
}
return EnumSet.copyOf(list);
return EnumSet.copyOf(orderedShards);
}
@Override
public Iterator<Byte> iterator() {
return new ColorIterator();
//@Override
public Iterator<Color> iterator() {
return this.orderedShards.iterator();
}
private class ColorIterator extends UnmodifiableIterator<Byte> {
int currentBit = -1;
private int getIndexOfNextColor(){
int nextBit = currentBit + 1;
while (nextBit < MagicColor.NUMBER_OR_COLORS) {
if ((myColor & MagicColor.WUBRG[nextBit]) != 0) {
break;
}
nextBit++;
}
return nextBit;
}
@Override
public boolean hasNext() {
return getIndexOfNextColor() < MagicColor.NUMBER_OR_COLORS;
}
@Override
public Byte next() {
currentBit = getIndexOfNextColor();
if (currentBit >= MagicColor.NUMBER_OR_COLORS) {
throw new NoSuchElementException();
}
return MagicColor.WUBRG[currentBit];
}
}
public Stream<MagicColor.Color> stream() {
return this.toEnumSet().stream();
public Stream<Color> stream() {
return this.orderedShards.stream();
}
//Get array of mana cost shards for color set in the proper order
public ManaCostShard[] getOrderedShards() {
return shardOrderLookup[myColor];
}
private static final ManaCostShard[][] shardOrderLookup = new ManaCostShard[MagicColor.ALL_COLORS + 1][];
static {
byte COLORLESS = MagicColor.COLORLESS;
byte WHITE = MagicColor.WHITE;
byte BLUE = MagicColor.BLUE;
byte BLACK = MagicColor.BLACK;
byte RED = MagicColor.RED;
byte GREEN = MagicColor.GREEN;
ManaCostShard C = ManaCostShard.COLORLESS;
ManaCostShard W = ManaCostShard.WHITE;
ManaCostShard U = ManaCostShard.BLUE;
ManaCostShard B = ManaCostShard.BLACK;
ManaCostShard R = ManaCostShard.RED;
ManaCostShard G = ManaCostShard.GREEN;
//colorless
shardOrderLookup[COLORLESS] = new ManaCostShard[] { C };
//mono-color
shardOrderLookup[WHITE] = new ManaCostShard[] { W };
shardOrderLookup[BLUE] = new ManaCostShard[] { U };
shardOrderLookup[BLACK] = new ManaCostShard[] { B };
shardOrderLookup[RED] = new ManaCostShard[] { R };
shardOrderLookup[GREEN] = new ManaCostShard[] { G };
//two-color
shardOrderLookup[WHITE | BLUE] = new ManaCostShard[] { W, U };
shardOrderLookup[WHITE | BLACK] = new ManaCostShard[] { W, B };
shardOrderLookup[BLUE | BLACK] = new ManaCostShard[] { U, B };
shardOrderLookup[BLUE | RED] = new ManaCostShard[] { U, R };
shardOrderLookup[BLACK | RED] = new ManaCostShard[] { B, R };
shardOrderLookup[BLACK | GREEN] = new ManaCostShard[] { B, G };
shardOrderLookup[RED | GREEN] = new ManaCostShard[] { R, G };
shardOrderLookup[RED | WHITE] = new ManaCostShard[] { R, W };
shardOrderLookup[GREEN | WHITE] = new ManaCostShard[] { G, W };
shardOrderLookup[GREEN | BLUE] = new ManaCostShard[] { G, U };
//three-color
shardOrderLookup[WHITE | BLUE | BLACK] = new ManaCostShard[] { W, U, B };
shardOrderLookup[WHITE | BLACK | GREEN] = new ManaCostShard[] { W, B, G };
shardOrderLookup[BLUE | BLACK | RED] = new ManaCostShard[] { U, B, R };
shardOrderLookup[BLUE | RED | WHITE] = new ManaCostShard[] { U, R, W };
shardOrderLookup[BLACK | RED | GREEN] = new ManaCostShard[] { B, R, G };
shardOrderLookup[BLACK | GREEN | BLUE] = new ManaCostShard[] { B, G, U };
shardOrderLookup[RED | GREEN | WHITE] = new ManaCostShard[] { R, G, W };
shardOrderLookup[RED | WHITE | BLACK] = new ManaCostShard[] { R, W, B };
shardOrderLookup[GREEN | WHITE | BLUE] = new ManaCostShard[] { G, W, U };
shardOrderLookup[GREEN | BLUE | RED] = new ManaCostShard[] { G, U, R };
//four-color
shardOrderLookup[WHITE | BLUE | BLACK | RED] = new ManaCostShard[] { W, U, B, R };
shardOrderLookup[BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { U, B, R, G };
shardOrderLookup[BLACK | RED | GREEN | WHITE] = new ManaCostShard[] { B, R, G, W };
shardOrderLookup[RED | GREEN | WHITE | BLUE] = new ManaCostShard[] { R, G, W, U };
shardOrderLookup[GREEN | WHITE | BLUE | BLACK] = new ManaCostShard[] { G, W, U, B };
//five-color
shardOrderLookup[WHITE | BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { W, U, B, R, G };
public Collection<Color> getOrderedColors() {
return orderedShards;
}
}

View File

@@ -0,0 +1,75 @@
package forge.card;
public class DraftOptions {
public enum DoublePick {
NEVER,
FIRST_PICK, // only first pick each pack
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
ALWAYS // each time you receive a pack, you can pick two cards
};
public enum DeckType {
Normal, // Standard deck, usually 40 cards
Commander // Special deck type for Commander format. Important for selection/construction
}
private DoublePick doublePick = DoublePick.NEVER;
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
private final DeckType deckType; // Normal or Commander
private final String freeCommander;
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
this.maxPodSize = maxPodSize;
this.recommendedPodSize = recommendedPodSize;
this.maxMatchPlayers = maxMatchPlayers;
this.deckType = DeckType.valueOf(deckType);
this.freeCommander = freeCommander;
if (doublePickOption != null) {
switch (doublePickOption.toLowerCase()) {
case "firstpick":
doublePick = DoublePick.FIRST_PICK;
break;
case "always":
doublePick = DoublePick.ALWAYS;
break;
case "whenpodsizeis4":
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
break;
}
}
}
public int getMaxPodSize() {
return maxPodSize;
}
public int getRecommendedPodSize() {
return recommendedPodSize;
}
public DoublePick getDoublePick() {
return doublePick;
}
public DoublePick isDoublePick(int podSize) {
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
if (podSize != 4) {
return DoublePick.NEVER;
}
// only when pod size is 4, so you can pick two cards each time
return DoublePick.ALWAYS;
}
return doublePick;
}
public int getMaxMatchPlayers() {
return maxMatchPlayers;
}
public DeckType getDeckType() {
return deckType;
}
public String getFreeCommander() {
return freeCommander;
}
}

View File

@@ -75,8 +75,6 @@ public interface ICardDatabase extends Iterable<PaperCard> {
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate);
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate, Predicate<PaperCard> filter);
/* CARDS COLLECTION RETRIEVAL METHODS
* ================================== */
Collection<PaperCard> getAllCards();

View File

@@ -1,7 +1,9 @@
package forge.card;
import com.google.common.collect.ImmutableList;
import forge.deck.DeckRecognizer;
import forge.util.ITranslatable;
import forge.util.Localizer;
/**
* Holds byte values for each color magic has.
@@ -157,21 +159,24 @@ public final class MagicColor {
}
}
public enum Color {
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
RED(Constant.RED, MagicColor.RED, "{R}"),
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
public enum Color implements ITranslatable {
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "C", "lblColorless");
private final String name, symbol;
private final String name, shortName, symbol;
private final String label;
private final byte colormask;
Color(String name0, byte colormask0, String symbol0) {
Color(String name0, byte colormask0, String shortName, String label) {
name = name0;
colormask = colormask0;
symbol = symbol0;
this.shortName = shortName;
symbol = "{" + shortName + "}";
this.label = label;
}
public static Color fromByte(final byte color) {
@@ -185,25 +190,25 @@ public final class MagicColor {
}
}
@Override
public String getName() {
return name;
}
public String getLocalizedName() {
//Should probably move some of this logic back here, or at least to a more general location.
return DeckRecognizer.getLocalisedMagicColorName(getName());
public String getShortName() {
return shortName;
}
public byte getColormask() {
@Override
public String getTranslatedName() {
return Localizer.getInstance().getMessage(label);
}
public byte getColorMask() {
return colormask;
}
public String getSymbol() {
return symbol;
}
@Override
public String toString() {
return name;
}
}
}

View File

@@ -17,6 +17,7 @@
*/
package forge.card.mana;
import forge.card.ColorSet;
import forge.util.BinaryUtil;
/**
@@ -34,52 +35,51 @@ public enum ManaCostShard {
COLORLESS(ManaAtom.COLORLESS, "C"),
/* Hybrid */
WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U", "WU"),
WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B", "WB"),
UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B", "UB"),
UR(ManaAtom.BLUE | ManaAtom.RED, "U/R", "UR"),
BR(ManaAtom.BLACK | ManaAtom.RED, "B/R", "BR"),
BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G", "BG"),
RW(ManaAtom.RED | ManaAtom.WHITE, "R/W", "RW"),
RG(ManaAtom.RED | ManaAtom.GREEN, "R/G", "RG"),
GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W", "GW"),
GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U", "GU"),
WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U"),
WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B"),
UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B"),
UR(ManaAtom.BLUE | ManaAtom.RED, "U/R"),
BR(ManaAtom.BLACK | ManaAtom.RED, "B/R"),
BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G"),
RW(ManaAtom.RED | ManaAtom.WHITE, "R/W"),
RG(ManaAtom.RED | ManaAtom.GREEN, "R/G"),
GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W"),
GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U"),
/* Or 2 generic */
W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W", "2W"),
U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U", "2U"),
B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B", "2B"),
R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R", "2R"),
G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G", "2G"),
W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W"),
U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U"),
B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B"),
R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R"),
G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G"),
/* Or Colorless */
CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W", "CW"),
CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U", "CU"),
CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B", "CB"),
CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R", "CR"),
CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G", "CG"),
CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W"),
CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U"),
CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B"),
CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R"),
CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G"),
// Snow and colorless
S(ManaAtom.IS_SNOW, "S"),
GENERIC(ManaAtom.GENERIC, "1"),
/* Phyrexian */
WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P", "WP"),
UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P", "UP"),
BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P", "BP"),
RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P", "RP"),
GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P", "GP"),
BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P", "BGP"),
BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P", "BRP"),
GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P", "GUP"),
GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P", "GWP"),
RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P", "RGP"),
RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P", "RWP"),
UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P", "UBP"),
URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P", "URP"),
WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P", "WBP"),
WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P", "WUP"),
WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P"),
UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P"),
BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P"),
RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P"),
GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P"),
BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P"),
BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P"),
GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P"),
GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P"),
RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P"),
RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P"),
UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P"),
URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P"),
WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P"),
WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P"),
X(ManaAtom.IS_X, "X"),
@@ -108,26 +108,12 @@ public enum ManaCostShard {
* the s value
*/
ManaCostShard(final int value, final String sValue) {
this(value, sValue, sValue);
}
/**
* Instantiates a new card mana cost shard.
*
* @param value
* the value
* @param sValue
* the s value
* @param imgKey
* the img key
*/
ManaCostShard(final int value, final String sValue, final String imgKey) {
this.shard = value;
this.cmc = this.getCMC();
this.cmpc = this.getCmpCost();
this.stringValue = "{" + sValue + "}";
this.shortStringValue = sValue;
this.imageKey = imgKey;
this.imageKey = sValue.replace("/", "");
}
public static final int COLORS_SUPERPOSITION = ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN;
@@ -186,6 +172,10 @@ public enum ManaCostShard {
return (byte)(this.shard & COLORS_SUPERPOSITION);
}
public final ColorSet getColor() {
return ColorSet.fromMask(getColorMask());
}
/**
* Value of.
*

View File

@@ -115,6 +115,20 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return parts.get(DeckSection.Main);
}
public Pair<Deck, List<PaperCard>> getValid() {
List<PaperCard> unsupported = new ArrayList<>();
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
CardPool pool = kv.getValue();
for (Entry<PaperCard, Integer> pc : pool) {
if (pc.getKey().getRules() != null && pc.getKey().getRules().isUnsupported()) {
unsupported.add(pc.getKey());
pool.remove(pc.getKey());
}
}
}
return Pair.of(this, unsupported);
}
public List<PaperCard> getCommanders() {
List<PaperCard> result = Lists.newArrayList();
final CardPool cp = get(DeckSection.Commander);

View File

@@ -22,6 +22,7 @@ import forge.StaticData;
import forge.card.CardDb;
import forge.card.CardEdition;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.item.IPaperCard;
import forge.item.PaperCard;
@@ -49,6 +50,16 @@ public class DeckRecognizer {
LIMITED_CARD,
CARD_FROM_NOT_ALLOWED_SET,
CARD_FROM_INVALID_SET,
/**
* Valid card request, but can't be imported because the player does not have enough copies.
* Should be replaced with a different printing if possible.
*/
CARD_NOT_IN_INVENTORY,
/**
* Valid card request for a card that isn't in the player's inventory, but new copies can be acquired freely.
* Usually used for basic lands. Should be supplied to the import controller by the editor.
*/
FREE_CARD_NOT_IN_INVENTORY,
// Warning messages
WARNING_MESSAGE,
UNKNOWN_CARD,
@@ -63,10 +74,14 @@ public class DeckRecognizer {
CARD_TYPE,
CARD_RARITY,
CARD_CMC,
MANA_COLOUR
MANA_COLOUR;
public static final EnumSet<TokenType> CARD_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY);
public static final EnumSet<TokenType> IN_DECK_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, DECK_NAME, FREE_CARD_NOT_IN_INVENTORY);
public static final EnumSet<TokenType> CARD_PLACEHOLDER_TOKEN_TYPES = EnumSet.of(CARD_TYPE, CARD_RARITY, CARD_CMC, MANA_COLOUR);
}
public enum LimitedCardType{
public enum LimitedCardType {
BANNED,
RESTRICTED,
}
@@ -108,6 +123,10 @@ public class DeckRecognizer {
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
}
public static Token NotInInventoryFree(final PaperCard card, final int count, final DeckSection section) {
return new Token(TokenType.FREE_CARD_NOT_IN_INVENTORY, count, card, section, true);
}
// WARNING MESSAGES
// ================
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
@@ -126,6 +145,10 @@ public class DeckRecognizer {
return new Token(TokenType.WARNING_MESSAGE, msg);
}
public static Token NotInInventory(final PaperCard card, final int count, final DeckSection section) {
return new Token(TokenType.CARD_NOT_IN_INVENTORY, count, card, section, false);
}
/* =================================
* DECK SECTIONS
* ================================= */
@@ -239,14 +262,11 @@ public class DeckRecognizer {
/**
* Filters all token types that have a PaperCard instance set (not null)
* @return true for tokens of type:
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
* False otherwise.
*/
public boolean isCardToken() {
return (this.type == TokenType.LEGAL_CARD ||
this.type == TokenType.LIMITED_CARD ||
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
this.type == TokenType.CARD_FROM_INVALID_SET);
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
}
/**
@@ -255,9 +275,7 @@ public class DeckRecognizer {
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
*/
public boolean isTokenForDeck() {
return (this.type == TokenType.LEGAL_CARD ||
this.type == TokenType.LIMITED_CARD ||
this.type == TokenType.DECK_NAME);
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
}
/**
@@ -266,7 +284,7 @@ public class DeckRecognizer {
* False otherwise.
*/
public boolean isCardTokenForDeck() {
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
return isCardToken() && isTokenForDeck();
}
/**
@@ -276,10 +294,7 @@ public class DeckRecognizer {
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
*/
public boolean isCardPlaceholder(){
return (this.type == TokenType.CARD_RARITY ||
this.type == TokenType.CARD_CMC ||
this.type == TokenType.MANA_COLOUR ||
this.type == TokenType.CARD_TYPE);
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
}
/** Determines if current token is a Deck Section token
@@ -536,7 +551,7 @@ public class DeckRecognizer {
PaperCard tokenCard = token.getCard();
if (isAllowed(tokenSection)) {
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
if (tokenSection != referenceDeckSectionInParsing) {
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
// just check that last token is stack is a card placeholder.
// In that case, add the new section token before the placeholder
@@ -575,7 +590,7 @@ public class DeckRecognizer {
refLine = purgeAllLinks(refLine);
String line;
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
else
line = refLine.trim(); // Remove any trailing formatting
@@ -584,7 +599,7 @@ public class DeckRecognizer {
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
if (!line.contains("Summon:"))
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
line = line.substring(2);
// == Patches to Corner Cases
@@ -600,8 +615,8 @@ public class DeckRecognizer {
Token result = recogniseCardToken(line, referenceSection);
if (result == null)
result = recogniseNonCardToken(line);
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
}
@@ -613,7 +628,7 @@ public class DeckRecognizer {
while (m.find()) {
line = line.replaceAll(m.group(), "").trim();
}
if (StringUtils.endsWith(line, "()"))
if (line.endsWith("()"))
return line.substring(0, line.length()-2);
return line;
}
@@ -741,21 +756,12 @@ public class DeckRecognizer {
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
if (deckSec != null) {
DeckSection cardSection;
switch (deckSec.toUpperCase().trim()) {
case "MB":
cardSection = DeckSection.Main;
break;
case "SB":
cardSection = DeckSection.Sideboard;
break;
case "CM":
cardSection = DeckSection.Commander;
break;
default:
cardSection = DeckSection.matchingSection(card);
break;
}
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
case "MB" -> DeckSection.Main;
case "SB" -> DeckSection.Sideboard;
case "CM" -> DeckSection.Commander;
default -> DeckSection.matchingSection(card);
};
if (cardSection.validate(card))
return cardSection;
}
@@ -988,80 +994,37 @@ public class DeckRecognizer {
private static String getMagicColourLabel(MagicColor.Color magicColor) {
if (magicColor == null) // Multicolour
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
return String.format("%s {W}{U}{B}{R}{G}", Localizer.getInstance().getMessage("lblMulticolor"));
return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol());
}
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
put(MagicColor.WHITE | MagicColor.BLUE, "WU");
put(MagicColor.BLUE | MagicColor.BLACK, "UB");
put(MagicColor.BLACK | MagicColor.RED, "BR");
put(MagicColor.RED | MagicColor.GREEN, "RG");
put(MagicColor.GREEN | MagicColor.WHITE, "GW");
put(MagicColor.WHITE | MagicColor.BLACK, "WB");
put(MagicColor.BLUE | MagicColor.RED, "UR");
put(MagicColor.BLACK | MagicColor.GREEN, "BG");
put(MagicColor.RED | MagicColor.WHITE, "RW");
put(MagicColor.GREEN | MagicColor.BLUE, "GU");
}};
private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2){
private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2) {
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|| magicColor1 == MagicColor.Color.COLORLESS)
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
String localisedName1 = magicColor1.getLocalizedName();
String localisedName2 = magicColor2.getLocalizedName();
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
String localisedName1 = magicColor1.getTranslatedName();
String localisedName2 = magicColor2.getTranslatedName();
return String.format("%s/%s {%s}", localisedName1, localisedName2, ColorSet.fromEnums(magicColor1, magicColor2));
}
private static MagicColor.Color getMagicColor(String colorName){
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
return null; // will be handled separately
byte color = MagicColor.fromName(colorName.toLowerCase());
switch (color) {
case MagicColor.WHITE:
return MagicColor.Color.WHITE;
case MagicColor.BLUE:
return MagicColor.Color.BLUE;
case MagicColor.BLACK:
return MagicColor.Color.BLACK;
case MagicColor.RED:
return MagicColor.Color.RED;
case MagicColor.GREEN:
return MagicColor.Color.GREEN;
default:
return MagicColor.Color.COLORLESS;
}
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
}
public static String getLocalisedMagicColorName(String colorName){
Localizer localizer = Localizer.getInstance();
switch(colorName.toLowerCase()){
case MagicColor.Constant.WHITE:
return localizer.getMessage("lblWhite");
case MagicColor.Constant.BLUE:
return localizer.getMessage("lblBlue");
case MagicColor.Constant.BLACK:
return localizer.getMessage("lblBlack");
case MagicColor.Constant.RED:
return localizer.getMessage("lblRed");
case MagicColor.Constant.GREEN:
return localizer.getMessage("lblGreen");
case MagicColor.Constant.COLORLESS:
return localizer.getMessage("lblColorless");
case "multicolour":
case "multicolor":
return localizer.getMessage("lblMulticolor");
default:
return "";
}
return switch (colorName.toLowerCase()) {
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
default -> "";
};
}
/**
@@ -1080,37 +1043,6 @@ public class DeckRecognizer {
return "";
}
private static Pair<String, String> getManaNameAndSymbol(String matchedMana) {
if (matchedMana == null)
return null;
Localizer localizer = Localizer.getInstance();
switch (matchedMana.toLowerCase()) {
case MagicColor.Constant.WHITE:
case "w":
return Pair.of(localizer.getMessage("lblWhite"), MagicColor.Color.WHITE.getSymbol());
case MagicColor.Constant.BLUE:
case "u":
return Pair.of(localizer.getMessage("lblBlue"), MagicColor.Color.BLUE.getSymbol());
case MagicColor.Constant.BLACK:
case "b":
return Pair.of(localizer.getMessage("lblBlack"), MagicColor.Color.BLACK.getSymbol());
case MagicColor.Constant.RED:
case "r":
return Pair.of(localizer.getMessage("lblRed"), MagicColor.Color.RED.getSymbol());
case MagicColor.Constant.GREEN:
case "g":
return Pair.of(localizer.getMessage("lblGreen"), MagicColor.Color.GREEN.getSymbol());
case MagicColor.Constant.COLORLESS:
case "c":
return Pair.of(localizer.getMessage("lblColorless"), MagicColor.Color.COLORLESS.getSymbol());
default: // Multicolour
return Pair.of(localizer.getMessage("lblMulticolor"), "");
}
}
public static boolean isDeckName(final String lineAsIs) {
if (lineAsIs == null)
return false;

View File

@@ -52,9 +52,4 @@ public interface IPaperCard extends InventoryItem, Serializable {
default String getUntranslatedType() {
return getRules().getType().toString();
}
@Override
default String getUntranslatedOracle() {
return getRules().getOracleText();
}
}

View File

@@ -375,7 +375,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
pc = readObjectAlternate(name, edition);
if (pc == null) {
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
pc = StaticData.instance().getCommonCards().createUnsupportedCard(name);
//throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
}
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
}
@@ -592,7 +593,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
if(markedColors == null)
markedColors = ColorSet.getNullColor();
markedColors = ColorSet.C;
return new PaperCardFlags(this, markedColors, null);
}

View File

@@ -57,6 +57,42 @@ public abstract class PaperCardPredicates {
return new PredicateFoil(isFoil);
}
/**
* Filters cards that were printed in any of the specified editions.
*/
public static Predicate<PaperCard> printedInAnyEditions(final String[] editionCodes) {
Set<String> editions = new HashSet<>(Arrays.asList(editionCodes));
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
.map(PaperCard::getEdition).anyMatch(editionCode ->
editions.contains(editionCode) &&
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
);
}
/**
* Filters cards that only printed in any of the specified editions.
*/
public static Predicate<PaperCard> onlyPrintedInEditions(final String[] editionCodes) {
Set<String> editions = new HashSet<>(Arrays.asList(editionCodes));
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
.map(PaperCard::getEdition).allMatch(editionCode ->
editions.contains(editionCode) &&
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
);
}
/**
* Filters cards that are obtainable in any edition.
*/
public static Predicate<PaperCard> isObtainableAnyEdition() {
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
.map(PaperCard::getEdition).anyMatch(editionCode ->
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
);
}
private static final class PredicatePrintedWithRarity implements Predicate<PaperCard> {
private final CardRarity matchingRarity;
@@ -76,25 +112,19 @@ public abstract class PaperCardPredicates {
}
private static final class PredicateColor implements Predicate<PaperCard> {
private final byte operand;
private final MagicColor.Color operand;
private PredicateColor(final byte color) {
private PredicateColor(final MagicColor.Color color) {
this.operand = color;
}
@Override
public boolean test(final PaperCard card) {
for (final byte color : card.getRules().getColor()) {
if (color == operand) {
return true;
}
if (card.getRules().getColor().hasAnyColor(operand)) {
return true;
}
if (card.getRules().getType().hasType(CardType.CoreType.Land)) {
for (final byte color : card.getRules().getColorIdentity()) {
if (color == operand) {
return true;
}
}
if (card.getRules().getType().hasType(CardType.CoreType.Land) && card.getRules().getColorIdentity().hasAnyColor(operand)) {
return true;
}
return false;
}
@@ -199,11 +229,11 @@ public abstract class PaperCardPredicates {
public static final Predicate<PaperCard> IS_RARE_OR_MYTHIC = PaperCardPredicates.IS_RARE.or(PaperCardPredicates.IS_MYTHIC_RARE);
public static final Predicate<PaperCard> IS_SPECIAL = new PredicateRarity(CardRarity.Special);
public static final Predicate<PaperCard> IS_BASIC_LAND_RARITY = new PredicateRarity(CardRarity.BasicLand);
public static final Predicate<PaperCard> IS_BLACK = new PredicateColor(MagicColor.BLACK);
public static final Predicate<PaperCard> IS_BLUE = new PredicateColor(MagicColor.BLUE);
public static final Predicate<PaperCard> IS_GREEN = new PredicateColor(MagicColor.GREEN);
public static final Predicate<PaperCard> IS_RED = new PredicateColor(MagicColor.RED);
public static final Predicate<PaperCard> IS_WHITE = new PredicateColor(MagicColor.WHITE);
public static final Predicate<PaperCard> IS_BLACK = new PredicateColor(MagicColor.Color.BLACK);
public static final Predicate<PaperCard> IS_BLUE = new PredicateColor(MagicColor.Color.BLUE);
public static final Predicate<PaperCard> IS_GREEN = new PredicateColor(MagicColor.Color.GREEN);
public static final Predicate<PaperCard> IS_RED = new PredicateColor(MagicColor.Color.RED);
public static final Predicate<PaperCard> IS_WHITE = new PredicateColor(MagicColor.Color.WHITE);
public static final Predicate<PaperCard> IS_COLORLESS = paperCard -> paperCard.getRules().getColor().isColorless();
public static final Predicate<PaperCard> IS_UNREBALANCED = PaperCard::isUnRebalanced;
public static final Predicate<PaperCard> IS_REBALANCED = PaperCard::isRebalanced;

View File

@@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return false;
CardSplitType cst = this.cardRules.getSplitType();
//expand this on future for other tokens that has other backsides besides transform..
return cst == CardSplitType.Transform;
return cst == CardSplitType.Transform || cst == CardSplitType.Modal;
}
@Override

View File

@@ -633,7 +633,10 @@ public class BoosterGenerator {
System.out.println("Parsing from main code: " + mainCode);
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
System.out.println("Attempting to lookup: " + sheetName);
src = tryGetStaticSheet(sheetName).toFlatList();
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
if (fromSheet == null)
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
src = fromSheet.toFlatList();
setPred = x -> true;
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet

View File

@@ -10,13 +10,11 @@ public interface ITranslatable extends IHasName {
default String getUntranslatedName() {
return getName();
}
default String getTranslatedName() {
return getName();
}
default String getUntranslatedType() {
return "";
}
default String getUntranslatedOracle() {
return "";
}
}

View File

@@ -207,8 +207,6 @@ public class ImageUtil {
else
editionCode = cp.getEdition().toLowerCase();
String cardCollectorNumber = cp.getCollectorNumber();
// Hack to account for variations in Arabian Nights
cardCollectorNumber = cardCollectorNumber.replace("+", "");
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
if (cardCollectorNumber.startsWith("OHOP")) {
editionCode = "ohop";
@@ -252,6 +250,11 @@ public class ImageUtil {
: "&face=front");
}
if (cardCollectorNumber.endsWith("")) {
faceParam = "&face=back";
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 1);
}
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, encodeUtf8(cardCollectorNumber),
langCode, versionParam, faceParam);
}
@@ -261,6 +264,10 @@ public class ImageUtil {
if (!faceParam.isEmpty()) {
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
}
if (collectorNumber.endsWith("")) {
faceParam = "&face=back";
collectorNumber = collectorNumber.substring(0, collectorNumber.length() - 1);
}
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
langCode, versionParam, faceParam);
}
@@ -281,8 +288,7 @@ public class ImageUtil {
char c;
for (int i = 0; i < in.length(); i++) {
c = in.charAt(i);
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
} else {
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
out.append(c);
}
}