diff --git a/.gitattributes b/.gitattributes
index 02ff9c557a0..9610e66c068 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -189,6 +189,7 @@ forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java -text
forge-core/src/main/java/forge/deck/generation/DeckGeneratorMonoColor.java -text
forge-core/src/main/java/forge/deck/generation/package-info.java svneol=native#text/plain
forge-core/src/main/java/forge/deck/io/DeckFileHeader.java -text
+forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java -text
forge-core/src/main/java/forge/deck/io/DeckSerializer.java -text
forge-core/src/main/java/forge/deck/io/DeckStorage.java -text
forge-core/src/main/java/forge/deck/io/OldDeckFileFormatException.java -text
@@ -15450,7 +15451,6 @@ forge-gui/src/main/java/forge/control/InputQueue.java svneol=native#text/plain
forge-gui/src/main/java/forge/control/KeyboardShortcuts.java -text
forge-gui/src/main/java/forge/control/RestartUtil.java -text
forge-gui/src/main/java/forge/control/package-info.java -text
-forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java -text
forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java -text
forge-gui/src/main/java/forge/deck/io/DeckPreferences.java -text
forge-gui/src/main/java/forge/deck/io/OldDeckParser.java -text
@@ -16024,9 +16024,15 @@ forge-m-base/src/forge/assets/FSkinTexture.java -text
forge-m-base/src/forge/assets/FTextureImage.java -text
forge-m-base/src/forge/assets/ImageCache.java -text
forge-m-base/src/forge/assets/ImageLoader.java -text
+forge-m-base/src/forge/deck/io/OldDeckParser.java -text
forge-m-base/src/forge/error/BugReporter.java -text
forge-m-base/src/forge/error/ExceptionHandler.java -text
+forge-m-base/src/forge/limited/CustomLimited.java -text
+forge-m-base/src/forge/model/CardBlock.java -text
+forge-m-base/src/forge/model/CardCollections.java -text
forge-m-base/src/forge/model/FModel.java -text
+forge-m-base/src/forge/model/MetaSet.java -text
+forge-m-base/src/forge/model/UnOpenedMeta.java -text
forge-m-base/src/forge/player/LobbyPlayerHuman.java -text
forge-m-base/src/forge/player/PlayerControllerHuman.java -text
forge-m-base/src/forge/screens/FScreen.java -text
@@ -16060,6 +16066,7 @@ forge-m-base/src/forge/toolbox/FOptionPane.java -text
forge-m-base/src/forge/toolbox/FOverlay.java -text
forge-m-base/src/forge/toolbox/FProgressBar.java -text
forge-m-base/src/forge/toolbox/FScrollPane.java -text
+forge-m-base/src/forge/toolbox/GuiChoose.java -text
forge-m-base/src/forge/utils/Constants.java -text
forge-m-base/src/forge/utils/ForgePreferences.java -text
forge-m-base/src/forge/utils/ForgeProfileProperties.java -text
diff --git a/forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java b/forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java
similarity index 100%
rename from forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java
rename to forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java
diff --git a/forge-m-base/src/forge/deck/io/OldDeckParser.java b/forge-m-base/src/forge/deck/io/OldDeckParser.java
new file mode 100644
index 00000000000..f160cb438b5
--- /dev/null
+++ b/forge-m-base/src/forge/deck/io/OldDeckParser.java
@@ -0,0 +1,275 @@
+/*
+ * 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 .
+ */
+package forge.deck.io;
+
+import forge.deck.Deck;
+import forge.deck.DeckGroup;
+import forge.toolbox.FOptionPane;
+import forge.util.FileSection;
+import forge.util.FileUtil;
+import forge.util.storage.IStorage;
+import forge.utils.Constants;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.TreeMap;
+
+/**
+ * TODO: Write javadoc for this type.
+ *
+ */
+public class OldDeckParser {
+
+ /** Constant BDKFileFilter. */
+ public static final FilenameFilter BDK_FILE_FILTER = new FilenameFilter() {
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return name.endsWith(".bdk");
+ }
+ };
+
+ /**
+ * TODO: Write javadoc for Constructor.
+ *
+ * @param file the file
+ * @param constructed2 the constructed2
+ * @param draft2 the draft2
+ * @param sealed2 the sealed2
+ * @param cube2 the cube2
+ */
+ public OldDeckParser(final IStorage constructed2, final IStorage draft2,
+ final IStorage sealed2, final IStorage cube2) {
+ this.deckDir = new File(Constants.DECK_BASE_DIR);
+ this.sealed = sealed2;
+ this.constructed = constructed2;
+ this.cube = cube2;
+ this.draft = draft2;
+ }
+
+ /**
+ * Gets the sealed.
+ *
+ * @return the sealed
+ */
+ protected final IStorage getSealed() {
+ return this.sealed;
+ }
+
+ /**
+ * Gets the constructed.
+ *
+ * @return the constructed
+ */
+ protected final IStorage getConstructed() {
+ return this.constructed;
+ }
+
+ /**
+ * Gets the draft.
+ *
+ * @return the draft
+ */
+ protected final IStorage getDraft() {
+ return this.draft;
+ }
+
+ /**
+ * Gets the cube.
+ *
+ * @return the cube
+ */
+ protected final IStorage getCube() {
+ return this.cube;
+ }
+
+ /**
+ * Gets the deck dir.
+ *
+ * @return the deck dir
+ */
+ protected final File getDeckDir() {
+ return this.deckDir;
+ }
+
+ private final IStorage sealed;
+ private final IStorage constructed;
+ private final IStorage draft;
+ private final IStorage cube;
+ private final File deckDir;
+
+ /**
+ * TODO: Write javadoc for this method.
+ */
+ public void tryParse() {
+ this.convertConstructedAndSealed();
+ this.convertDrafts();
+ }
+
+ private void convertDrafts() {
+ for (final File f : this.deckDir.listFiles(OldDeckParser.BDK_FILE_FILTER)) {
+ boolean gotError = false;
+ final Deck human = DeckSerializer.fromFile(new File(f, "0.dck"));
+ final DeckGroup d = new DeckGroup(human.getName());
+ d.setHumanDeck(human);
+
+ for (int i = 1; i < DeckGroupSerializer.MAX_DRAFT_PLAYERS; i++) {
+ final Deck nextAi = DeckSerializer.fromFile(new File(f, i + ".dck"));
+ if (nextAi == null) {
+ gotError = true;
+ break;
+ }
+ d.addAiDeck(nextAi);
+ }
+
+ boolean mayDelete = !gotError;
+ if (!gotError) {
+ this.draft.add(d);
+ } else {
+ final String msg = String.format("Draft '%s' lacked some decks.%n%nShould it be deleted?");
+ mayDelete = FOptionPane.showConfirmDialog(msg, "Draft loading error");
+ }
+
+ if (mayDelete) {
+ for (final File f1 : f.listFiles()) {
+ f1.delete();
+ }
+ f.delete();
+ }
+
+ }
+ }
+
+ private void convertConstructedAndSealed() {
+ boolean allowDeleteUnsupportedConstructed = false;
+ final Map>> sealedDecks = new TreeMap>>(
+ String.CASE_INSENSITIVE_ORDER);
+
+ for (final File f : this.deckDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
+ boolean importedOk = false;
+
+ final List fileLines = FileUtil.readFile(f);
+ final Map> sections = FileSection.parseSections(fileLines);
+ final DeckFileHeader dh = DeckSerializer.readDeckMetadata(sections);
+ String name = dh.getName();
+
+ if (dh.isCustomPool()) {
+ try {
+ this.cube.add(DeckSerializer.fromSections(sections));
+ importedOk = true;
+ }
+ catch (final NoSuchElementException ex) {
+ if (!allowDeleteUnsupportedConstructed) {
+ final String msg = String
+ .format("Can not convert deck '%s' for some unsupported cards it contains. %n%s%n%nMay Forge delete all such decks?",
+ name, ex.getMessage());
+ allowDeleteUnsupportedConstructed = FOptionPane.showConfirmDialog(msg, "Problem converting decks");
+ }
+ }
+ if (importedOk || allowDeleteUnsupportedConstructed) {
+ f.delete();
+ }
+ continue;
+ }
+
+ switch (dh.getDeckType()) {
+ case Constructed:
+ try {
+ this.constructed.add(DeckSerializer.fromSections(sections));
+ importedOk = true;
+ } catch (final NoSuchElementException ex) {
+ if (!allowDeleteUnsupportedConstructed) {
+ final String msg = String
+ .format("Can not convert deck '%s' for some unsupported cards it contains. %n%s%n%nMay Forge delete all such decks?",
+ name, ex.getMessage());
+ allowDeleteUnsupportedConstructed = FOptionPane.showConfirmDialog(msg, "Problem converting decks");
+ }
+ }
+ if (importedOk || allowDeleteUnsupportedConstructed) {
+ f.delete();
+ }
+ break;
+
+ case Limited:
+ name = name.startsWith("AI_") ? name.replace("AI_", "") : name;
+
+ Pair> stored = sealedDecks.get(name);
+ if (null == stored) {
+ stored = ImmutablePair.of(new DeckGroup(name), MutablePair.of((File) null, (File) null));
+ }
+
+ final Deck deck = DeckSerializer.fromSections(sections);
+ if (dh.isIntendedForAi()) {
+ stored.getLeft().addAiDeck(deck);
+ stored.getRight().setRight(f);
+ } else {
+ stored.getLeft().setHumanDeck(deck);
+ stored.getRight().setLeft(f);
+ }
+
+ if ((stored.getLeft().getHumanDeck() != null) && !stored.getLeft().getAiDecks().isEmpty()) {
+ // have both parts of sealed deck, may convert
+ this.sealed.add(stored.getLeft());
+ stored.getRight().getLeft().delete();
+ stored.getRight().getRight().delete();
+
+ // there stay only orphans
+ sealedDecks.remove(name);
+ } else {
+ sealedDecks.put(name, stored);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // advise to kill orphaned decks
+ if (!sealedDecks.isEmpty()) {
+ final StringBuilder sb = new StringBuilder();
+ for (final Pair> s : sealedDecks.values()) {
+ final String missingPart = s.getRight().getLeft() == null ? "human" : "computer";
+ sb.append(String.format("Sealed deck '%s' has no matching '%s' deck.%n", s.getKey().getName(),
+ missingPart));
+ }
+ sb.append(System.getProperty("line.separator"));
+ sb.append("May Forge delete these decks?");
+ if (FOptionPane.showConfirmDialog(sb.toString(), "Some of your sealed decks are orphaned")) {
+ for (final Pair> s : sealedDecks.values()) {
+ if (s.getRight().getLeft() != null) {
+ s.getRight().getLeft().delete();
+ }
+ if (s.getRight().getRight() != null) {
+ s.getRight().getRight().delete();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @return the deckDir
+ */
+
+}
diff --git a/forge-m-base/src/forge/limited/CustomLimited.java b/forge-m-base/src/forge/limited/CustomLimited.java
new file mode 100644
index 00000000000..d9cd2003f55
--- /dev/null
+++ b/forge-m-base/src/forge/limited/CustomLimited.java
@@ -0,0 +1,180 @@
+/*
+ * 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 .
+ */
+package forge.limited;
+
+import forge.card.CardEdition;
+import forge.deck.Deck;
+import forge.deck.DeckBase;
+import forge.item.PaperCard;
+import forge.item.SealedProduct;
+import forge.model.FModel;
+import forge.util.FileSection;
+import forge.util.ItemPool;
+import forge.util.TextUtil;
+import forge.util.storage.IStorage;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * CustomDraft class.
+ *
+ *
+ * @author Forge
+ * @version $Id: CustomLimited.java 24769 2014-02-09 13:56:04Z Hellfish $
+ */
+public class CustomLimited extends DeckBase {
+ private final SealedProduct.Template tpl;
+
+ /**
+ * TODO: Write javadoc for Constructor.
+ *
+ * @param name0 the name0
+ * @param slots
+ */
+ public CustomLimited(final String name0, List> slots) {
+ super(name0);
+ tpl = new SealedProduct.Template(slots);
+ }
+
+ private static final long serialVersionUID = 7435640939026612173L;
+
+ /** The Num packs. */
+ private int numPacks = 3;
+
+ private transient ItemPool cardPool;
+
+ /** The Land set code. */
+ private String landSetCode = CardEdition.Predicates.getRandomSetWithAllBasicLands(FModel.getMagicDb().getEditions()).getCode();
+
+ private boolean singleton;
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.getName();
+ }
+
+ @Override
+ public String getItemType() {
+ return "Limited deck";
+ }
+
+ /**
+ * Parses the.
+ *
+ * @param dfData the df data
+ * @param cubes the cubes
+ * @return the custom limited
+ */
+ public static CustomLimited parse(final List dfData, final IStorage cubes) {
+
+ final FileSection data = FileSection.parse(dfData, ":");
+
+ List> slots = new ArrayList>();
+ String boosterData = data.get("Booster");
+ if(StringUtils.isNotEmpty(boosterData)){
+ final String[] booster = TextUtil.splitWithParenthesis(boosterData, ',');
+ for(String slotDesc : booster) {
+ String[] kv = TextUtil.splitWithParenthesis(slotDesc, ' ', 2);
+ slots.add(ImmutablePair.of(kv[1], Integer.parseInt(kv[0])));
+ }
+ } else
+ slots = SealedProduct.Template.genericBooster.getSlots();
+
+ final CustomLimited cd = new CustomLimited(data.get("Name"), slots);
+ cd.landSetCode = data.get("LandSetCode");
+ cd.numPacks = data.getInt("NumPacks");
+ cd.singleton = data.getBoolean("Singleton");
+ final Deck deckCube = cubes.get(data.get("DeckFile"));
+ cd.cardPool = deckCube == null ? ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getUniqueCards(), PaperCard.class) : deckCube.getMain();
+
+ return cd;
+ }
+
+
+
+ /**
+ * Gets the num packs.
+ *
+ * @return the numPacks
+ */
+ public int getNumPacks() {
+ return this.numPacks;
+ }
+
+ /**
+ * Sets the num packs.
+ *
+ * @param numPacksIn
+ * the numPacks to set
+ */
+ public void setNumPacks(final int numPacksIn) {
+ this.numPacks = numPacksIn;
+ }
+
+ /**
+ * Gets the land set code.
+ *
+ * @return the landSetCode
+ */
+ public String getLandSetCode() {
+ return this.landSetCode;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see forge.item.CardCollectionBase#getCardPool()
+ */
+ public ItemPool getCardPool() {
+ return this.cardPool;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see forge.deck.DeckBase#getInstance(java.lang.String)
+ */
+ @Override
+ protected DeckBase newInstance(final String name0) {
+ return new CustomLimited(name0, tpl.getSlots());
+ }
+
+ /**
+ * TODO: Write javadoc for this method.
+ * @return
+ */
+ public SealedProduct.Template getSealedProductTemplate() {
+ return tpl;
+ }
+
+ public boolean isSingleton() {
+ return singleton;
+ }
+}
diff --git a/forge-m-base/src/forge/model/CardBlock.java b/forge-m-base/src/forge/model/CardBlock.java
new file mode 100644
index 00000000000..7c3951a1517
--- /dev/null
+++ b/forge-m-base/src/forge/model/CardBlock.java
@@ -0,0 +1,302 @@
+/*
+ * 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 .
+ */
+package forge.model;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import forge.card.CardEdition;
+import forge.card.IUnOpenedProduct;
+import forge.card.UnOpenedProduct;
+import forge.item.IPaperCard;
+import forge.item.PaperCard;
+import forge.util.TextUtil;
+import forge.util.storage.StorageReaderFile;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+// import forge.deck.Deck;
+
+/**
+ * This is a CardBlock class.
+ */
+public final class CardBlock implements Comparable {
+ private static final CardEdition[] EMPTY_SET_ARRAY = new CardEdition[] {};
+
+ private final int orderNum;
+ private final String name;
+ private final CardEdition[] sets;
+ private final Map metaSets = new TreeMap();
+ private final CardEdition landSet;
+ private final int cntBoostersDraft;
+ private final int cntBoostersSealed;
+ private Predicate filter = null;
+
+ /**
+ * Instantiates a new card block.
+ *
+ * @param index
+ * the index
+ * @param name
+ * the name
+ * @param sets
+ * the sets
+ * @param metas
+ * the included meta-sets
+ * @param landSet
+ * the land set
+ * @param cntBoostersDraft
+ * the cnt boosters draft
+ * @param cntBoostersSealed
+ * the cnt boosters sealed
+ */
+ public CardBlock(final int index, final String name, final List sets, final List metas,
+ final CardEdition landSet, final int cntBoostersDraft, final int cntBoostersSealed) {
+ this.orderNum = index;
+ this.name = name;
+ this.sets = sets.toArray(CardBlock.EMPTY_SET_ARRAY);
+ for(MetaSet m : metas) {
+ this.metaSets.put(m.getCode(), m);
+ }
+ this.landSet = landSet;
+ this.cntBoostersDraft = cntBoostersDraft;
+ this.cntBoostersSealed = cntBoostersSealed;
+ }
+
+ /**
+ * Gets the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Gets the sets.
+ *
+ * @return the sets
+ */
+ public CardEdition[] getSets() {
+ return this.sets;
+ }
+
+ /**
+ * Gets the land set.
+ *
+ * @return the land set
+ */
+ public CardEdition getLandSet() {
+ return this.landSet;
+ }
+
+ /**
+ * Gets the cnt boosters draft.
+ *
+ * @return the cnt boosters draft
+ */
+ public int getCntBoostersDraft() {
+ return this.cntBoostersDraft;
+ }
+
+ /**
+ * Gets the cnt boosters sealed.
+ *
+ * @return the cnt boosters sealed
+ */
+ public int getCntBoostersSealed() {
+ return this.cntBoostersSealed;
+ }
+
+ /**
+ * Gets the filter.
+ *
+ * @return the filter
+ */
+ public Predicate getFilter() {
+ if (this.filter == null) {
+ this.filter = this.buildFilter();
+ }
+ return this.filter;
+ }
+
+ private Predicate buildFilter() {
+ final List setCodes = new ArrayList();
+ for (final CardEdition set : this.sets) {
+ setCodes.add(set.getCode());
+ }
+ return IPaperCard.Predicates.printedInSets(setCodes, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((this.landSet == null) ? 0 : this.landSet.hashCode());
+ result = (prime * result) + ((this.name == null) ? 0 : this.name.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+
+ final CardBlock other = (CardBlock) obj;
+ if (!this.landSet.equals(other.landSet)) {
+ return false;
+ }
+ if (!this.name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(final CardBlock o) {
+ return this.orderNum - o.orderNum;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if (this.metaSets.isEmpty() && this.sets.length < 1) {
+ return this.name + " (empty)";
+ } else if (this.metaSets.size() + this.getNumberSets() < 2) {
+ return this.name + " (set)";
+ }
+ return this.name + " (block)";
+ }
+
+ public static final Function FN_GET_NAME = new Function() {
+
+ @Override
+ public String apply(CardBlock arg1) {
+ return arg1.getName();
+ }
+ };
+
+ public static class Reader extends StorageReaderFile {
+
+ private final CardEdition.Collection editions;
+ /**
+ * TODO: Write javadoc for Constructor.
+ * @param pathname
+ * @param keySelector0
+ */
+ public Reader(String pathname, CardEdition.Collection editions0) {
+ super(pathname, CardBlock.FN_GET_NAME);
+ editions = editions0;
+ }
+
+ /* (non-Javadoc)
+ * @see forge.util.StorageReaderFile#read(java.lang.String)
+ */
+ @Override
+ protected CardBlock read(String line, int i) {
+ final String[] sParts = TextUtil.splitWithParenthesis(line.trim(), ',', 3);
+ String name = sParts[0];
+
+ String[] numbers = sParts[1].trim().split("/");
+ int draftBoosters = StringUtils.isNumeric(numbers[0]) ? Integer.parseInt(numbers[0]) : 0;
+ int sealedBoosters = StringUtils.isNumeric(numbers[1]) ? Integer.parseInt(numbers[1]) : 0;
+ CardEdition landSet = editions.getEditionByCodeOrThrow(numbers[2]);
+
+ List sets = new ArrayList();
+ List metas = new ArrayList();
+
+ String[] setNames = TextUtil.splitWithParenthesis(sParts[2], ' ' );
+ for(final String set : setNames ) {
+ if(set.startsWith("Meta-")) {
+ String metaSpec = set.substring(5);
+ boolean noDraft = metaSpec.startsWith("NoDraft-");
+ if( noDraft ) metaSpec = metaSpec.substring(8);
+ metas.add(new MetaSet(metaSpec, noDraft));
+ } else {
+ sets.add(editions.getEditionByCodeOrThrow(set));
+ }
+ }
+
+ return new CardBlock(i+1, name, sets, metas, landSet, draftBoosters, sealedBoosters);
+ }
+
+ }
+
+ /**
+ * The number of normal sets in the block.
+ *
+ * @return int, number of sets.
+ */
+ public int getNumberSets() {
+ if (sets == null || sets.length < 1) {
+ return 0;
+ }
+ else {
+ return sets.length;
+ }
+ }
+
+ public Iterable getMetaSetNames() {
+ return metaSets.keySet();
+ }
+
+ public MetaSet getMetaSet(String key) {
+ return metaSets.get(key);
+ }
+
+ /**
+ * Tries to create a booster for the selected meta-set code.
+ *
+ * @param code
+ * String, the MetaSet code
+ * @return UnOpenedProduct, the created booster.
+ */
+ public IUnOpenedProduct getBooster(final String code) {
+ MetaSet ms = metaSets.get(code);
+ return ms == null ? new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(code)) : ms.getBooster();
+ }
+}
diff --git a/forge-m-base/src/forge/model/CardCollections.java b/forge-m-base/src/forge/model/CardCollections.java
new file mode 100644
index 00000000000..d11d5f5fd08
--- /dev/null
+++ b/forge-m-base/src/forge/model/CardCollections.java
@@ -0,0 +1,129 @@
+/*
+ * 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 .
+ */
+package forge.model;
+
+import forge.deck.Deck;
+import forge.deck.DeckGroup;
+import forge.deck.io.DeckGroupSerializer;
+import forge.deck.io.DeckStorage;
+import forge.deck.io.OldDeckParser;
+import forge.util.storage.IStorage;
+import forge.util.storage.StorageImmediatelySerialized;
+import forge.utils.Constants;
+
+import org.apache.commons.lang3.time.StopWatch;
+
+import java.io.File;
+
+/**
+ * Holds editable maps of decks saved to disk. Adding or removing items to(from)
+ * such map turns into immediate file update
+ */
+public class CardCollections {
+ private final IStorage constructed;
+ private final IStorage draft;
+ private final IStorage sealed;
+ private final IStorage cube;
+ private final IStorage scheme;
+ private final IStorage plane;
+ private final IStorage commander;
+
+ /**
+ * TODO: Write javadoc for Constructor.
+ *
+ * @param file the file
+ */
+ public CardCollections() {
+ StopWatch sw = new StopWatch();
+ sw.start();
+ this.constructed = new StorageImmediatelySerialized("Constructed decks", new DeckStorage(new File(Constants.DECK_CONSTRUCTED_DIR), true), true);
+ this.draft = new StorageImmediatelySerialized("Draft deck sets", new DeckGroupSerializer(new File(Constants.DECK_DRAFT_DIR)));
+ this.sealed = new StorageImmediatelySerialized("Sealed deck sets", new DeckGroupSerializer(new File(Constants.DECK_SEALED_DIR)));
+ this.cube = new StorageImmediatelySerialized("Cubes", new DeckStorage(new File(Constants.DECK_CUBE_DIR)));
+ this.scheme = new StorageImmediatelySerialized("Archenemy decks", new DeckStorage(new File(Constants.DECK_SCHEME_DIR)));
+ this.plane = new StorageImmediatelySerialized("Planechase decks", new DeckStorage(new File(Constants.DECK_PLANE_DIR)));
+ this.commander = new StorageImmediatelySerialized("Commander decks", new DeckStorage(new File(Constants.DECK_COMMANDER_DIR)));
+
+ sw.stop();
+ System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar, %d commander.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size(),commander.size());
+// int sum = constructed.size() + sealed.size() + draft.size() + cube.size() + scheme.size() + plane.size();
+// FSkin.setProgessBarMessage(String.format("Loaded %d decks in %f sec", sum, sw.getTime() / 1000f ));
+ // remove this after most people have been switched to new layout
+ final OldDeckParser oldParser = new OldDeckParser(this.constructed, this.draft, this.sealed, this.cube);
+ oldParser.tryParse();
+ }
+
+ /**
+ * Gets the constructed.
+ *
+ * @return the constructed
+ */
+ public final IStorage getConstructed() {
+ return this.constructed;
+ }
+
+ /**
+ * Gets the draft.
+ *
+ * @return the draft
+ */
+ public final IStorage getDraft() {
+ return this.draft;
+ }
+
+ /**
+ * Gets the cubes.
+ *
+ * @return the cubes
+ */
+ public final IStorage getCubes() {
+ return this.cube;
+ }
+
+ /**
+ * Gets the sealed.
+ *
+ * @return the sealed
+ */
+ public IStorage getSealed() {
+ return this.sealed;
+ }
+
+ /**
+ * TODO: Write javadoc for this method.
+ * @return
+ */
+ public IStorage getScheme() {
+ return this.scheme;
+ }
+
+ /**
+ * @return the plane
+ */
+ public IStorage getPlane() {
+ return plane;
+ }
+
+ /**
+ * @return the plane
+ */
+ public IStorage getCommander() {
+ return commander;
+ }
+
+}
diff --git a/forge-m-base/src/forge/model/FModel.java b/forge-m-base/src/forge/model/FModel.java
index bf1f0269d00..077784fe8b8 100644
--- a/forge-m-base/src/forge/model/FModel.java
+++ b/forge-m-base/src/forge/model/FModel.java
@@ -25,6 +25,8 @@ import forge.game.GameFormat;
import forge.game.card.CardUtil;
import forge.toolbox.FProgressBar;
import forge.util.FileUtil;
+import forge.util.storage.IStorage;
+import forge.util.storage.StorageBase;
import forge.utils.Constants;
import forge.utils.ForgePreferences;
@@ -53,15 +55,15 @@ public class FModel {
private static ForgePreferences preferences;
// Someone should take care of 2 gauntlets here
- /*private static GauntletData gauntletData;
- private static GauntletMini gauntlet;
+ //private static GauntletData gauntletData;
+ //private static GauntletMini gauntlet;
- private static QuestController quest;
+ //private static QuestController quest;
private static CardCollections decks;
private static IStorage blocks;
private static IStorage fantasyBlocks;
- private static IStorage worlds;*/
+ //private static IStorage worlds;
private static GameFormat.Collection formats;
public static void initialize(final FProgressBar progressBar) {
@@ -138,18 +140,18 @@ public class FModel {
}
formats = new GameFormat.Collection(new GameFormat.Reader(new File(Constants.BLOCK_DATA_DIR + "formats.txt")));
- /*blocks = new StorageBase("Block definitions", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions()));
- questPreferences = new QuestPreferences();
- gauntletData = new GauntletData();
+ blocks = new StorageBase("Block definitions", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions()));
+ //questPreferences = new QuestPreferences();
+ //gauntletData = new GauntletData();
fantasyBlocks = new StorageBase("Custom blocks", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions()));
- worlds = new StorageBase("Quest worlds", new QuestWorld.Reader(Constants.QUEST_WORLD_DIR + "worlds.txt"));*/
+ //worlds = new StorageBase("Quest worlds", new QuestWorld.Reader(Constants.QUEST_WORLD_DIR + "worlds.txt"));
loadDynamicGamedata();
progressBar.setDescription("Loading decks");
- /*decks = new CardCollections();
- quest = new QuestController();*/
+ decks = new CardCollections();
+ //quest = new QuestController();
//preload AI profiles
AiProfileUtil.loadAllProfiles(Constants.AI_PROFILE_DIR);
@@ -272,23 +274,23 @@ public class FModel {
return preferences;
}
- /*public static IStorage getBlocks() {
+ public static IStorage getBlocks() {
return blocks;
}
- public static QuestPreferences getQuestPreferences() {
+ /*public static QuestPreferences getQuestPreferences() {
return questPreferences;
}
public static GauntletData getGauntletData() {
return gauntletData;
- }
+ }*/
public static CardCollections getDecks() {
return decks;
}
- public static IStorage getWorlds() {
+ /*public static IStorage getWorlds() {
return worlds;
}*/
@@ -296,6 +298,10 @@ public class FModel {
return formats;
}
+ public static IStorage getFantasyBlocks() {
+ return fantasyBlocks;
+ }
+
/**
* Finalizer, generally should be avoided, but here closes the log file
* stream and resets the system output streams.
@@ -308,13 +314,9 @@ public class FModel {
/*public static void setGauntletData(GauntletData data0) {
gauntletData = data0;
- }
+ }*/
- public static IStorage getFantasyBlocks() {
- return fantasyBlocks;
- }
-
- public static GauntletMini getGauntletMini() {
+ /*public static GauntletMini getGauntletMini() {
if (gauntlet == null) {
gauntlet = new GauntletMini();
}
diff --git a/forge-m-base/src/forge/model/MetaSet.java b/forge-m-base/src/forge/model/MetaSet.java
new file mode 100644
index 00000000000..b1a12514214
--- /dev/null
+++ b/forge-m-base/src/forge/model/MetaSet.java
@@ -0,0 +1,214 @@
+/*
+ * 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 .
+ */
+
+package forge.model;
+
+import com.google.common.base.Predicate;
+import forge.card.IUnOpenedProduct;
+import forge.card.UnOpenedProduct;
+import forge.item.IPaperCard;
+import forge.item.PaperCard;
+import forge.item.SealedProduct;
+import forge.limited.CustomLimited;
+import forge.util.FileUtil;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * The class MetaSet. This class is used to define 'special'
+ * sets within a (fantasy) block, like custom sets (cubes),
+ * combination sets (sub-blocks), and full cardpool.
+ *
+ * NOTE: The format of a MetaSet definition is as follows
+ * (in a blocks definition file):
+ *
+ * "metaX A/B/C"
+ *
+ * where "X" is an integer from 0...8 (just like for sets)
+ *
+ * "A" is either "cube", "meta", "full", "choose1", "random1",
+ * "set", "pack", or "combo".
+ *
+ * "full" uses all available cards for this booster, just like
+ * the full cardpool option. The values of "B" and "C" are not
+ * relevant ("B" is not used at all, and the displayed name is
+ * always "*FULL").
+ *
+ * "meta" uses a cardpool that is combined from several
+ * editions. The parameter "B" is a list of 3-letter
+ * edition codes, separated with commas, e.g.
+ * "2ED,ARN,ATQ". The parameter "C" is the name
+ * that is displayed for this meta-booster in the
+ * set selection menu.
+ *
+ * "cube" uses a previously defined custom sealed deck
+ * deck cube as the cardpool for this booster. The cube
+ * definition file must be in res/sealed/ and have a
+ * ".sealed" extension. The related .dck file must be
+ * in the res/decks/cube directory.
+ * "B" is the name of the cube definition file without
+ * the ".sealed" extension, e.g. "juzamjedi".
+ * "C" is the name that is displayed for this meta-booster
+ * in the set selection menu.
+ *
+ * The new types added after beta 1.2.14:
+ *
+ * "choose1": define several metasets in a semicolon-separated (;)
+ * list in value B, the player will choose one of them.
+ *
+ * "random1": like choose1, except that the player will get a
+ * random pack.
+ *
+ * "combo": define several metasets in a semicolon-separated (;)
+ * list in value B, the booster will be based on the combined
+ * cardpool of all these. Note that if one of the metasets is
+ * a "full", the rest are irrelevant.
+ *
+ * "booster": generate a single booster based on value B set. (You
+ * should use this only for combo, choose1 and random1 metasets,
+ * otherwise use normal Sets instead of MetaSets in the block
+ * definition!)
+ *
+ * "pack": like set, but attempts to generate a starter pack instead
+ * of a booster. If starter packs are not available for value B set,
+ * a booster is generated instead.
+ *
+ */
+public class MetaSet {
+
+ private enum MetaSetType {
+ Full("F", "All cards"),
+ Cube("C", "Cube"),
+ JoinedSet("J", "Joined set"),
+ Choose("Select", "Choose from list"),
+ Combo("All", "Combined booster"),
+ Random("Any", "Randomly selected"),
+ Booster("B", "Booster"),
+ SpecialBooster("S", "Special Booster"),
+ Pack("T", "Tournament/Starter");
+
+ private final String shortHand;
+ public final String descriptiveName;
+ private MetaSetType(String shortname, String descName) {
+ shortHand = shortname;
+ descriptiveName = descName;
+ }
+
+ public static MetaSetType smartValueOf(String trim) {
+ for(MetaSetType mt : MetaSetType.values()) {
+ if( mt.name().equalsIgnoreCase(trim) || mt.shortHand.equalsIgnoreCase(trim))
+ return mt;
+ }
+ throw new IllegalArgumentException(trim + " not recognized as Meta Set");
+ }
+ }
+
+ private final MetaSetType type;
+ private final String data;
+ private final String code;
+ private final boolean draftable;
+ // private BoosterGenerator boosterGen;
+
+ /**
+ * The constructor. A new MetaSet is currently only instantiated in CardBlock.java
+ * when CardBlock information is read.
+ *
+ * @param creationString
+ * a {@link java.lang.String} object.
+ */
+ public MetaSet(final String creationString, boolean canDraft) {
+ int idxFirstPar = creationString.indexOf('(');
+ int idxLastPar = creationString.lastIndexOf(')');
+
+ draftable = canDraft;
+ type = MetaSetType.smartValueOf(creationString.substring(0, idxFirstPar).trim());
+ data = creationString.substring(idxFirstPar + 1, idxLastPar);
+ String description = creationString.substring(idxLastPar + 1);
+ code = description + "\u00A0(" + type.descriptiveName + ")"; // u00A0 (nbsp) will not be equal to simple space
+ }
+
+ /**
+ * Return the code.
+ *
+ * @return
+ * String, code
+ */
+ public final String getCode() {
+ return code;
+ }
+
+ /**
+ *
+ * Attempt to get a booster.
+ *
+ * @return UnOpenedProduct, the generated booster.
+ */
+ public IUnOpenedProduct getBooster() {
+
+ switch(type) {
+ case Full:
+ return new UnOpenedProduct(SealedProduct.Template.genericBooster);
+
+ case Booster:
+ return new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(data));
+
+ case SpecialBooster:
+ return new UnOpenedProduct(FModel.getMagicDb().getSpecialBoosters().get(data));
+
+ case Pack:
+ return new UnOpenedProduct(FModel.getMagicDb().getTournamentPacks().get(data));
+
+ case JoinedSet:
+ Predicate predicate = IPaperCard.Predicates.printedInSets(data.split(" "));
+ return new UnOpenedProduct(SealedProduct.Template.genericBooster, predicate);
+
+ case Choose: return UnOpenedMeta.choose(data);
+ case Random: return UnOpenedMeta.random(data);
+ case Combo: return UnOpenedMeta.selectAll(data);
+
+ case Cube:
+ final File dFolder = new File("res/sealed/");
+
+ if (!dFolder.exists()) {
+ throw new RuntimeException("GenerateSealed : folder not found -- folder is " + dFolder.getAbsolutePath());
+ }
+
+ if (!dFolder.isDirectory()) {
+ throw new RuntimeException("GenerateSealed : not a folder -- " + dFolder.getAbsolutePath());
+ }
+
+ List dfData = FileUtil.readFile("res/sealed/" + data + ".sealed");
+ final CustomLimited myCube = CustomLimited.parse(dfData, FModel.getDecks().getCubes());
+
+ SealedProduct.Template fnPick = myCube.getSealedProductTemplate();
+ return new UnOpenedProduct(fnPick, myCube.getCardPool());
+
+ default: return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return code;
+ }
+
+ public boolean isDraftable() {
+ return draftable;
+ }
+}
diff --git a/forge-m-base/src/forge/model/UnOpenedMeta.java b/forge-m-base/src/forge/model/UnOpenedMeta.java
new file mode 100644
index 00000000000..030937ad7b7
--- /dev/null
+++ b/forge-m-base/src/forge/model/UnOpenedMeta.java
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+package forge.model;
+
+import forge.card.IUnOpenedProduct;
+import forge.item.PaperCard;
+import forge.toolbox.GuiChoose;
+import forge.util.MyRandom;
+import forge.util.TextUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * This type extends UnOpenedProduct to support booster choice or random boosters
+ * in sealed deck games. See MetaSet.java for further information.
+ */
+
+public class UnOpenedMeta implements IUnOpenedProduct {
+
+ private enum JoinOperation {
+ RandomOne,
+ ChooseOne,
+ SelectAll,
+ }
+
+ private final ArrayList metaSets;
+ private final JoinOperation operation;
+ private final Random generator = MyRandom.getRandom();
+
+ /**
+ * Constructor for UnOpenedMeta.
+ * @param creationString
+ * String, is parsed for MetaSet info.
+ * @param choose
+ * sets the random/choice status.
+ */
+ private UnOpenedMeta(final String creationString, final JoinOperation op) {
+ metaSets = new ArrayList();
+ operation = op;
+
+ for (String m : TextUtil.splitWithParenthesis(creationString, ';')) {
+ metaSets.add(new MetaSet(m, true));
+ }
+ }
+
+ /**
+ * Open the booster pack, return contents.
+ * @return List, list of cards.
+ */
+ @Override
+ public List get() {
+ return this.open(true, false);
+ }
+
+ /**
+ * Like open, can define whether is human or not.
+ * @param isHuman
+ * boolean, is human player?
+ * @param partialities
+ * known partialities for the AI.
+ * @return List, list of cards.
+ */
+ public List open(final boolean isHuman, final boolean allowCancel) {
+ if (metaSets.isEmpty()) {
+ throw new RuntimeException("Empty UnOpenedMetaset, cannot generate booster.");
+ }
+
+ switch (operation) {
+ case ChooseOne:
+ if (isHuman) {
+ final MetaSet ms;
+ if (allowCancel) {
+ ms = GuiChoose.oneOrNone("Choose Booster", metaSets);
+ if (ms == null) {
+ return null;
+ }
+ }
+ else {
+ ms = GuiChoose.one("Choose Booster", metaSets);
+ }
+ return ms.getBooster().get();
+ }
+
+ case RandomOne: // AI should fall though here from the case above
+ int selected = generator.nextInt(metaSets.size());
+ final IUnOpenedProduct newBooster = metaSets.get(selected).getBooster();
+ return newBooster.get();
+
+ case SelectAll:
+ List allCards = new ArrayList();
+ for (MetaSet ms : metaSets) {
+ allCards.addAll(ms.getBooster().get());
+ }
+ return allCards;
+ }
+ throw new IllegalStateException("Got wrong operation type in unopenedMeta - execution should never reach this point");
+ }
+
+ public static UnOpenedMeta choose(String desc) {
+ return new UnOpenedMeta(desc, JoinOperation.ChooseOne);
+ }
+ public static UnOpenedMeta random(String desc) {
+ return new UnOpenedMeta(desc, JoinOperation.RandomOne);
+ }
+ public static UnOpenedMeta selectAll(String desc) {
+ return new UnOpenedMeta(desc, JoinOperation.SelectAll);
+ }
+}
diff --git a/forge-m-base/src/forge/player/LobbyPlayerHuman.java b/forge-m-base/src/forge/player/LobbyPlayerHuman.java
index 93376807b17..96782faaabc 100644
--- a/forge-m-base/src/forge/player/LobbyPlayerHuman.java
+++ b/forge-m-base/src/forge/player/LobbyPlayerHuman.java
@@ -1,10 +1,11 @@
package forge.player;
-import forge.ai.PlayerControllerAi;
import forge.game.Game;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.player.PlayerController;
+import forge.model.FModel;
+import forge.utils.ForgePreferences.FPref;
public class LobbyPlayerHuman extends LobbyPlayer {
public LobbyPlayerHuman(String name) {
@@ -18,17 +19,18 @@ public class LobbyPlayerHuman extends LobbyPlayer {
@Override
public PlayerController createControllerFor(Player human) {
- return new PlayerControllerAi(human.getGame(), human, this); //TODO new PlayerControllerHuman(human.getGame(), human, this);
+ return new PlayerControllerHuman(human.getGame(), human, this);
}
@Override
public Player createIngamePlayer(Game game) { //TODO
Player player = new Player(/*GuiDisplayUtil.personalizeHuman(*/getName()/*)*/, game);
- player.setFirstController(new PlayerControllerAi(game, player, this)/*new PlayerControllerHuman(game, player, this)*/);
+ player.setFirstController(new PlayerControllerHuman(game, player, this));
- /*if (ForgePreferences.DEV_MODE && Singletons.getModel().getPreferences().getPrefBoolean(FPref.DEV_UNLIMITED_LAND)) {
+ if (FModel.getPreferences().getPrefBoolean(FPref.DEV_MODE_ENABLED) &&
+ FModel.getPreferences().getPrefBoolean(FPref.DEV_UNLIMITED_LAND)) {
player.canCheatPlayUnlimitedLands = true;
- }*/
+ }
return player;
}
diff --git a/forge-m-base/src/forge/player/PlayerControllerHuman.java b/forge-m-base/src/forge/player/PlayerControllerHuman.java
index 2b74bc18f59..ec7f969aed2 100644
--- a/forge-m-base/src/forge/player/PlayerControllerHuman.java
+++ b/forge-m-base/src/forge/player/PlayerControllerHuman.java
@@ -1,20 +1,16 @@
package forge.player;
-/*import com.google.common.base.Predicate;
+import com.google.common.base.Predicate;
import com.google.common.collect.Multimap;
import forge.card.ColorSet;
-import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
-import forge.deck.CardPool;
import forge.deck.Deck;
-import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameType;
-import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card;
import forge.game.card.CardShields;
import forge.game.card.CounterType;
@@ -23,7 +19,6 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
-import forge.game.phase.PhaseType;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -35,25 +30,20 @@ import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.trigger.WrappedAbility;
-import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
-import forge.util.Lang;
-import forge.util.TextUtil;
-
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
-import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
-*//**
+/**
* A prototype for player controller class
*
* Handles phase skips for now.
- *//*
+ */
public class PlayerControllerHuman extends PlayerController {
public PlayerControllerHuman(Game game0, Player p, LobbyPlayer lp) {
super(game0, p, lp);
@@ -523,4 +513,4 @@ public class PlayerControllerHuman extends PlayerController {
// TODO Auto-generated method stub
return null;
}
-}*/
+}
diff --git a/forge-m-base/src/forge/toolbox/GuiChoose.java b/forge-m-base/src/forge/toolbox/GuiChoose.java
new file mode 100644
index 00000000000..8b4b73ca4a9
--- /dev/null
+++ b/forge-m-base/src/forge/toolbox/GuiChoose.java
@@ -0,0 +1,346 @@
+package forge.toolbox;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+
+import forge.game.card.Card;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.*;
+
+/**
+ * TODO: Write javadoc for this type.
+ *
+ */
+public class GuiChoose {
+
+ /**
+ * Convenience for getChoices(message, 0, 1, choices).
+ *
+ * @param
+ * is automatically inferred.
+ * @param message
+ * a {@link java.lang.String} object.
+ * @param choices
+ * a T object.
+ * @return null if choices is missing, empty, or if the users' choices are
+ * empty; otherwise, returns the first item in the List returned by
+ * getChoices.
+ * @see #getChoices(String, int, int, Object...)
+ */
+ public static T oneOrNone(final String message, final T[] choices) {
+ if ((choices == null) || (choices.length == 0)) {
+ return null;
+ }
+ final List choice = GuiChoose.getChoices(message, 0, 1, choices);
+ return choice.isEmpty() ? null : choice.get(0);
+ } // getChoiceOptional(String,T...)
+
+ public static T oneOrNone(final String message, final Collection choices) {
+ if ((choices == null) || choices.isEmpty()) {
+ return null;
+ }
+ final List choice = GuiChoose.getChoices(message, 0, 1, choices);
+ return choice.isEmpty() ? null : choice.get(0);
+ } // getChoiceOptional(String,T...)
+
+ // returned Object will never be null
+ /**
+ *
+ * getChoice.
+ *
+ *
+ * @param
+ * a T object.
+ * @param message
+ * a {@link java.lang.String} object.
+ * @param choices
+ * a T object.
+ * @return a T object.
+ */
+ public static T one(final String message, final T[] choices) {
+ final List choice = GuiChoose.getChoices(message, 1, 1, choices);
+ assert choice.size() == 1;
+ return choice.get(0);
+ }
+
+ public static T one(final String message, final Collection choices) {
+ if (choices == null || choices.isEmpty())
+ return null;
+ if( choices.size() == 1)
+ return Iterables.getFirst(choices, null);
+
+ final List choice = GuiChoose.getChoices(message, 1, 1, choices);
+ assert choice.size() == 1;
+ return choice.get(0);
+ }
+
+ public static List noneOrMany(final String message, final Collection choices) {
+ return GuiChoose.getChoices(message, 0, choices.size(), choices, null, null);
+ }
+
+ // Nothing to choose here. Code uses this to just reveal one or more items
+ public static void reveal(final String message, final T item) {
+ List items = new ArrayList();
+ items.add(item);
+ reveal(message, items);
+ }
+ public static void reveal(final String message, final T[] items) {
+ GuiChoose.getChoices(message, -1, -1, items);
+ }
+ public static void reveal(final String message, final Collection items) {
+ GuiChoose.getChoices(message, -1, -1, items);
+ }
+
+ // Get Integer in range
+ public static Integer getInteger(final String message) {
+ return getInteger(message, 0, Integer.MAX_VALUE);
+ }
+ public static Integer getInteger(final String message, int min) {
+ return getInteger(message, min, Integer.MAX_VALUE);
+ }
+ public static Integer getInteger(final String message, int min, int max) {
+ if (max <= min) { return min; } //just return min if max <= min
+
+ //force cutting off after 100 numbers at most
+ if (max == Integer.MAX_VALUE) {
+ return getInteger(message, min, max, min + 99);
+ }
+ int count = max - min + 1;
+ if (count > 100) {
+ return getInteger(message, min, max, min + 99);
+ }
+
+ final Integer[] choices = new Integer[count];
+ for (int i = 0; i < count; i++) {
+ choices[i] = Integer.valueOf(i + min);
+ }
+ return GuiChoose.oneOrNone(message, choices);
+ }
+ public static Integer getInteger(final String message, int min, int max, int cutoff) {
+ if (max <= min || cutoff < min) { return min; } //just return min if max <= min or cutoff < min
+
+ if (cutoff >= max) { //fallback to regular integer prompt if cutoff at or after max
+ return getInteger(message, min, max);
+ }
+
+ List