Commit 6857eadd authored by imakunee's avatar imakunee

-CardPool

 Added getFilteredPool() to easily get a Predicate applied copy of a CardPool.

 -GameRules
 Minor formatting change.

 -worlds.txt
 Added Random Commander to the list.

 -DeckConstructionRules
 New enum for defining the subformat a quest is using.

 -QuestAssets
 getLife() now has a switch for modifying the life for sub-formats.

 -QuestData
 New data save version. Includes a DeckConstructionRules enum.

 -QuestDataIO
 updateSaveFile will update old saves to have a default DeckConstructionRules complying with the new QuestData save version.

 -QuestController
 Updated to include support for DeckConstructionRules and specialized duel managers

 -QuestEvent
 Now have boolean to define if this is a "random" match for the duel list. Currently only QuestEventCommanderDuelManager makes use of this feature for Commander quests.

 -QuestEventCommanderDuel
 New QuestEventDuel used in the QuestEventCommanderDuelManager which contains a DeckProxy for use in generating random commander decks.

 -QuestEventCommanderDuelManager
 New duel manager to generate duels by difficulty for a Commander quest. Currently uses random generation to generate the decks of each opponent.

 -QuestSpellShop
 Sell Extras button now has a switch for taking into account special deck construction rules such as Commander only allowing singletons.

 -QuestUtil
 Starting a game now checks for various sub-format specific changes including a switch case for which variety of registered player to use.

 -QuestUtilCards
 Starting cardpool size is now modified by a switch case for sub-formats such as Commander.

 -QuestWinLoseController
 QuestEvents marked as random matches will now award a "Random Opponent Bonus" equal to the credit base. Currently only QuestEventCommanderDuelManager creates QuestEvents marked as such.

 -QuestWorld
 Added support for the Commander quest format and world.

 -CEditorQuest
 Many changes to add support for Commander in a style that, hopefully, also paths the way for future format support.

 -CSubmenuQuestData
 Support for Commander quests.

 -VSubmenuQuestData
 Support for Commander quests.
parent bf2ccd74
......@@ -17,6 +17,7 @@
*/
package forge.deck;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.card.CardDb;
......@@ -216,4 +217,17 @@ public class CardPool extends ItemPool<PaperCard> {
}
return sb.toString();
}
/**
* Applies a predicate to this CardPool's cards.
* @param predicate the Predicate to apply to this CardPool
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
*/
public CardPool getFilteredPool(Predicate<PaperCard> predicate){
CardPool filteredPool = new CardPool();
for(PaperCard pc : this.items.keySet()){
if(predicate.apply(pc)) filteredPool.add(pc);
}
return filteredPool;
}
}
......@@ -78,7 +78,8 @@ public class GameRules {
}
public boolean hasCommander() {
return appliedVariants.contains(GameType.Commander) || appliedVariants.contains(GameType.TinyLeaders)
return appliedVariants.contains(GameType.Commander)
|| appliedVariants.contains(GameType.TinyLeaders)
|| appliedVariants.contains(GameType.Brawl);
}
......
......@@ -18,11 +18,20 @@
package forge.screens.deckeditor.controllers;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.UiCommand;
import forge.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.deck.generation.DeckGeneratorBase;
import forge.gui.GuiUtils;
import forge.gui.framework.DragCell;
import forge.gui.framework.FScreen;
......@@ -35,6 +44,7 @@ import forge.itemmanager.views.ItemTableColumn;
import forge.model.FModel;
import forge.properties.ForgePreferences.FPref;
import forge.quest.QuestController;
import forge.quest.data.DeckConstructionRules;
import forge.screens.deckeditor.AddBasicLandsDialog;
import forge.screens.deckeditor.SEditorIO;
import forge.screens.deckeditor.views.VAllDecks;
......@@ -48,6 +58,7 @@ import forge.util.ItemPool;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.print.Paper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -103,6 +114,14 @@ public final class CEditorQuest extends CDeckEditor<Deck> {
allSections.add(DeckSection.Main);
allSections.add(DeckSection.Sideboard);
//Add sub-format specific sections
switch(FModel.getQuest().getDeckConstructionRules()){
case Default: break;
case Commander:
allSections.add(DeckSection.Commander);
break;
}
this.questData = questData0;
final CardManager catalogManager = new CardManager(cDetailPicture, false, true);
......@@ -158,6 +177,10 @@ public final class CEditorQuest extends CDeckEditor<Deck> {
@Override
protected CardLimit getCardLimit() {
if (FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) {
//If this is a commander quest, only allow single copies of cards
if(FModel.getQuest().getDeckConstructionRules() == DeckConstructionRules.Commander){
return CardLimit.Singleton;
}
return CardLimit.Default;
}
return CardLimit.None; //if not enforcing deck legality, don't enforce default limit
......@@ -245,16 +268,98 @@ public final class CEditorQuest extends CDeckEditor<Deck> {
public void resetTables() {
this.sectionMode = DeckSection.Main;
final Deck deck = this.controller.getModel();
// show cards, makes this user friendly
this.getCatalogManager().setPool(getRemainingCardPool());
this.getDeckManager().setPool(getDeck().getMain());
}
/***
* Provides the pool of cards the player has available to add to his or her deck. Also manages showing available cards
* to choose from for special deck construction rules, e.g.: Commander.
* @return CardPool of cards available to add to the player's deck.
*/
private CardPool getRemainingCardPool(){
final CardPool cardpool = getInitialCatalog();
// remove bottom cards that are in the deck from the card pool
cardpool.removeAll(deck.getMain());
cardpool.removeAll(getDeck().getMain());
// remove sideboard cards from the catalog
cardpool.removeAll(deck.getOrCreate(DeckSection.Sideboard));
// show cards, makes this user friendly
this.getCatalogManager().setPool(cardpool);
this.getDeckManager().setPool(deck.getMain());
cardpool.removeAll(getDeck().getOrCreate(DeckSection.Sideboard));
switch(FModel.getQuest().getDeckConstructionRules()){
case Default: break;
case Commander:
//remove this deck's currently selected commander(s) from the catalog
cardpool.removeAll(getDeck().getOrCreate(DeckSection.Commander));
//TODO: Only thin if deck conformance is being applied
if(getDeck().getOrCreate(DeckSection.Commander).toFlatList().size() > 0) {
Predicate<PaperCard> identityPredicate = new MatchCommanderColorIdentity(getDeckColorIdentity());
CardPool filteredPool = cardpool.getFilteredPool(identityPredicate);
return filteredPool;
}
break;
}
return cardpool;
}
/**
* Predicate that filters out based on a color identity provided upon instantiation. Used to filter the card
* list when a commander is chosen so the user can more easily see what cards are available for his or her deck
* and avoid making additions that are not legal.
*/
public static class MatchCommanderColorIdentity implements Predicate<PaperCard> {
private final ColorSet allowedColor;
public MatchCommanderColorIdentity(ColorSet color) {
allowedColor = color;
}
@Override
public boolean apply(PaperCard subject) {
CardRules cr = subject.getRules();
ManaCost mc = cr.getManaCost();
return !mc.isPureGeneric() && allowedColor.containsAllColorsFrom(cr.getColorIdentity().getColor());
}
}
/**
* Compiles the color identity of the loaded deck based on the commanders.
* @return A ColorSet containing the color identity of the currently loaded deck.
*/
public ColorSet getDeckColorIdentity(){
List<PaperCard> commanders = getDeck().getOrCreate(DeckSection.Commander).toFlatList();
List<String> colors = new ArrayList<>();
//Return early if there are no current commanders
if(commanders.size() == 0) return ColorSet.fromNames(colors);
//For each commander,add each color of its color identity if not already added
for(PaperCard pc : commanders){
if(!colors.contains("w") && pc.getRules().getColorIdentity().hasWhite()) colors.add("w");
if(!colors.contains("u") && pc.getRules().getColorIdentity().hasBlue()) colors.add("u");
if(!colors.contains("b") && pc.getRules().getColorIdentity().hasBlack()) colors.add("b");
if(!colors.contains("r") && pc.getRules().getColorIdentity().hasRed()) colors.add("r");
if(!colors.contains("g") && pc.getRules().getColorIdentity().hasGreen()) colors.add("g");
}
return ColorSet.fromNames(colors);
}
/*
Used to make the code more readable in game terms.
*/
private Deck getDeck(){
return this.controller.getModel();
}
private ItemPool<PaperCard> getCommanderCardPool(){
Predicate<PaperCard> commanderPredicate = Predicates.compose(CardRulesPredicates.Presets.CAN_BE_COMMANDER, PaperCard.FN_GET_RULES);
return getRemainingCardPool().getFilteredPool(commanderPredicate);
}
@Override
......@@ -280,14 +385,30 @@ public final class CEditorQuest extends CDeckEditor<Deck> {
}
/**
* Switch between the main deck and the sideboard editor.
* Switch between the main deck and the sideboard/Command Zone editor.
*/
public void setEditorMode(DeckSection sectionMode) {
if (sectionMode == DeckSection.Sideboard) {
this.getDeckManager().setPool(this.controller.getModel().getOrCreate(DeckSection.Sideboard));
}
else {
this.getDeckManager().setPool(this.controller.getModel().getMain());
//Fixes null pointer error on switching tabs while quest deck editor is open. TODO: Find source of bug possibly?
if(sectionMode == null) sectionMode = DeckSection.Main;
//Based on which section the editor is in, display the remaining card pool (or applicable card pool if in
//Commander) and the current section's cards
switch(sectionMode){
case Main :
this.getCatalogManager().setup(ItemManagerConfig.CARD_CATALOG);
this.getCatalogManager().setPool(getRemainingCardPool());
this.getDeckManager().setPool(this.controller.getModel().getMain());
break;
case Sideboard :
this.getCatalogManager().setup(ItemManagerConfig.CARD_CATALOG);
this.getCatalogManager().setPool(getRemainingCardPool());
this.getDeckManager().setPool(getDeck().getOrCreate(DeckSection.Sideboard));
break;
case Commander :
this.getCatalogManager().setup(ItemManagerConfig.COMMANDER_POOL);
this.getCatalogManager().setPool(getCommanderCardPool());
this.getDeckManager().setPool(getDeck().getOrCreate(DeckSection.Commander));
break;
}
this.sectionMode = sectionMode;
......
......@@ -10,6 +10,7 @@ import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.quest.*;
import forge.quest.StartingPoolPreferences.PoolType;
import forge.quest.data.DeckConstructionRules;
import forge.quest.data.GameFormatQuest;
import forge.quest.data.QuestData;
import forge.quest.data.QuestPreferences.QPref;
......@@ -340,9 +341,16 @@ public enum CSubmenuQuestData implements ICDoc {
break;
}
//Apply the appropriate deck construction rules for this quest
DeckConstructionRules dcr = DeckConstructionRules.Default;
if(VSubmenuQuestData.SINGLETON_INSTANCE.isCommander()){
dcr = DeckConstructionRules.Commander;
}
final QuestController qc = FModel.getQuest();
qc.newGame(questName, difficulty, mode, fmtPrizes, view.isUnlockSetsAllowed(), dckStartPool, fmtStartPool, view.getStartingWorldName(), userPrefs);
qc.newGame(questName, difficulty, mode, fmtPrizes, view.isUnlockSetsAllowed(), dckStartPool, fmtStartPool, view.getStartingWorldName(), userPrefs, dcr);
FModel.getQuest().save();
// Save in preferences.
......
......@@ -62,6 +62,7 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
private final FRadioButton radHard = new FRadioButton("Hard");
private final FRadioButton radExpert = new FRadioButton("Expert");
private final FCheckBox boxFantasy = new FCheckBox("Fantasy Mode");
private final FCheckBox boxCommander = new FCheckBox("Commander Subformat");
private final FLabel lblStartingWorld = new FLabel.Builder().text("Starting world:").build();
private final FComboBoxWrapper<QuestWorld> cbxStartingWorld = new FComboBoxWrapper<>();
......@@ -274,9 +275,25 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
}
});
// Fantasy box enabled by Default
// Fantasy box selected by Default
boxFantasy.setSelected(true);
boxFantasy.setEnabled(true);
// Commander box unselected by Default
boxCommander.setSelected(false);
boxCommander.setEnabled(true);
boxCommander.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
if(!isCommander()) return; //do nothing if unselecting Commander Subformat
//Otherwise, set the starting world to Random Commander
cbxStartingWorld.setSelectedItem(FModel.getWorlds().get("Random Commander"));
}
}
);
boxCompleteSet.setEnabled(true);
boxAllowDuplicates.setEnabled(true);
......@@ -286,6 +303,7 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
final JPanel pnlDifficultyMode = new JPanel(new MigLayout("insets 0, gap 1%, flowy"));
pnlDifficultyMode.add(difficultyPanel, "gapright 4%");
pnlDifficultyMode.add(boxFantasy, "h 25px!, gapbottom 15, gapright 4%");
pnlDifficultyMode.add(boxCommander, "h 25px!, gapbottom 15, gapright 4%");
pnlDifficultyMode.add(lblStartingWorld, "h 25px!, hidemode 3");
cbxStartingWorld.addTo(pnlDifficultyMode, "h 27px!, w 40%, pushx, gapbottom 7");
pnlDifficultyMode.setOpaque(false);
......@@ -487,6 +505,14 @@ public enum VSubmenuQuestData implements IVSubmenu<CSubmenuQuestData> {
return boxFantasy.isSelected();
}
/**
* Auth. Imakuni
* @return True if the "Commander Subformat" check box is selected.
*/
public boolean isCommander() {
return boxCommander.isSelected();
}
public boolean startWithCompleteSet() {
return boxCompleteSet.isSelected();
}
......
Name:Main world
Name:Random Standard
Name:Random Commander
Name:Amonkhet|Dir:Amonkhet|Sets:AKH, HOU
Name:Jamuraa|Dir:jamuraa|Sets:5ED, ARN, MIR, VIS, WTH|Banned:Chaos Orb; Falling Star
Name:Kamigawa|Dir:2004 Kamigawa|Sets:CHK, BOK, SOK
......
......@@ -276,9 +276,10 @@ public class QuestController {
public void newGame(final String name, final int difficulty, final QuestMode mode,
final GameFormat formatPrizes, final boolean allowSetUnlocks,
final Deck startingCards, final GameFormat formatStartingPool,
final String startingWorld, final StartingPoolPreferences userPrefs) {
final String startingWorld, final StartingPoolPreferences userPrefs,
DeckConstructionRules dcr) {
this.load(new QuestData(name, difficulty, mode, formatPrizes, allowSetUnlocks, startingWorld)); // pass awards and unlocks here
this.load(new QuestData(name, difficulty, mode, formatPrizes, allowSetUnlocks, startingWorld, dcr)); // pass awards and unlocks here
if (startingCards != null) {
this.myCards.addDeck(startingCards);
......@@ -435,6 +436,12 @@ public class QuestController {
QuestWorld world = getWorld();
String path = ForgeConstants.DEFAULT_CHALLENGES_DIR;
//Use a variant specialized duel manager if this is a variant quest
switch(FModel.getQuest().getDeckConstructionRules()){
case Default: break;
case Commander: this.duelManager = new QuestEventCommanderDuelManager(); return;
}
if (world != null) {
if (world.getName().equals(QuestWorld.STANDARDWORLDNAME)) {
......@@ -449,7 +456,6 @@ public class QuestController {
}
this.duelManager = new QuestEventDuelManager(new File(path));
}
public HashSet<StarRating> GetRating() {
......@@ -607,4 +613,6 @@ public class QuestController {
public void setCurrentDeck(String s) {
model.currentDeck = s;
}
public DeckConstructionRules getDeckConstructionRules(){return model.deckConstructionRules;}
}
......@@ -48,6 +48,7 @@ public abstract class QuestEvent implements IQuestEvent {
private String profile = "Default";
// Opponent name if different from the challenge name
private String opponentName = null;
private boolean isRandomMatch = false;
public static final Function<QuestEvent, String> FN_GET_NAME = new Function<QuestEvent, String>() {
......@@ -174,4 +175,7 @@ public abstract class QuestEvent implements IQuestEvent {
this.showDifficulty = showDifficulty;
}
public boolean getIsRandomMatch(){return isRandomMatch;}
public void setIsRandomMatch(boolean b){isRandomMatch = b;}
}
package forge.quest;
import forge.deck.DeckProxy;
/**
* A QuestEventDuel with a CommanderDeckGenerator used exclusively within QuestEventCommanderDuelManager for the
* creation of randomly generated Commander decks in a Commander variant quest.
* Auth. Imakuni & Forge
*/
public class QuestEventCommanderDuel extends QuestEventDuel{
/**
* The CommanderDeckGenerator for this duel.
*/
private DeckProxy deckProxy;
public DeckProxy getDeckProxy() {return deckProxy;}
public void setDeckProxy(DeckProxy dp) {deckProxy = dp;}
}
package forge.quest;
import forge.deck.*;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.quest.data.QuestPreferences;
import forge.util.CollectionSuppliers;
import forge.util.MyRandom;
import forge.util.maps.EnumMapOfLists;
import forge.util.maps.MapOfLists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Manages the creation of random Commander duels for a Commander variant quest. Random generation is handled via
* the CommanderDeckGenerator class.
* Auth. Forge & Imakuni#8015
*/
public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInterface {
/**
* The list of all possible Commander variant duels.
*/
private ArrayList<QuestEventDuel> commanderDuels = new ArrayList<>();
/**
* Contains the expert deck lists for the commanders.
*/
private List<DeckProxy> expertCommanderDecks;
/**
* Immediately calls assembleDuels() to setup the commanderDuels variable.
*/
public QuestEventCommanderDuelManager(){
assembleDuels();
}
/**
* Assembles the list of all possible Commander duels via CommanderDeckGenerator. Should be done within constructor.
*/
private void assembleDuels(){
//isCardGen = true seemed to make slightly more difficult decks based purely on experience with a very small sample size.
//Gotta work on this more, its making pretty average decks after further testing.
expertCommanderDecks = CommanderDeckGenerator.getCommanderDecks(DeckFormat.Commander, true, true);
List<DeckProxy> generatedDuels = CommanderDeckGenerator.getCommanderDecks(DeckFormat.Commander, true, false);
for(DeckProxy dp : generatedDuels){
QuestEventCommanderDuel duel = new QuestEventCommanderDuel();
duel.setDescription("Randomly generated " + dp.getName() + " commander deck.");
duel.setName(dp.getName());
duel.setTitle(dp.getName());
duel.setOpponentName(dp.getName());
duel.setDifficulty(QuestEventDifficulty.EASY);
duel.setDeckProxy(dp);
//Setting a blank deck avoids a null pointer exception. The deck is generated in generateDuels() to avoid long load times.
duel.setEventDeck(new Deck());
commanderDuels.add(duel);
}
}
/**
* Retrieve list of all possible Commander duels.
* @return ArrayList containing all possible Commander duels.
*/
public Iterable<QuestEventDuel> getAllDuels() {
return commanderDuels;
}
/**
* Retrieve list of all possible Commander duels.
* @param difficulty Currently unused
* @return ArrayList containing all possible Commander duels.
*/
public Iterable<QuestEventDuel> getDuels(QuestEventDifficulty difficulty){
return commanderDuels;
}
/**
* Composes an ArrayList containing 4 QuestEventDuels composed with Commander variant decks. One duel will have its
* title replaced as Random.
* @return ArrayList of QuestEventDuels containing 4 duels.
*/
public List<QuestEventDuel> generateDuels(){
final List<QuestEventDuel> duelOpponents = new ArrayList<>();
//While there are less than 4 duels chosen
while(duelOpponents.size() < 4){
//Get a random duel from the possible duels list
QuestEventCommanderDuel duel = (QuestEventCommanderDuel)commanderDuels.get(((int) (commanderDuels.size() * MyRandom.getRandom().nextDouble())));
//If the chosen duels list already contains this duel, get a different duel to prevent duplicate duels
if(duelOpponents.contains(duel)) continue;
//Add the randomly chosen duel to the duel list
duelOpponents.add(duel);
//Here the actual deck for this commander is generated by calling .getDeck() on the saved DeckProxy
duel.setEventDeck(duel.getDeckProxy().getDeck());
//Modify deck for difficulty
modifyDuelForDifficulty(duel);
}
//Modify the stats of the final duel to hide the opponent, creating a "random" duel.
//We make a copy of the final duel and overwrite it in the duelOpponents to avoid changing the variables in
//the original duel, which gets reused.
QuestEventCommanderDuel duel = (QuestEventCommanderDuel)duelOpponents.get(duelOpponents.size() - 1);
QuestEventCommanderDuel randomDuel = new QuestEventCommanderDuel();
randomDuel.setName(duel.getName());
randomDuel.setOpponentName(duel.getName());
randomDuel.setDeckProxy(duel.getDeckProxy());
randomDuel.setTitle("Random Opponent");
randomDuel.setShowDifficulty(false);
randomDuel.setDescription("Fight a random generated commander opponent.");
randomDuel.setIsRandomMatch(true);
randomDuel.setEventDeck(duel.getEventDeck());
//Replace the final duel with this newly modified "random" duel
duelOpponents.set(duelOpponents.size()-1, randomDuel);
return duelOpponents;
}
/**
* Retrieves the expert level deck generation of a deck with the same commander as the provided DeckProxy.
* @param dp The easy generation commander deck
* @return The same commander's expert generation DeckProxy
*/
private Deck getExpertGenDeck(DeckProxy dp){
for(QuestEventDuel qed : commanderDuels){
QuestEventCommanderDuel cmdQED = (QuestEventCommanderDuel)qed;
if(cmdQED.getDeckProxy().getName().equals(dp.getName())){
return cmdQED.getDeckProxy().getDeck();
}
}
return null;
}
/**
* Modifies a given duel by replacing a percentage of the deck with random cards from the more difficult generated version
* of the same commander's deck. Medium replaces 30%, Hard replaces 60%, Expert replaces 100%.
* @param duel The QuestEventCommanderDuel to modify
*/
private void modifyDuelForDifficulty(QuestEventCommanderDuel duel){
final QuestPreferences questPreferences = FModel.getQuestPreferences();
final int index = FModel.getQuest().getAchievements().getDifficulty();
final int numberOfWins = FModel.getQuest().getAchievements().getWin();
Deck expertDeck = getExpertGenDeck(duel.getDeckProxy());
int difficultyReplacementPercent = 0;
//Note: The code is ordered to make the least number of comparisons I could think of at the time for speed reasons.
//In reality, it shouldn't really make much difference, but why not?
if (numberOfWins >= questPreferences.getPrefInt(QuestPreferences.DifficultyPrefs.WINS_EXPERTAI, index)) {
//At expert, the deck is replaced with the entire expert deck, and we can return immediately
duel.setEventDeck(expertDeck);
duel.setDifficulty(QuestEventDifficulty.EXPERT);
return;
}
if (numberOfWins >= questPreferences.getPrefInt(QuestPreferences.DifficultyPrefs.WINS_MEDIUMAI, index)) {
difficultyReplacementPercent += 30;
duel.setDifficulty(QuestEventDifficulty.MEDIUM);
} else return; //return early here since it would be an easy opponent with no changes
if (numberOfWins >= questPreferences.getPrefInt(QuestPreferences.DifficultyPrefs.WINS_HARDAI, index)) {
difficultyReplacementPercent += 30;
duel.setDifficulty(QuestEventDifficulty.HARD);
}
CardPool easyMain = duel.getEventDeck().getMain();
CardPool expertMain = expertDeck.getMain();
List<PaperCard> easyList = easyMain.toFlatList();
List<PaperCard> expertList = expertMain.toFlatList();
//Replace cards in the easy deck with cards from the expert deck up to the difficulty replacement percent
for(int i = 0; i < difficultyReplacementPercent; i++){
if(!easyMain.contains(expertList.get(i))) { //ensure that the card being copied over isn't already in the deck
easyMain.remove(easyList.get(i));
easyMain.add(expertList.get(i));
}