mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Add ability to draft with less than 8 players
This commit is contained in:
@@ -52,6 +52,14 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public final class CardEdition implements Comparable<CardEdition> {
|
public final class CardEdition implements Comparable<CardEdition> {
|
||||||
|
|
||||||
|
public DraftOptions getDraftOptions() {
|
||||||
|
return draftOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDraftOptions(DraftOptions draftOptions) {
|
||||||
|
this.draftOptions = draftOptions;
|
||||||
|
}
|
||||||
|
|
||||||
// immutable
|
// immutable
|
||||||
public enum Type {
|
public enum Type {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
@@ -275,18 +283,22 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
// Booster/draft info
|
// Booster/draft info
|
||||||
private List<BoosterSlot> boosterSlots = null;
|
private List<BoosterSlot> boosterSlots = null;
|
||||||
private boolean smallSetOverride = false;
|
private boolean smallSetOverride = false;
|
||||||
private boolean foilAlwaysInCommonSlot = false;
|
private String additionalUnlockSet = "";
|
||||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||||
|
|
||||||
|
// Replace all of these things with booster slots
|
||||||
|
private boolean foilAlwaysInCommonSlot = false;
|
||||||
private double foilChanceInBooster = 0;
|
private double foilChanceInBooster = 0;
|
||||||
private double chanceReplaceCommonWith = 0;
|
private double chanceReplaceCommonWith = 0;
|
||||||
private String slotReplaceCommonWith = "Common";
|
private String slotReplaceCommonWith = "Common";
|
||||||
private String additionalSheetForFoils = "";
|
private String additionalSheetForFoils = "";
|
||||||
private String additionalUnlockSet = "";
|
|
||||||
private String boosterMustContain = "";
|
private String boosterMustContain = "";
|
||||||
private String boosterReplaceSlotFromPrintSheet = "";
|
private String boosterReplaceSlotFromPrintSheet = "";
|
||||||
private String sheetReplaceCardFromSheet = "";
|
private String sheetReplaceCardFromSheet = "";
|
||||||
private String sheetReplaceCardFromSheet2 = "";
|
private String sheetReplaceCardFromSheet2 = "";
|
||||||
private String doublePickDuringDraft = "";
|
|
||||||
|
// Draft options
|
||||||
|
private DraftOptions draftOptions = null;
|
||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, EditionEntry> cardMap;
|
private final ListMultimap<String, EditionEntry> cardMap;
|
||||||
@@ -373,7 +385,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
||||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||||
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
|
|
||||||
public String getBoosterMustContain() { return boosterMustContain; }
|
public String getBoosterMustContain() { return boosterMustContain; }
|
||||||
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||||
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
||||||
@@ -808,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.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.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.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||||
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
||||||
@@ -816,6 +826,23 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
||||||
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
57
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package forge.card;
|
||||||
|
|
||||||
|
public class DraftOptions {
|
||||||
|
public enum DoublePick {
|
||||||
|
NEVER,
|
||||||
|
FIRST_PICK, // only first pick each pack
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public int getMaxPodSize() {
|
||||||
|
return maxPodSize;
|
||||||
|
}
|
||||||
|
public int getRecommendedPodSize() {
|
||||||
|
return recommendedPodSize;
|
||||||
|
}
|
||||||
|
public DoublePick getDoublePick() {
|
||||||
|
return doublePick;
|
||||||
|
}
|
||||||
|
public int getMaxMatchPlayers() {
|
||||||
|
return maxMatchPlayers;
|
||||||
|
}
|
||||||
|
public DeckType getDeckType() {
|
||||||
|
return deckType;
|
||||||
|
}
|
||||||
|
public String getFreeCommander() {
|
||||||
|
return freeCommander;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ Date=2025-09-26
|
|||||||
Name=Marvel's Spider-Man
|
Name=Marvel's Spider-Man
|
||||||
Type=Expansion
|
Type=Expansion
|
||||||
ScryfallCode=SPM
|
ScryfallCode=SPM
|
||||||
|
DoublePick=Always
|
||||||
|
RecommendedPodSize=4
|
||||||
|
|
||||||
[cards]
|
[cards]
|
||||||
1 M Anti-Venom, Horrifying Healer @Néstor Ossandón Leal
|
1 M Anti-Venom, Horrifying Healer @Néstor Ossandón Leal
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package forge.gamemodes.limited;
|
|||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
|
import forge.card.DraftOptions;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.deck.DeckBase;
|
import forge.deck.DeckBase;
|
||||||
@@ -54,15 +55,17 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
private int nextId = 0;
|
private int nextId = 0;
|
||||||
private static final int N_PLAYERS = 8;
|
private static final int N_PLAYERS = 8;
|
||||||
public static final String FILE_EXT = ".draft";
|
public static final String FILE_EXT = ".draft";
|
||||||
|
|
||||||
|
int podSize;
|
||||||
private final List<LimitedPlayer> players = new ArrayList<>();
|
private final List<LimitedPlayer> players = new ArrayList<>();
|
||||||
private final LimitedPlayer localPlayer;
|
private final LimitedPlayer localPlayer;
|
||||||
private boolean readyForComputerPick = false;
|
private boolean readyForComputerPick = false;
|
||||||
|
|
||||||
private IDraftLog draftLog = null;
|
private IDraftLog draftLog = null;
|
||||||
|
|
||||||
private String doublePickDuringDraft = ""; // "FirstPick" or "Always"
|
|
||||||
private boolean shouldShowDraftLog = false;
|
private boolean shouldShowDraftLog = false;
|
||||||
|
|
||||||
|
private DraftOptions.DoublePick doublePickDuringDraft;
|
||||||
protected int nextBoosterGroup = 0;
|
protected int nextBoosterGroup = 0;
|
||||||
private int currentBoosterSize = 0;
|
private int currentBoosterSize = 0;
|
||||||
private int currentBoosterPick = 0;
|
private int currentBoosterPick = 0;
|
||||||
@@ -80,6 +83,9 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
if (!draft.generateProduct()) {
|
if (!draft.generateProduct()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// Choose the amount of players
|
||||||
|
|
||||||
|
|
||||||
draft.initializeBoosters();
|
draft.initializeBoosters();
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
@@ -157,11 +163,14 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
CardEdition edition = FModel.getMagicDb().getEditions().get(setCode);
|
CardEdition edition = FModel.getMagicDb().getEditions().get(setCode);
|
||||||
// If this is metaset, edtion will be null
|
// If this is metaset, edtion will be null
|
||||||
if (edition != null) {
|
if (edition != null) {
|
||||||
doublePickDuringDraft = edition.getDoublePickDuringDraft();
|
doublePickDuringDraft = edition.getDraftOptions().getDoublePick();
|
||||||
|
// Let's set the pod size to the recommended one for this edition
|
||||||
|
if (podSize != edition.getDraftOptions().getRecommendedPodSize()) {
|
||||||
|
setPodSize(edition.getDraftOptions().getRecommendedPodSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final IUnOpenedProduct product1 = block.getBooster(setCode);
|
final IUnOpenedProduct product1 = block.getBooster(setCode);
|
||||||
|
|
||||||
for (int i = 0; i < nPacks; i++) {
|
for (int i = 0; i < nPacks; i++) {
|
||||||
this.product.add(product1);
|
this.product.add(product1);
|
||||||
}
|
}
|
||||||
@@ -296,10 +305,11 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
|
|
||||||
protected BoosterDraft(final LimitedPoolType draftType, int numPlayers) {
|
protected BoosterDraft(final LimitedPoolType draftType, int numPlayers) {
|
||||||
this.draftFormat = draftType;
|
this.draftFormat = draftType;
|
||||||
|
this.podSize = numPlayers;
|
||||||
|
|
||||||
localPlayer = new LimitedPlayer(0, this);
|
localPlayer = new LimitedPlayer(0, this);
|
||||||
players.add(localPlayer);
|
players.add(localPlayer);
|
||||||
for (int i = 1; i < numPlayers; i++) {
|
for (int i = 1; i < this.podSize; i++) {
|
||||||
players.add(new LimitedPlayerAI(i, this));
|
players.add(new LimitedPlayerAI(i, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,6 +319,22 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
return new DraftPack(product.get(), nextId++);
|
return new DraftPack(product.get(), nextId++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPodSize(int size) {
|
||||||
|
if (size < 2 || size > N_PLAYERS) {
|
||||||
|
throw new IllegalArgumentException("BoosterDraft : invalid pod size " + size);
|
||||||
|
}
|
||||||
|
this.podSize = size;
|
||||||
|
|
||||||
|
// Resize players list if it was already generated
|
||||||
|
while (this.players.size() < this.podSize) {
|
||||||
|
this.players.add(new LimitedPlayerAI(this.players.size(), this));
|
||||||
|
}
|
||||||
|
while (this.players.size() > this.podSize) {
|
||||||
|
this.players.remove(this.players.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPileDraft() {
|
public boolean isPileDraft() {
|
||||||
return false;
|
return false;
|
||||||
@@ -336,7 +362,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LimitedPlayer getNeighbor(LimitedPlayer player, boolean left) {
|
public LimitedPlayer getNeighbor(LimitedPlayer player, boolean left) {
|
||||||
return players.get((player.order + (left ? 1 : -1) + N_PLAYERS) % N_PLAYERS);
|
return players.get((player.order + (left ? 1 : -1) + this.podSize) % this.podSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupCustomDraft(final CustomLimited draft) {
|
private void setupCustomDraft(final CustomLimited draft) {
|
||||||
@@ -438,7 +464,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
public void initializeBoosters() {
|
public void initializeBoosters() {
|
||||||
|
|
||||||
for (Supplier<List<PaperCard>> boosterRound : this.product) {
|
for (Supplier<List<PaperCard>> boosterRound : this.product) {
|
||||||
for (int i = 0; i < N_PLAYERS; i++) {
|
for (int i = 0; i < this.podSize; i++) {
|
||||||
DraftPack pack = new DraftPack(boosterRound.get(), nextId++);
|
DraftPack pack = new DraftPack(boosterRound.get(), nextId++);
|
||||||
this.players.get(i).receiveUnopenedPack(pack);
|
this.players.get(i).receiveUnopenedPack(pack);
|
||||||
}
|
}
|
||||||
@@ -467,8 +493,8 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deck[] getComputerDecks() {
|
public Deck[] getComputerDecks() {
|
||||||
Deck[] decks = new Deck[7];
|
Deck[] decks = new Deck[this.podSize - 1];
|
||||||
for (int i = 1; i < N_PLAYERS; i++) {
|
for (int i = 1; i < this.podSize; i++) {
|
||||||
decks[i - 1] = ((LimitedPlayerAI) this.players.get(i)).buildDeck(IBoosterDraft.LAND_SET_CODE[0] != null ? IBoosterDraft.LAND_SET_CODE[0].getCode() : null);
|
decks[i - 1] = ((LimitedPlayerAI) this.players.get(i)).buildDeck(IBoosterDraft.LAND_SET_CODE[0] != null ? IBoosterDraft.LAND_SET_CODE[0].getCode() : null);
|
||||||
}
|
}
|
||||||
return decks;
|
return decks;
|
||||||
@@ -476,7 +502,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LimitedPlayer[] getOpposingPlayers() {
|
public LimitedPlayer[] getOpposingPlayers() {
|
||||||
return this.players.subList(1, players.size()).toArray(new LimitedPlayer[7]);
|
return this.players.subList(1, players.size()).toArray(new LimitedPlayer[this.podSize - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -501,9 +527,9 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
public void passPacks() {
|
public void passPacks() {
|
||||||
// Alternate direction of pack passing
|
// Alternate direction of pack passing
|
||||||
int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1;
|
int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1;
|
||||||
if ("FirstPick".equals(this.doublePickDuringDraft) && currentBoosterPick == 1) {
|
if (DraftOptions.DoublePick.FIRST_PICK.equals(this.doublePickDuringDraft) && currentBoosterPick == 0) {
|
||||||
adjust = 0;
|
adjust = 0;
|
||||||
} else if (currentBoosterPick % 2 == 1 && "Always".equals(this.doublePickDuringDraft)) {
|
} else if (currentBoosterPick % 2 == 0 && DraftOptions.DoublePick.ALWAYS.equals(this.doublePickDuringDraft)) {
|
||||||
// This may not work with Conspiracy cards that mess with the draft
|
// This may not work with Conspiracy cards that mess with the draft
|
||||||
// But it probably doesn't matter since Conspiracy doesn't have double pick?
|
// But it probably doesn't matter since Conspiracy doesn't have double pick?
|
||||||
adjust = 0;
|
adjust = 0;
|
||||||
@@ -518,7 +544,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<DraftPack, LimitedPlayer> toPass = new HashMap<>();
|
Map<DraftPack, LimitedPlayer> toPass = new HashMap<>();
|
||||||
for (int i = 0; i < N_PLAYERS; i++) {
|
for (int i = 0; i < this.podSize; i++) {
|
||||||
LimitedPlayer pl = this.players.get(i);
|
LimitedPlayer pl = this.players.get(i);
|
||||||
DraftPack passingPack = pl.passPack();
|
DraftPack passingPack = pl.passPack();
|
||||||
|
|
||||||
@@ -555,7 +581,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (passToPlayer == null) {
|
if (passToPlayer == null) {
|
||||||
passToPlayer = this.players.get((i + adjust + N_PLAYERS) % N_PLAYERS);
|
passToPlayer = this.players.get((i + adjust + this.podSize) % this.podSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!toPass.containsKey(passingPack));
|
assert(!toPass.containsKey(passingPack));
|
||||||
@@ -573,7 +599,7 @@ public class BoosterDraft implements IBoosterDraft {
|
|||||||
|
|
||||||
protected void computerChoose() {
|
protected void computerChoose() {
|
||||||
// Loop through players 1-7 to draft their current pack
|
// Loop through players 1-7 to draft their current pack
|
||||||
for (int i = 1; i < N_PLAYERS; i++) {
|
for (int i = 1; i < this.podSize; i++) {
|
||||||
LimitedPlayer pl = this.players.get(i);
|
LimitedPlayer pl = this.players.get(i);
|
||||||
if (pl.shouldSkipThisPick()) {
|
if (pl.shouldSkipThisPick()) {
|
||||||
pl.debugPrint("Skipped (shouldSkipThisPick)");
|
pl.debugPrint("Skipped (shouldSkipThisPick)");
|
||||||
|
|||||||
Reference in New Issue
Block a user