Merge branch 'CollectorBoosters' into 'master'

Collector boosters

See merge request core-developers/forge!4913
This commit is contained in:
Michael Kamensky
2021-06-23 03:44:28 +00:00
12 changed files with 259 additions and 49 deletions

View File

@@ -125,6 +125,7 @@ public final class CardEdition implements Comparable<CardEdition> {
SPECIAL_SLOT("special slot"), //to help with convoluted boosters SPECIAL_SLOT("special slot"), //to help with convoluted boosters
PRECON_PRODUCT("precon product"), PRECON_PRODUCT("precon product"),
BORDERLESS("borderless"), BORDERLESS("borderless"),
ETCHED("etched"),
SHOWCASE("showcase"), SHOWCASE("showcase"),
EXTENDED_ART("extended art"), EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"), ALTERNATE_ART("alternate art"),
@@ -269,6 +270,7 @@ public final class CardEdition implements Comparable<CardEdition> {
private int boosterArts = 1; private int boosterArts = 1;
private SealedProduct.Template boosterTpl = null; private SealedProduct.Template boosterTpl = null;
private final Map<String, SealedProduct.Template> boosterTemplates = new HashMap<>();
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) { private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
this.cardMap = cardMap; this.cardMap = cardMap;
@@ -415,11 +417,28 @@ public final class CardEdition implements Comparable<CardEdition> {
} }
public SealedProduct.Template getBoosterTemplate() { public SealedProduct.Template getBoosterTemplate() {
return boosterTpl; return getBoosterTemplate("Draft");
}
public SealedProduct.Template getBoosterTemplate(String boosterType) {
return boosterTemplates.get(boosterType);
}
public String getRandomBoosterKind() {
List<String> boosterTypes = Lists.newArrayList(boosterTemplates.keySet());
if (boosterTypes.isEmpty()) {
return null;
}
Collections.shuffle(boosterTypes);
return boosterTypes.get(0);
}
public Set<String> getAvailableBoosterTypes() {
return boosterTemplates.keySet();
} }
public boolean hasBoosterTemplate() { public boolean hasBoosterTemplate() {
return boosterTpl != null; return boosterTemplates.containsKey("Draft");
} }
public List<PrintSheet> getPrintSheetsBySection() { public List<PrintSheet> getPrintSheetsBySection() {
@@ -559,7 +578,21 @@ public final class CardEdition implements Comparable<CardEdition> {
res.boosterArts = section.getInt("BoosterCovers", 1); res.boosterArts = section.getInt("BoosterCovers", 1);
String boosterDesc = section.get("Booster"); String boosterDesc = section.get("Booster");
res.boosterTpl = boosterDesc == null ? null : new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(boosterDesc));
if (section.contains("Booster")) {
// Historical naming convention in Forge for "DraftBooster"
res.boosterTpl = new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(boosterDesc));
res.boosterTemplates.put("Draft", res.boosterTpl);
}
String[] boostertype = { "Draft", "Collector", "Set" };
// Theme boosters aren't here because they are closer to preconstructed decks, and should be treated as such
for (String type : boostertype) {
String name = type + "Booster";
if (section.contains(name)) {
res.boosterTemplates.put(type, new SealedProduct.Template(res.code, SealedProduct.Template.Reader.parseSlots(section.get(name))));
}
}
res.alias = section.get("alias"); res.alias = section.get("alias");
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH)); res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
@@ -706,14 +739,16 @@ public final class CardEdition implements Comparable<CardEdition> {
}; };
public IItemReader<SealedProduct.Template> getBoosterGenerator() { public IItemReader<SealedProduct.Template> getBoosterGenerator() {
// TODO Auto-generated method stub
return new StorageReaderBase<SealedProduct.Template>(null) { return new StorageReaderBase<SealedProduct.Template>(null) {
@Override @Override
public Map<String, SealedProduct.Template> readAll() { public Map<String, SealedProduct.Template> readAll() {
Map<String, SealedProduct.Template> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Map<String, SealedProduct.Template> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for(CardEdition ce : Collection.this) { for(CardEdition ce : Collection.this) {
if (ce.hasBoosterTemplate()) { List<String> boosterTypes = Lists.newArrayList(ce.getAvailableBoosterTypes());
map.put(ce.getCode(), ce.getBoosterTemplate()); for (String type : boosterTypes) {
String setAffix = type.equals("Draft") ? "" : type;
map.put(ce.getCode() + setAffix, ce.getBoosterTemplate(type));
} }
} }
return map; return map;

View File

@@ -35,9 +35,12 @@ public class BoosterPack extends SealedProduct {
public static final Function<CardEdition, BoosterPack> FN_FROM_SET = new Function<CardEdition, BoosterPack>() { public static final Function<CardEdition, BoosterPack> FN_FROM_SET = new Function<CardEdition, BoosterPack>() {
@Override @Override
public BoosterPack apply(final CardEdition arg1) { public BoosterPack apply(final CardEdition edition) {
Template d = StaticData.instance().getBoosters().get(arg1.getCode()); String boosterKind = edition.getRandomBoosterKind();
return new BoosterPack(arg1.getName(), d); Template d = edition.getBoosterTemplate(boosterKind);
StringBuilder sb = new StringBuilder(edition.getName());
sb.append(" ").append(boosterKind);
return new BoosterPack(sb.toString(), d);
} }
}; };

View File

@@ -132,7 +132,7 @@ public abstract class SealedProduct implements InventoryItemFromSet {
public static class Template { public static class Template {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final static Template genericBooster = new Template(null, Lists.newArrayList( public final static Template genericDraftBooster = new Template(null, Lists.newArrayList(
Pair.of(BoosterSlots.COMMON, 10), Pair.of(BoosterSlots.UNCOMMON, 3), Pair.of(BoosterSlots.COMMON, 10), Pair.of(BoosterSlots.UNCOMMON, 3),
Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1) Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1)
)); ));

View File

@@ -236,6 +236,11 @@ public class BoosterGenerator {
String slotType = slot.getLeft(); // add expansion symbol here? String slotType = slot.getLeft(); // add expansion symbol here?
int numCards = slot.getRight(); int numCards = slot.getRight();
boolean convertCardFoil = slotType.endsWith("+");
if (convertCardFoil) {
slotType = slotType.substring(0, slotType.length() - 1);
}
String[] sType = TextUtil.splitWithParenthesis(slotType, ' '); String[] sType = TextUtil.splitWithParenthesis(slotType, ' ');
String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null; String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null;
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
@@ -280,7 +285,19 @@ public class BoosterGenerator {
} }
PrintSheet ps = getPrintSheet(sheetKey); PrintSheet ps = getPrintSheet(sheetKey);
result.addAll(ps.random(numCards, true)); List<PaperCard> paperCards;
// For cards that end in '+', attempt to convert this card to foil.
if (convertCardFoil) {
paperCards = Lists.newArrayList();
for(PaperCard pc : ps.random(numCards, true)) {
paperCards.add(pc.getFoiled());
}
} else {
paperCards = ps.random(numCards, true);
}
result.addAll(paperCards);
sheetsUsed.add(ps); sheetsUsed.add(ps);
if (foilInThisSlot) { if (foilInThisSlot) {
@@ -508,6 +525,7 @@ public class BoosterGenerator {
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10) mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
) { // custom print sheet ) { // custom print sheet
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" "); String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
System.out.println("Attempting to lookup :" + sheetName);
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList(); src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
setPred = Predicates.alwaysTrue(); setPred = Predicates.alwaysTrue();

View File

@@ -5,7 +5,7 @@ Name=Strixhaven Mystical Archive
Type=Other Type=Other
ScryfallCode=STA ScryfallCode=STA
[cards] [borderless]
1 M Approach of the Second Sun 1 M Approach of the Second Sun
2 M Day of Judgment 2 M Day of Judgment
3 U Defiant Strike 3 U Defiant Strike
@@ -132,3 +132,132 @@ ScryfallCode=STA
124 R Growth Spiral 124 R Growth Spiral
125 R Lightning Helix 125 R Lightning Helix
126 R Putrefy 126 R Putrefy
[etched]
1e M Approach of the Second Sun
2e M Day of Judgment
3e U Defiant Strike
4e U Divine Gambit
5e R Ephemerate
6e R Gift of Estates
7e R Gods Willing
8e R Mana Tithe
9e U Revitalize
10e R Swords to Plowshares
11e M Teferi's Protection
12e M Blue Sun's Zenith
13e R Brainstorm
14e R Compulsive Research
15e R Counterspell
16e R Memory Lapse
17e M Mind's Desire
18e U Negate
19e U Opt
20e U Strategic Planning
21e R Tezzeret's Gambit
22e M Time Warp
23e U Whirlwind Denial
24e U Agonizing Remorse
25e M Crux of Fate
26e R Dark Ritual
27e M Demonic Tutor
28e R Doom Blade
29e U Duress
30e U Eliminate
31e R Inquisition of Kozilek
32e R Sign in Blood
33e M Tainted Pact
34e R Tendrils of Agony
35e U Village Rites
36e M Chaos Warp
37e U Claim the Firstborn
38e R Faithless Looting
39e R Grapeshot
40e M Increasing Vengeance
41e U Infuriate
42e C Lightning Bolt
43e M Mizzix's Mastery
44e U Shock
45e R Stone Rain
46e U Thrill of Possibility
47e R Urza's Rage
48e R Abundant Harvest
49e U Adventurous Impulse
50e M Channel
51e U Cultivate
52e R Harmonize
53e R Krosan Grip
54e M Natural Order
55e M Primal Command
56e R Regrowth
57e U Snakeskin Veil
58e R Weather the Storm
59e R Despark
60e R Electrolyze
61e R Growth Spiral
62e R Lightning Helix
63e R Putrefy
64e M Approach of the Second Sun
65e M Day of Judgment
66e U Defiant Strike
67e U Divine Gambit
68e R Ephemerate
69e R Gift of Estates
70e R Gods Willing
71e R Mana Tithe
72e U Revitalize
73e R Swords to Plowshares
74e M Teferi's Protection
75e M Blue Sun's Zenith
76e R Brainstorm
77e R Compulsive Research
78e R Counterspell
79e R Memory Lapse
80e M Mind's Desire
81e U Negate
82e U Opt
83e U Strategic Planning
84e R Tezzeret's Gambit
85e M Time Warp
86e U Whirlwind Denial
87e U Agonizing Remorse
88e M Crux of Fate
89e R Dark Ritual
90e M Demonic Tutor
91e R Doom Blade
92e U Duress
93e U Eliminate
94e R Inquisition of Kozilek
95e R Sign in Blood
96e M Tainted Pact
97e R Tendrils of Agony
98e U Village Rites
99e M Chaos Warp
100e U Claim the Firstborn
101e R Faithless Looting
102e R Grapeshot
103e M Increasing Vengeance
104e U Infuriate
105e C Lightning Bolt
106e M Mizzix's Mastery
107e U Shock
108e R Stone Rain
109e U Thrill of Possibility
110e R Urza's Rage
111e R Abundant Harvest
112e U Adventurous Impulse
113e M Channel
114e U Cultivate
115e R Harmonize
116e R Krosan Grip
117e M Natural Order
118e M Primal Command
119e R Regrowth
120e U Snakeskin Veil
121e R Weather the Storm
122e R Despark
123e R Electrolyze
124e R Growth Spiral
125e R Lightning Helix
126e R Putrefy

View File

@@ -6,7 +6,8 @@ Code2=STX
MciCode=stx MciCode=stx
Type=Expansion Type=Expansion
BoosterCovers=3 BoosterCovers=3
Booster=1 fromSheet("STX lesson"), 9 Common, 3 Uncommon, 1 RareMythic, 1 fromSheet("STA cards") DraftBooster=1 fromSheet("STX lesson"), 9 Common, 3 Uncommon, 1 RareMythic, 1 Any STA
CollectorBooster=1 Uncommon:fromSheet("STA borderless")+, 1 RareMythic:fromSheet("STA borderless")+, 1 Uncommon:fromSheet("STA etched")+, 1 RareMythic:fromSheet("STA etched")+, 1 RareMythic C21+, 1 RareMythic STA, 1 Uncommon STA, 1 fromSheet("STX lesson")+, 1 RareMythic+, 2 Uncommon+, 5 Common+
Prerelease=6 Boosters, 1 RareMythic+ Prerelease=6 Boosters, 1 RareMythic+
ScryfallCode=STX ScryfallCode=STX
@@ -407,26 +408,26 @@ ScryfallCode=STX
382 U Decisive Denial 382 U Decisive Denial
[lesson] [lesson]
383 Environmental Sciences 2 Environmental Sciences
384 Expanded Anatomy 2 Expanded Anatomy
385 Introduction to Annihilation 2 Introduction to Annihilation
386 Introduction to Prophecy 2 Introduction to Prophecy
387 Mascot Exhibition 1 Mascot Exhibition
388 Academic Probation 1 Academic Probation
389 Reduce to Memory 2 Reduce to Memory
390 Mercurial Transformation 2 Mercurial Transformation
391 Teachings of the Archaics 1 Teachings of the Archaics
392 Confront the Past 1 Confront the Past
393 Necrotic Fumes 2 Necrotic Fumes
394 Illuminate History 1 Illuminate History
395 Start from Scratch 2 Start from Scratch
396 Basic Conjuration 1 Basic Conjuration
397 Containment Breach 2 Containment Breach
398 Elemental Summoning 2 Elemental Summoning
399 Fractal Summoning 2 Fractal Summoning
400 Inkling Summoning 2 Inkling Summoning
401 Pest Summoning 2 Pest Summoning
402 Spirit Summoning 2 Spirit Summoning
[tokens] [tokens]
bg_1_1_pest_lifegain bg_1_1_pest_lifegain

View File

@@ -87,7 +87,7 @@ public class BoosterDraft implements IBoosterDraft {
protected boolean generateProduct() { protected boolean generateProduct() {
switch (this.draftFormat) { switch (this.draftFormat) {
case Full: // Draft from all cards in Forge case Full: // Draft from all cards in Forge
final Supplier<List<PaperCard>> s = new UnOpenedProduct(SealedProduct.Template.genericBooster); final Supplier<List<PaperCard>> s = new UnOpenedProduct(SealedProduct.Template.genericDraftBooster);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
this.product.add(s); this.product.add(s);

View File

@@ -107,7 +107,7 @@ public class CustomLimited extends DeckBase {
slots.add(ImmutablePair.of(kv[1], Integer.parseInt(kv[0]))); slots.add(ImmutablePair.of(kv[1], Integer.parseInt(kv[0])));
} }
} else } else
slots = SealedProduct.Template.genericBooster.getSlots(); slots = SealedProduct.Template.genericDraftBooster.getSlots();
final CustomLimited cd = new CustomLimited(data.get("Name"), slots); final CustomLimited cd = new CustomLimited(data.get("Name"), slots);
cd.landSetCode = data.get("LandSetCode"); cd.landSetCode = data.get("LandSetCode");

View File

@@ -166,7 +166,7 @@ public class SealedCardPoolGenerator {
switch(poolType) { switch(poolType) {
case Full: case Full:
// Choose number of boosters // Choose number of boosters
if (!chooseNumberOfBoosters(new UnOpenedProduct(SealedProduct.Template.genericBooster))) { if (!chooseNumberOfBoosters(new UnOpenedProduct(SealedProduct.Template.genericDraftBooster))) {
return; return;
} }
landSetCode = CardEdition.Predicates.getRandomSetWithAllBasicLands(FModel.getMagicDb().getEditions()).getCode(); landSetCode = CardEdition.Predicates.getRandomSetWithAllBasicLands(FModel.getMagicDb().getEditions()).getCode();
@@ -211,7 +211,7 @@ public class SealedCardPoolGenerator {
code = pieces[1]; code = pieces[1];
} }
// Generate boosters // Generate draft boosters
for(int i = 0; i < num; i++) { for(int i = 0; i < num; i++) {
this.product.add(new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(code))); this.product.add(new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(code)));
} }

View File

@@ -69,20 +69,23 @@ public final class BoosterUtils {
}; };
private static final GameFormat.Collection formats = FModel.getFormats(); private static final GameFormat.Collection formats = FModel.getFormats();
private static final Predicate<CardEdition> filterExt = formats.getExtended().editionLegalPredicate; private static final Predicate<CardEdition> filterPioneer = formats.getPioneer().editionLegalPredicate;
private static final Predicate<CardEdition> filterModern= formats.getModern().editionLegalPredicate;
/** The filter t2booster. */ private static final Predicate<CardEdition> filterStandard = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
private static final Predicate<CardEdition> filterT2booster = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
formats.getStandard().editionLegalPredicate); formats.getStandard().editionLegalPredicate);
/** The filter ext but t2. */ private static final Predicate<CardEdition> filterPioneerNotStandard = Predicates.and(
private static final Predicate<CardEdition> filterExtButT2 = Predicates.and(
CardEdition.Predicates.CAN_MAKE_BOOSTER, CardEdition.Predicates.CAN_MAKE_BOOSTER,
Predicates.and(filterExt, formats.getStandard().editionLegalPredicate)); Predicates.and(filterPioneer, Predicates.not(formats.getStandard().editionLegalPredicate)));
private static final Predicate<CardEdition> filterModernNotPioneer = Predicates.and(
CardEdition.Predicates.CAN_MAKE_BOOSTER,
Predicates.and(filterModern, Predicates.not(filterPioneer)));
/** The filter not ext. */ /** The filter not ext. */
private static final Predicate<CardEdition> filterNotExt = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER, private static final Predicate<CardEdition> filterNotModern = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
Predicates.not(filterExt)); Predicates.not(filterModern));
/** /**
* Gets the quest starter deck. * Gets the quest starter deck.
@@ -169,7 +172,19 @@ public final class BoosterUtils {
return generateRandomBoosterPacks(quantity, isLegalInQuestFormat(questController.getFormat())); return generateRandomBoosterPacks(quantity, isLegalInQuestFormat(questController.getFormat()));
} else { } else {
final int rollD100 = MyRandom.getRandom().nextInt(100); final int rollD100 = MyRandom.getRandom().nextInt(100);
return generateRandomBoosterPacks(quantity, rollD100 < 40 ? filterT2booster : (rollD100 < 75 ? filterExtButT2 : filterNotExt)); // 30% Standard, 20% Pioneer, pre-standard -> 20% Modern, pre-pioneer -> 30% Pre-modern
Predicate<CardEdition> rolledFilter;
if (rollD100 < 30) {
rolledFilter = filterStandard;
} else if (rollD100 < 50) {
rolledFilter = filterPioneerNotStandard;
} else if (rollD100 < 70) {
rolledFilter = filterModernNotPioneer;
} else {
rolledFilter = filterNotModern;
}
return generateRandomBoosterPacks(quantity, rolledFilter);
} }
} }

View File

@@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -589,7 +590,15 @@ public class QuestWinLoseController {
final IUnOpenedProduct product = new UnOpenedProduct(getBoosterTemplate(), cards); final IUnOpenedProduct product = new UnOpenedProduct(getBoosterTemplate(), cards);
cardsWon = product.get(); cardsWon = product.get();
} else { } else {
final IUnOpenedProduct product = new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(chooseEd.getCode())); final IUnOpenedProduct product;
List<String> boosterTypes = Lists.newArrayList(chooseEd.getAvailableBoosterTypes());
String setAffix = "";
String type = SGuiChoose.one("Which booster type do you choose?", boosterTypes);
if (!type.equals("Draft")) {
setAffix = type;
}
product = new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(chooseEd.getCode() + setAffix));
cardsWon = product.get(); cardsWon = product.get();
} }

View File

@@ -166,7 +166,7 @@ public class MetaSet {
switch(type) { switch(type) {
case Full: case Full:
return new UnOpenedProduct(SealedProduct.Template.genericBooster); return new UnOpenedProduct(SealedProduct.Template.genericDraftBooster);
case Booster: case Booster:
return new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(data)); return new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(data));
@@ -179,7 +179,7 @@ public class MetaSet {
case JoinedSet: case JoinedSet:
Predicate<PaperCard> predicate = IPaperCard.Predicates.printedInSets(data.split(" ")); Predicate<PaperCard> predicate = IPaperCard.Predicates.printedInSets(data.split(" "));
return new UnOpenedProduct(SealedProduct.Template.genericBooster, predicate); return new UnOpenedProduct(SealedProduct.Template.genericDraftBooster, predicate);
case Choose: return UnOpenedMeta.choose(data); case Choose: return UnOpenedMeta.choose(data);
case Random: return UnOpenedMeta.random(data); case Random: return UnOpenedMeta.random(data);