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
PRECON_PRODUCT("precon product"),
BORDERLESS("borderless"),
ETCHED("etched"),
SHOWCASE("showcase"),
EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"),
@@ -269,6 +270,7 @@ public final class CardEdition implements Comparable<CardEdition> {
private int boosterArts = 1;
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) {
this.cardMap = cardMap;
@@ -415,11 +417,28 @@ public final class CardEdition implements Comparable<CardEdition> {
}
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() {
return boosterTpl != null;
return boosterTemplates.containsKey("Draft");
}
public List<PrintSheet> getPrintSheetsBySection() {
@@ -559,7 +578,21 @@ public final class CardEdition implements Comparable<CardEdition> {
res.boosterArts = section.getInt("BoosterCovers", 1);
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.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() {
// TODO Auto-generated method stub
return new StorageReaderBase<SealedProduct.Template>(null) {
@Override
public Map<String, SealedProduct.Template> readAll() {
Map<String, SealedProduct.Template> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for(CardEdition ce : Collection.this) {
if (ce.hasBoosterTemplate()) {
map.put(ce.getCode(), ce.getBoosterTemplate());
List<String> boosterTypes = Lists.newArrayList(ce.getAvailableBoosterTypes());
for (String type : boosterTypes) {
String setAffix = type.equals("Draft") ? "" : type;
map.put(ce.getCode() + setAffix, ce.getBoosterTemplate(type));
}
}
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>() {
@Override
public BoosterPack apply(final CardEdition arg1) {
Template d = StaticData.instance().getBoosters().get(arg1.getCode());
return new BoosterPack(arg1.getName(), d);
public BoosterPack apply(final CardEdition edition) {
String boosterKind = edition.getRandomBoosterKind();
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 {
@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.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?
int numCards = slot.getRight();
boolean convertCardFoil = slotType.endsWith("+");
if (convertCardFoil) {
slotType = slotType.substring(0, slotType.length() - 1);
}
String[] sType = TextUtil.splitWithParenthesis(slotType, ' ');
String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null;
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
@@ -280,7 +285,19 @@ public class BoosterGenerator {
}
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);
if (foilInThisSlot) {
@@ -508,6 +525,7 @@ public class BoosterGenerator {
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
) { // custom print sheet
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
System.out.println("Attempting to lookup :" + sheetName);
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
setPred = Predicates.alwaysTrue();

View File

@@ -5,7 +5,7 @@ Name=Strixhaven Mystical Archive
Type=Other
ScryfallCode=STA
[cards]
[borderless]
1 M Approach of the Second Sun
2 M Day of Judgment
3 U Defiant Strike
@@ -132,3 +132,132 @@ ScryfallCode=STA
124 R Growth Spiral
125 R Lightning Helix
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
Type=Expansion
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+
ScryfallCode=STX
@@ -407,26 +408,26 @@ ScryfallCode=STX
382 U Decisive Denial
[lesson]
383 Environmental Sciences
384 Expanded Anatomy
385 Introduction to Annihilation
386 Introduction to Prophecy
387 Mascot Exhibition
388 Academic Probation
389 Reduce to Memory
390 Mercurial Transformation
391 Teachings of the Archaics
392 Confront the Past
393 Necrotic Fumes
394 Illuminate History
395 Start from Scratch
396 Basic Conjuration
397 Containment Breach
398 Elemental Summoning
399 Fractal Summoning
400 Inkling Summoning
401 Pest Summoning
402 Spirit Summoning
2 Environmental Sciences
2 Expanded Anatomy
2 Introduction to Annihilation
2 Introduction to Prophecy
1 Mascot Exhibition
1 Academic Probation
2 Reduce to Memory
2 Mercurial Transformation
1 Teachings of the Archaics
1 Confront the Past
2 Necrotic Fumes
1 Illuminate History
2 Start from Scratch
1 Basic Conjuration
2 Containment Breach
2 Elemental Summoning
2 Fractal Summoning
2 Inkling Summoning
2 Pest Summoning
2 Spirit Summoning
[tokens]
bg_1_1_pest_lifegain

View File

@@ -87,7 +87,7 @@ public class BoosterDraft implements IBoosterDraft {
protected boolean generateProduct() {
switch (this.draftFormat) {
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++) {
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])));
}
} else
slots = SealedProduct.Template.genericBooster.getSlots();
slots = SealedProduct.Template.genericDraftBooster.getSlots();
final CustomLimited cd = new CustomLimited(data.get("Name"), slots);
cd.landSetCode = data.get("LandSetCode");

View File

@@ -166,7 +166,7 @@ public class SealedCardPoolGenerator {
switch(poolType) {
case Full:
// Choose number of boosters
if (!chooseNumberOfBoosters(new UnOpenedProduct(SealedProduct.Template.genericBooster))) {
if (!chooseNumberOfBoosters(new UnOpenedProduct(SealedProduct.Template.genericDraftBooster))) {
return;
}
landSetCode = CardEdition.Predicates.getRandomSetWithAllBasicLands(FModel.getMagicDb().getEditions()).getCode();
@@ -211,7 +211,7 @@ public class SealedCardPoolGenerator {
code = pieces[1];
}
// Generate boosters
// Generate draft boosters
for(int i = 0; i < num; i++) {
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 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> filterT2booster = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
private static final Predicate<CardEdition> filterStandard = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
formats.getStandard().editionLegalPredicate);
/** The filter ext but t2. */
private static final Predicate<CardEdition> filterExtButT2 = Predicates.and(
private static final Predicate<CardEdition> filterPioneerNotStandard = Predicates.and(
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. */
private static final Predicate<CardEdition> filterNotExt = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
Predicates.not(filterExt));
private static final Predicate<CardEdition> filterNotModern = Predicates.and(CardEdition.Predicates.CAN_MAKE_BOOSTER,
Predicates.not(filterModern));
/**
* Gets the quest starter deck.
@@ -169,7 +172,19 @@ public final class BoosterUtils {
return generateRandomBoosterPacks(quantity, isLegalInQuestFormat(questController.getFormat()));
} else {
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.Map.Entry;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -589,7 +590,15 @@ public class QuestWinLoseController {
final IUnOpenedProduct product = new UnOpenedProduct(getBoosterTemplate(), cards);
cardsWon = product.get();
} 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();
}

View File

@@ -166,7 +166,7 @@ public class MetaSet {
switch(type) {
case Full:
return new UnOpenedProduct(SealedProduct.Template.genericBooster);
return new UnOpenedProduct(SealedProduct.Template.genericDraftBooster);
case Booster:
return new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(data));
@@ -179,7 +179,7 @@ public class MetaSet {
case JoinedSet:
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 Random: return UnOpenedMeta.random(data);