diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 2247134dd55..1c6d6b01218 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -445,7 +445,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { return; } cardInfoPane.setLocation(40, 40); - cardInfoPane.setBackground(new Color(0, 0, 0, 255)); // use non-transparent background to full draw, see bug example in #12261 UI.addComponent(MageComponents.CARD_INFO_PANE, cardInfoPane); MageRoundPane popupContainer = new MageRoundPane(); diff --git a/Mage.Client/src/main/java/mage/client/cards/CardsList.java b/Mage.Client/src/main/java/mage/client/cards/CardsList.java index 0b810c4106f..3f6169d2958 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardsList.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardsList.java @@ -114,7 +114,7 @@ panelCardArea.setOpaque(false); cardArea.setOpaque(false); panelCardArea.getViewport().setOpaque(false); - panelControl.setBackground(new Color(250, 250, 250, 150)); + panelControl.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); panelControl.setOpaque(true); cbSortBy.setModel(new DefaultComboBoxModel<>(SortBy.values())); } diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index 53c3492ce60..38b89230725 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -857,7 +857,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg JPanel toolbar = new JPanel(new BorderLayout()); JPanel toolbarInner = new JPanel(); - toolbar.setBackground(new Color(250, 250, 250, 150)); + toolbar.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); toolbar.setOpaque(true); toolbarInner.setOpaque(false); toolbarInner.add(deckNameAndCountLabel); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 1ef48a9400e..cc2c4cc9657 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -10,6 +10,7 @@ import mage.cards.repository.*; import mage.client.MageFrame; import mage.client.cards.*; import mage.client.constants.Constants.SortBy; +import mage.client.dialog.PreferencesDialog; import mage.client.deckeditor.table.TableModel; import mage.client.dialog.CheckBoxList; import mage.client.util.GUISizeHelper; @@ -97,11 +98,11 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene jTextFieldSearch.addActionListener(searchAction); // make the components more readable - tbColor.setBackground(new Color(250, 250, 250, 150)); + tbColor.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); tbColor.setOpaque(true); // false = transparent - tbTypes.setBackground(new Color(250, 250, 250, 150)); + tbTypes.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); tbTypes.setOpaque(true); // false = transparent - cardSelectorBottomPanel.setBackground(new Color(250, 250, 250, 150)); + cardSelectorBottomPanel.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); cardSelectorBottomPanel.setOpaque(true); // false = transparent } diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java index 8b2693d25ca..c1b25b1e152 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -313,7 +313,7 @@ public class PickChoiceDialog extends MageDialog { // as card name cardInfo.init(item.getHint(), this.bigCard, this.gameId); } else if (item.getHintType() == ChoiceHintType.CARD_DUNGEON) { - // as card name + // as dungeon name CardView cardView = new CardView(new DungeonView(Dungeon.createDungeon(item.getHint(), true))); cardInfo.init(cardView, this.bigCard, this.gameId); } else if (item.getHintType() == ChoiceHintType.GAME_OBJECT) { diff --git a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java index b26c4102542..e52404cb491 100644 --- a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java +++ b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java @@ -13,29 +13,29 @@ import java.awt.*; */ public enum ThemeType { // https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/_nimbusDefaults.html - DEFAULT("Default", - "", - true, - false, - true, - true, - true, - true, - true, - new Color(169, 176, 190), // nimbusBlueGrey - new Color(214, 217, 223), // control - new Color(255, 255, 255), // nimbusLightBackground - new Color(242, 242, 189), // info - new Color(51, 98, 140), // nimbusBase - null, // mageToolbar + DEFAULT("Default", // name + "", // path + true, // hasBackground + false, // hasLoginBackground + true, // hasBattleBackground + true, // hasSkipButtons + true, // hasPhaseIcons + true, // hasWinLossImages + true, // shortcutsVisibleForSkipButtons + new Color(169, 176, 190), // nimbusBlueGrey + new Color(214, 217, 223), // control + new Color(255, 255, 255), // nimbusLightBackground + new Color(242, 242, 189), // info + new Color(51, 98, 140), // nimbusBase + null, // mageToolbar new Color(200, 200, 180, 200), // playerPanel_inactiveBackgroundColor new Color(200, 255, 200, 200), // playerPanel_activeBackgroundColor - new Color(131, 94, 83, 200), // playerPanel_deadBackgroundColor - // card icons - new Color(169, 176, 190), - Color.black, - new Color(51, 98, 140), - Color.black + new Color(131, 94, 83, 200), // playerPanel_deadBackgroundColor + new Color(250, 250, 250, 150), // deckEditorToolbarBackgroundColor + new Color(169, 176, 190), // cardIconsFillColor + Color.black, // cardIconsStrokeColor + new Color(51, 98, 140), // cardIconsTextColor + Color.black // textColor ), GREY("Grey", "grey-theme/", @@ -55,6 +55,7 @@ public enum ThemeType { new Color(172, 172, 172, 200), // playerPanel_inactiveBackgroundColor new Color(180, 234, 180, 200), // playerPanel_activeBackgroundColor new Color(99, 99, 99, 200), // playerPanel_deadBackgroundColor + new Color(250, 250, 250, 150), // deckEditorToolbarBackgroundColor // card icons new Color(158, 158, 158), Color.black, @@ -79,6 +80,7 @@ public enum ThemeType { new Color(243, 233, 164), // playerPanel_inactiveBackgroundColor new Color(204, 236, 201), // playerPanel_activeBackgroundColor new Color(106, 0, 255), // playerPanel_deadBackgroundColor + new Color(250, 250, 250, 150), // deckEditorToolbarBackgroundColor // card icons new Color(246, 136, 158), Color.black, @@ -103,6 +105,7 @@ public enum ThemeType { new Color(219, 193, 172), // playerPanel_inactiveBackgroundColor new Color(204, 236, 201), // playerPanel_activeBackgroundColor new Color(99, 72, 50, 255), // playerPanel_deadBackgroundColor + new Color(250, 250, 250, 150), // deckEditorToolbarBackgroundColor // card icons new Color(219, 193, 172), Color.black, @@ -127,6 +130,7 @@ public enum ThemeType { new Color(172, 195, 219), // playerPanel_inactiveBackgroundColor new Color(204, 236, 201), // playerPanel_activeBackgroundColor new Color(50, 68, 99, 255), // playerPanel_deadBackgroundColor + new Color(250, 250, 250, 150), // deckEditorToolbarBackgroundColor // card icons new Color(172, 197, 219), Color.black, @@ -142,19 +146,20 @@ public enum ThemeType { true, true, true, - new Color(43, 45, 49), // buttons, scrollar background, disabled inputs - new Color(49, 51, 56), // window background - new Color(58, 56, 64), // inputs, table rows - new Color(58, 56, 64), // tooltips - new Color(25, 25, 25), // title bars, scrollbar foreground - new Color(43, 45, 49), // mageToolbar - new Color(43, 45, 49), // playerPanel_inactiveBackgroundColor - new Color(64, 61, 56), // playerPanel_activeBackgroundColor + new Color(43, 45, 49), // buttons, scrollar background, disabled inputs + new Color(49, 51, 56), // window background + new Color(58, 56, 64), // inputs, table rows + new Color(58, 56, 64), // tooltips + new Color(25, 25, 25), // title bars, scrollbar foreground + new Color(43, 45, 49), // mageToolbar + new Color(43, 45, 49), // playerPanel_inactiveBackgroundColor + new Color(64, 61, 56), // playerPanel_activeBackgroundColor new Color(50, 68, 99, 255), // playerPanel_deadBackgroundColor - new Color(172, 197, 219), - Color.BLACK, - new Color(0, 78, 97), - new Color(220, 220, 220) + new Color(50, 50, 50, 250), // deckEditorToolbarBackgroundColor + new Color(172, 197, 219), // cardIconsFillColor + Color.BLACK, // cardIconsStrokeColor + new Color(0, 78, 97), // cardIconsTextColor + new Color(220, 220, 220) // textColor ); private final String name; @@ -175,6 +180,7 @@ public enum ThemeType { private final Color playerPanel_inactiveBackgroundColor; private final Color playerPanel_activeBackgroundColor; private final Color playerPanel_deadBackgroundColor; + private final Color deckEditorToolbarBackgroundColor; // card icons settings (example: flying icon) private final Color cardIconsFillColor; private final Color cardIconsStrokeColor; @@ -201,6 +207,7 @@ public enum ThemeType { Color playerPanel_inactiveBackgroundColor, Color playerPanel_activeBackgroundColor, Color playerPanel_deadBackgroundColor, + Color deckEditorToolbarBackgroundColor, Color cardIconsFillColor, Color cardIconsStrokeColor, Color cardIconsTextColor, @@ -224,6 +231,7 @@ public enum ThemeType { this.playerPanel_activeBackgroundColor = playerPanel_activeBackgroundColor; this.playerPanel_deadBackgroundColor = playerPanel_deadBackgroundColor; this.playerPanel_inactiveBackgroundColor = playerPanel_inactiveBackgroundColor; + this.deckEditorToolbarBackgroundColor = deckEditorToolbarBackgroundColor; this.cardIconsFillColor = cardIconsFillColor; this.cardIconsStrokeColor = cardIconsStrokeColor; this.cardIconsTextColor = cardIconsTextColor; @@ -292,6 +300,10 @@ public enum ThemeType { return playerPanel_deadBackgroundColor; } + public Color getDeckEditorToolbarBackgroundColor() { + return deckEditorToolbarBackgroundColor; + } + private String getImagePath(String imageType, String name) { return "/" + imageType + "/" + path + name; } diff --git a/Mage.Client/src/main/resources/info/energy.png b/Mage.Client/src/main/resources/info/energy.png index 5cdfba5dc49..d0803075022 100644 Binary files a/Mage.Client/src/main/resources/info/energy.png and b/Mage.Client/src/main/resources/info/energy.png differ diff --git a/Mage.Client/src/main/resources/info/experience.png b/Mage.Client/src/main/resources/info/experience.png index 180ef0f47a9..0bac946cd2a 100644 Binary files a/Mage.Client/src/main/resources/info/experience.png and b/Mage.Client/src/main/resources/info/experience.png differ diff --git a/Mage.Client/src/main/resources/info/rad.png b/Mage.Client/src/main/resources/info/rad.png index 363c4412b09..8b8c53d3403 100644 Binary files a/Mage.Client/src/main/resources/info/rad.png and b/Mage.Client/src/main/resources/info/rad.png differ diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 14c39af809e..8402770ab52 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -18,7 +18,7 @@ public class MageVersion implements Serializable, Comparable { public static final int MAGE_VERSION_MAJOR = 1; public static final int MAGE_VERSION_MINOR = 4; public static final int MAGE_VERSION_RELEASE = 54; - public static final String MAGE_VERSION_RELEASE_INFO = "V2"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas + public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas // strict mode // Each update requires a strict version diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java index 63e9bec779a..3724522e671 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java @@ -20,6 +20,7 @@ public class Commander extends AbstractCommander { banned.add("Braids, Cabal Minion"); banned.add("Channel"); banned.add("Coalition Victory"); + banned.add("Dockside Extortionist"); banned.add("Emrakul, the Aeons Torn"); banned.add("Erayo, Soratami Ascendant"); banned.add("Fastbond"); @@ -29,17 +30,20 @@ public class Commander extends AbstractCommander { banned.add("Griselbrand"); banned.add("Hullbreacher"); banned.add("Iona, Shield of Emeria"); + banned.add("Jeweled Lotus"); banned.add("Jihad"); banned.add("Karakas"); banned.add("Leovold, Emissary of Trest"); banned.add("Library of Alexandria"); banned.add("Limited Resources"); banned.add("Lutri, the Spellchaser"); + banned.add("Mana Crypt"); banned.add("Mox Emerald"); banned.add("Mox Jet"); banned.add("Mox Pearl"); banned.add("Mox Ruby"); banned.add("Mox Sapphire"); + banned.add("Nadu, Winged Wisdom"); banned.add("Panoptic Mirror"); banned.add("Paradox Engine"); banned.add("Primeval Titan"); diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 50689e270e6..2a768f4ebd9 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -96,7 +96,7 @@ public class GameController implements GameCallback { this.tableId = tableId; this.choosingPlayerId = choosingPlayerId; this.gameOptions = gameOptions; - this.useResponseIdleTimeout = game.getPlayers().values().stream().anyMatch(Player::isHuman); + this.useResponseIdleTimeout = game.getPlayers().values().stream().filter(Player::isHuman).count() > 1; init(); } diff --git a/Mage.Sets/src/mage/cards/c/ConductiveMachete.java b/Mage.Sets/src/mage/cards/c/ConductiveMachete.java index afc99303f5a..dfd0fd82ed6 100644 --- a/Mage.Sets/src/mage/cards/c/ConductiveMachete.java +++ b/Mage.Sets/src/mage/cards/c/ConductiveMachete.java @@ -29,7 +29,7 @@ public final class ConductiveMachete extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(2, 1))); // Equip {4} - this.addAbility(new EquipAbility(4)); + this.addAbility(new EquipAbility(4, false)); } private ConductiveMachete(final ConductiveMachete card) { diff --git a/Mage.Sets/src/mage/cards/c/CursedWindbreaker.java b/Mage.Sets/src/mage/cards/c/CursedWindbreaker.java index 0adbba0c400..0b54debf9a4 100644 --- a/Mage.Sets/src/mage/cards/c/CursedWindbreaker.java +++ b/Mage.Sets/src/mage/cards/c/CursedWindbreaker.java @@ -33,7 +33,7 @@ public final class CursedWindbreaker extends CardImpl { ))); // Equip {3} - this.addAbility(new EquipAbility(3)); + this.addAbility(new EquipAbility(3, false)); } private CursedWindbreaker(final CursedWindbreaker card) { diff --git a/Mage.Sets/src/mage/cards/d/DissectionTools.java b/Mage.Sets/src/mage/cards/d/DissectionTools.java index df90c072f42..8fa11a69cb2 100644 --- a/Mage.Sets/src/mage/cards/d/DissectionTools.java +++ b/Mage.Sets/src/mage/cards/d/DissectionTools.java @@ -45,7 +45,7 @@ public final class DissectionTools extends CardImpl { // Equip--Sacrifice a creature. this.addAbility(new EquipAbility( - Outcome.BoostCreature, new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_A_CREATURE) + Outcome.BoostCreature, new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_A_CREATURE), false )); } diff --git a/Mage.Sets/src/mage/cards/e/EnduringCourage.java b/Mage.Sets/src/mage/cards/e/EnduringCourage.java index be65c188e04..a447b90c9bd 100644 --- a/Mage.Sets/src/mage/cards/e/EnduringCourage.java +++ b/Mage.Sets/src/mage/cards/e/EnduringCourage.java @@ -10,7 +10,9 @@ import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import java.util.UUID; @@ -29,9 +31,9 @@ public final class EnduringCourage extends CardImpl { this.toughness = new MageInt(3); // Whenever another creature you control enters, it gets +2/+0 and gains haste until end of turn. - Ability ability = new EntersBattlefieldAllTriggeredAbility( + Ability ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new BoostTargetEffect(2, 0).setText("it gets +2/+0"), - StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL + StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL, false, SetTargetPointer.PERMANENT ); ability.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) .setText("and gains haste until end of turn")); diff --git a/Mage.Sets/src/mage/cards/e/ExperimentalPilot.java b/Mage.Sets/src/mage/cards/e/ExperimentalPilot.java index d01efe3f128..9e55c13e9b4 100644 --- a/Mage.Sets/src/mage/cards/e/ExperimentalPilot.java +++ b/Mage.Sets/src/mage/cards/e/ExperimentalPilot.java @@ -52,7 +52,7 @@ public final class ExperimentalPilot extends CardImpl { this.toughness = new MageInt(2); // Ward {2} - this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); // {U}, Discard two cards: Draft a card from Experimental Pilot's spellbook. Ability ability = new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/j/JacesSanctum.java b/Mage.Sets/src/mage/cards/j/JacesSanctum.java index b8848165daf..e08a4647d82 100644 --- a/Mage.Sets/src/mage/cards/j/JacesSanctum.java +++ b/Mage.Sets/src/mage/cards/j/JacesSanctum.java @@ -40,7 +40,7 @@ public final class JacesSanctum extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SpellsCostReductionControllerEffect(filter, 1))); // Whenever you cast an instant or sorcery spell, scry 1. - this.addAbility(new SpellCastControllerTriggeredAbility(new ScryEffect(1), filter2, false)); + this.addAbility(new SpellCastControllerTriggeredAbility(new ScryEffect(1, false), filter2, false)); } private JacesSanctum(final JacesSanctum card) { @@ -51,4 +51,4 @@ public final class JacesSanctum extends CardImpl { public JacesSanctum copy() { return new JacesSanctum(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/k/KillersMask.java b/Mage.Sets/src/mage/cards/k/KillersMask.java index 2c73b58f9cb..d7384a64d8c 100644 --- a/Mage.Sets/src/mage/cards/k/KillersMask.java +++ b/Mage.Sets/src/mage/cards/k/KillersMask.java @@ -33,7 +33,7 @@ public final class KillersMask extends CardImpl { ))); // Equip {2} - this.addAbility(new EquipAbility(2)); + this.addAbility(new EquipAbility(2, false)); } private KillersMask(final KillersMask card) { diff --git a/Mage.Sets/src/mage/cards/l/LegionsInitiative.java b/Mage.Sets/src/mage/cards/l/LegionsInitiative.java index eee0411ff05..f7558cd045c 100644 --- a/Mage.Sets/src/mage/cards/l/LegionsInitiative.java +++ b/Mage.Sets/src/mage/cards/l/LegionsInitiative.java @@ -95,18 +95,19 @@ class LegionsInitiativeExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { return false; } Cards cards = new CardsImpl(); + UUID exileZone = CardUtil.getExileZoneId(game, source); game.getBattlefield().getActivePermanents( StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), source, game ).stream().filter(Objects::nonNull).forEach(cards::add); - return player.moveCardsToExile( + return controller.moveCardsToExile( cards.getCards(game), source, game, true, - CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source) + exileZone, CardUtil.getSourceName(game, source) ); } @@ -120,8 +121,8 @@ class LegionsInitiativeReturnFromExileEffect extends OneShotEffect { LegionsInitiativeReturnFromExileEffect() { super(Outcome.PutCardInPlay); - staticText = "return those cards to the battlefield under their owner's control " + - "and those creatures gain haste until end of turn"; + staticText = "return those cards to the battlefield under their owner's control " + + "and those creatures gain haste until end of turn"; } private LegionsInitiativeReturnFromExileEffect(final LegionsInitiativeReturnFromExileEffect effect) { @@ -135,13 +136,15 @@ class LegionsInitiativeReturnFromExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, -1)); - if (player == null || exileZone == null || exileZone.isEmpty()) { + Player controller = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (controller == null + || exileZone == null + || exileZone.isEmpty()) { return false; } Cards cards = new CardsImpl(exileZone); - player.moveCards(cards, Zone.BATTLEFIELD, source, game); + controller.moveCards(cards, Zone.BATTLEFIELD, source, game); List permanents = cards.stream().map(game::getPermanent).filter(Objects::nonNull).collect(Collectors.toList()); if (permanents.isEmpty()) { return false; diff --git a/Mage.Sets/src/mage/cards/l/LongRiverLurker.java b/Mage.Sets/src/mage/cards/l/LongRiverLurker.java index 1247a07a329..8143d2b3bd1 100644 --- a/Mage.Sets/src/mage/cards/l/LongRiverLurker.java +++ b/Mage.Sets/src/mage/cards/l/LongRiverLurker.java @@ -46,7 +46,7 @@ public final class LongRiverLurker extends CardImpl { this.toughness = new MageInt(3); // Ward {1} - this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); + this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"), false)); // Other Frogs you control have ward {1}. this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/n/NashiSearcherInTheDark.java b/Mage.Sets/src/mage/cards/n/NashiSearcherInTheDark.java index f3efe3694ff..f2535b439e7 100644 --- a/Mage.Sets/src/mage/cards/n/NashiSearcherInTheDark.java +++ b/Mage.Sets/src/mage/cards/n/NashiSearcherInTheDark.java @@ -36,7 +36,7 @@ public final class NashiSearcherInTheDark extends CardImpl { this.toughness = new MageInt(2); // Menace - this.addAbility(new MenaceAbility()); + this.addAbility(new MenaceAbility(false)); // Whenever Nashi, Searcher in the Dark deals combat damage to a player, you mill that many cards. You may put any number of legendary and/or enchantment cards from among them into your hand. If you put no cards into your hand this way, put a +1/+1 counter on Nashi. this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new NashiSearcherInTheDarkEffect())); diff --git a/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java new file mode 100644 index 00000000000..6cecc1acc09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java @@ -0,0 +1,115 @@ +package mage.cards.n; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ShardToken; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.functions.EmptyCopyApplier; + +/** + * + * @author Grath + */ +public final class NikoLightOfHope extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("nonlegendary creature you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public NikoLightOfHope(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Niko, Light of Hope enters, create two Shard tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ShardToken(), 2))); + + // {2}, {T}: Exile target nonlegendary creature you control. Shards you control become copies of it until the beginning of the next end step. Return it to the battlefield under its owner's control at the beginning of the next end step. + Ability ability = new SimpleActivatedAbility(new NikoLightOfHopeEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetControlledCreaturePermanent(1, 1, filter, false)); + this.addAbility(ability); + } + + private NikoLightOfHope(final NikoLightOfHope card) { + super(card); + } + + @Override + public NikoLightOfHope copy() { + return new NikoLightOfHope(this); + } +} + +class NikoLightOfHopeEffect extends OneShotEffect { + + NikoLightOfHopeEffect() { + super(Outcome.Benefit); + staticText = "Exile target nonlegendary creature you control. Shards you control become copies of it until the beginning of the next end step. Return it to the battlefield under its owner's control at the beginning of the next end step."; + } + + private NikoLightOfHopeEffect(final NikoLightOfHopeEffect effect) { + super(effect); + } + + @Override + public NikoLightOfHopeEffect copy() { + return new NikoLightOfHopeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null && controller != null) { + if (permanent.moveToExile(source.getSourceId(), "Niko, Light of Hope", source, game)) { + FilterPermanent filter = new FilterPermanent("shards"); + filter.add(SubType.SHARD.getPredicate()); + for (Permanent copyTo : game.getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { + game.copyPermanent(Duration.UntilTheNextEndStep, permanent, copyTo.getId(), source, new EmptyCopyApplier()); + } + ExileZone exile = game.getExile().getExileZone(source.getSourceId()); + if (exile != null && !exile.isEmpty()) { + Card card = game.getCard(permanent.getId()); + if (card != null) { + Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false); + effect.setTargetPointer(new FixedTarget(card.getId())); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + } + } + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/o/OracleOfTheAlpha.java b/Mage.Sets/src/mage/cards/o/OracleOfTheAlpha.java index 3ee8e9dc668..54651e078ac 100644 --- a/Mage.Sets/src/mage/cards/o/OracleOfTheAlpha.java +++ b/Mage.Sets/src/mage/cards/o/OracleOfTheAlpha.java @@ -42,7 +42,7 @@ public final class OracleOfTheAlpha extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new OracleOfTheAlphaEffect())); // Whenever Oracle of the Alpha attacks, scry 1. - this.addAbility(new AttacksTriggeredAbility(new ScryEffect(1))); + this.addAbility(new AttacksTriggeredAbility(new ScryEffect(1, false))); } private OracleOfTheAlpha(final OracleOfTheAlpha card) { diff --git a/Mage.Sets/src/mage/cards/r/ReaperOfTheWilds.java b/Mage.Sets/src/mage/cards/r/ReaperOfTheWilds.java index bb74c64aa27..2e1c087fc35 100644 --- a/Mage.Sets/src/mage/cards/r/ReaperOfTheWilds.java +++ b/Mage.Sets/src/mage/cards/r/ReaperOfTheWilds.java @@ -31,7 +31,7 @@ public final class ReaperOfTheWilds extends CardImpl { this.toughness = new MageInt(5); // Whenever another creature dies, scry 1. - this.addAbility(new DiesCreatureTriggeredAbility(new ScryEffect(1), false, true)); + this.addAbility(new DiesCreatureTriggeredAbility(new ScryEffect(1, false), false, true)); // {B}: Reaper of the Wilds gains deathtouch until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl<>("{B}"))); // {1}{G}: Reaper of the Wilds gains hexproof until end of turn. diff --git a/Mage.Sets/src/mage/cards/s/SawbladeSkinripper.java b/Mage.Sets/src/mage/cards/s/SawbladeSkinripper.java index eded8a073a5..9c0d15130af 100644 --- a/Mage.Sets/src/mage/cards/s/SawbladeSkinripper.java +++ b/Mage.Sets/src/mage/cards/s/SawbladeSkinripper.java @@ -39,7 +39,7 @@ public final class SawbladeSkinripper extends CardImpl { this.toughness = new MageInt(2); // Menace - this.addAbility(new MenaceAbility()); + this.addAbility(new MenaceAbility(false)); // {2}, Sacrifice another creature or enchantment: Put a +1/+1 counter on Sawblade Skinripper. Ability ability = new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java b/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java index a3c56ecdb5b..3ae943bd288 100644 --- a/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java +++ b/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java @@ -41,7 +41,7 @@ public final class StrengthTestingHammer extends CardImpl { this.addAbility(ability); // Equip {3} - this.addAbility(new EquipAbility(3)); + this.addAbility(new EquipAbility(3, false)); } private StrengthTestingHammer(final StrengthTestingHammer card) { @@ -100,6 +100,3 @@ class StrengthTestingHammerEffect extends OneShotEffect { return false; } } - - - diff --git a/Mage.Sets/src/mage/cards/t/TegwyllsScouring.java b/Mage.Sets/src/mage/cards/t/TegwyllsScouring.java new file mode 100644 index 00000000000..b01786f5bbe --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TegwyllsScouring.java @@ -0,0 +1,63 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.PayMoreToCastAsThoughtItHadFlashAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.permanent.token.FaerieRogueToken; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author karapuzz14 + */ +public final class TegwyllsScouring extends CardImpl { + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped creatures you control with flying"); + + static { + filter.add(new AbilityPredicate(FlyingAbility.class)); + filter.add(TappedPredicate.UNTAPPED); + } + + public TegwyllsScouring(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); + + + // You may cast Tegwyll's Scouring as though it had flash by tapping three untapped creatures you control with flying in addition to paying its other costs. + Cost asThoughCost = new TapTargetCost(new TargetControlledCreaturePermanent(3, 3, filter, true)).setText(""); + CostsImpl costs = new CostsImpl<>().setText("tapping three untapped creatures you control with flying"); + costs.add(asThoughCost); + + Ability ability = new PayMoreToCastAsThoughtItHadFlashAbility(this, costs); + ability.addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES)); + ability.addEffect(new CreateTokenEffect(new FaerieRogueToken(), 3)); + this.addAbility(ability); + + // Destroy all creatures. + this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES)); + + //Create three 1/1 black Faerie Rogue creature tokens with flying. + this.getSpellAbility().addEffect(new CreateTokenEffect(new FaerieRogueToken(), 3)); + } + + private TegwyllsScouring(final TegwyllsScouring card) { + super(card); + } + + @Override + public TegwyllsScouring copy() { + return new TegwyllsScouring(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheLordOfPain.java b/Mage.Sets/src/mage/cards/t/TheLordOfPain.java new file mode 100644 index 00000000000..470a4556562 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheLordOfPain.java @@ -0,0 +1,141 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.CantGainLifeAllEffect; +import mage.constants.*; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPlayer; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.SpellsCastWatcher; + +/** + * + * @author Grath + */ +public final class TheLordOfPain extends CardImpl { + public TheLordOfPain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Your opponents can't gain life. + this.addAbility(new SimpleStaticAbility( + new CantGainLifeAllEffect(Duration.WhileOnBattlefield, TargetController.OPPONENT) + )); + + // Whenever a player casts their first spell each turn, choose another target player. The Lord of Pain deals damage equal to that spell's mana value to the chosen player. + this.addAbility(new TheLordOfPainTriggeredAbility()); + } + + private TheLordOfPain(final TheLordOfPain card) { + super(card); + } + + @Override + public TheLordOfPain copy() { + return new TheLordOfPain(this); + } +} + +enum TheLordOfPainPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + return game.getState() + .getWatcher(SpellsCastWatcher.class) + .getCount(input.getControllerId()) == 1; + } +} + +class TheLordOfPainTriggeredAbility extends SpellCastAllTriggeredAbility { + private static final FilterSpell filter = new FilterSpell("their first spell each turn"); + + static { + filter.add(TheLordOfPainPredicate.instance); + } + + public TheLordOfPainTriggeredAbility() { + super(new TheLordOfPainEffect(), filter, false, SetTargetPointer.PLAYER); + } + + protected TheLordOfPainTriggeredAbility(final TheLordOfPainTriggeredAbility ability) { + super(ability); + } + + @Override + public TheLordOfPainTriggeredAbility copy() { + return new TheLordOfPainTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + Player controller = game.getPlayer(getControllerId()); + Spell spell = (Spell)getEffects().get(0).getValue("spellCast"); + if (controller != null) { + FilterPlayer filter2 = new FilterPlayer("another target player"); + filter2.add(Predicates.not(new MageObjectReferencePredicate(spell.getControllerId(), game))); + TargetPlayer target = new TargetPlayer(1, 1, false, filter2); + controller.choose(Outcome.Damage, target, this, game); + getEffects().setTargetPointer(new FixedTarget(target.getFirstTarget())); + return true; + } + } + return false; + } +} + +class TheLordOfPainEffect extends OneShotEffect { + + TheLordOfPainEffect() { + super(Outcome.Benefit); + staticText = "choose another target player. {this} deals damage equal to that spell's mana value to the chosen player"; + } + + private TheLordOfPainEffect(final TheLordOfPainEffect effect) { + super(effect); + } + + @Override + public TheLordOfPainEffect copy() { + return new TheLordOfPainEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = (Spell)this.getValue("spellCast"); + if (spell != null) { + int cost = spell.getManaValue(); + Player target = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (target != null) { + target.damage(cost, source.getSourceId(), source, game); + return true; + } + } + return false; } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMindskinner.java b/Mage.Sets/src/mage/cards/t/TheMindskinner.java index 98580d540aa..067e522d99c 100644 --- a/Mage.Sets/src/mage/cards/t/TheMindskinner.java +++ b/Mage.Sets/src/mage/cards/t/TheMindskinner.java @@ -66,11 +66,12 @@ class TheMindskinnerEffect extends PreventionEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { + int amount = event.getAmount(); preventDamageAction(event, source, game); for (UUID playerId : game.getOpponents(source.getControllerId())) { Player player = game.getPlayer(playerId); if (player != null) { - player.millCards(event.getAmount(), source, game); + player.millCards(amount, source, game); } } return true; diff --git a/Mage.Sets/src/mage/cards/w/WinterMisanthropicGuide.java b/Mage.Sets/src/mage/cards/w/WinterMisanthropicGuide.java index f6df98bcf64..689ecbc1f50 100644 --- a/Mage.Sets/src/mage/cards/w/WinterMisanthropicGuide.java +++ b/Mage.Sets/src/mage/cards/w/WinterMisanthropicGuide.java @@ -33,7 +33,7 @@ public final class WinterMisanthropicGuide extends CardImpl { this.toughness = new MageInt(4); // Ward {2} - this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); // At the beginning of your upkeep, each player draws two cards. this.addAbility(new BeginningOfUpkeepTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/z/ZenithChronicler.java b/Mage.Sets/src/mage/cards/z/ZenithChronicler.java index 9a46850d0ab..20f7bc10662 100644 --- a/Mage.Sets/src/mage/cards/z/ZenithChronicler.java +++ b/Mage.Sets/src/mage/cards/z/ZenithChronicler.java @@ -76,7 +76,7 @@ class ZenithChroniclerTriggeredAbility extends TriggeredAbilityImpl { Player controller = game.getPlayer(getControllerId()); Spell spell = game.getSpell(event.getTargetId()); SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); - if (controller != null && spell != null && watcher != null) { + if (controller != null && spell != null && watcher != null && spell.getColor(game).isMulticolored()) { int multicoloredSpell = 0; for (Spell spellCastThisTurn : watcher.getSpellsCastThisTurn(spell.getControllerId())) { if (spellCastThisTurn.getColor(game).isMulticolored() && ++multicoloredSpell > 1) { diff --git a/Mage.Sets/src/mage/cards/z/ZimoneMysteryUnraveler.java b/Mage.Sets/src/mage/cards/z/ZimoneMysteryUnraveler.java new file mode 100644 index 00000000000..a71427986c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZimoneMysteryUnraveler.java @@ -0,0 +1,86 @@ +package mage.cards.z; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.IfAbilityHasResolvedXTimesEffect; +import mage.abilities.effects.keyword.ManifestDreadEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.card.FaceDownPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.watchers.common.AbilityResolvedWatcher; + +/** + * + * @author Grath + */ +public final class ZimoneMysteryUnraveler extends CardImpl { + + public ZimoneMysteryUnraveler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Landfall -- Whenever a land you control enters, manifest dread if this is the first time this ability has resolved this turn. Otherwise, you may turn a permanent you control face up. + Ability ability = new LandfallAbility(new IfAbilityHasResolvedXTimesEffect( + Outcome.Benefit, 1, new ManifestDreadEffect() + ).setText("manifest dread if this is the first time this ability has resolved this turn."), false); + ability.addEffect(new IfAbilityHasResolvedXTimesEffect(Outcome.Benefit, 2, true, new ZimoneMysteryUnravelerEffect())); + this.addAbility(ability, new AbilityResolvedWatcher()); + } + + private ZimoneMysteryUnraveler(final ZimoneMysteryUnraveler card) { + super(card); + } + + @Override + public ZimoneMysteryUnraveler copy() { + return new ZimoneMysteryUnraveler(this); + } +} + +class ZimoneMysteryUnravelerEffect extends OneShotEffect { + + public ZimoneMysteryUnravelerEffect() { + super(Outcome.Benefit); + this.staticText = "you may turn a permanent you control face up"; + } + + protected ZimoneMysteryUnravelerEffect(final ZimoneMysteryUnravelerEffect effect) { + super(effect); + } + + @Override + public ZimoneMysteryUnravelerEffect copy() { + return new ZimoneMysteryUnravelerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + FilterControlledPermanent filter = new FilterControlledPermanent("a permanent you control"); + filter.add(FaceDownPredicate.instance); + if (controller != null) { + TargetControlledPermanent target = new TargetControlledPermanent(0, 1, filter, true); + controller.choose(Outcome.BoostCreature, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + return permanent != null && permanent.turnFaceUp(source, game, source.getControllerId()); + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/DominariaRemastered.java b/Mage.Sets/src/mage/sets/DominariaRemastered.java index cf6c84c2f6e..847cc35dad3 100644 --- a/Mage.Sets/src/mage/sets/DominariaRemastered.java +++ b/Mage.Sets/src/mage/sets/DominariaRemastered.java @@ -3,6 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; /** * @author TheElk801 @@ -20,7 +27,7 @@ public class DominariaRemastered extends ExpansionSet { this.hasBoosters = true; this.hasBasicLands = true; this.maxCardNumberInBooster = 261; - this.numBoosterCommon = 10; // Frame/art variants not yet implemented for booster generation + this.numBoosterCommon = 10; this.numBoosterUncommon = 3; this.numBoosterRare = 1; this.ratioBoosterMythic = 7; // 60 rare, 20 mythic @@ -484,8 +491,75 @@ public class DominariaRemastered extends ExpansionSet { cards.add(new SetCardInfo("Zur the Enchanter", 374, Rarity.RARE, mage.cards.z.ZurTheEnchanter.class, NON_FULL_USE_VARIOUS)); } -// @Override -// public BoosterCollator createCollator() { -// return new DominariaRemasteredCollator(); -// } + @Override + public BoosterCollator createCollator() { + return new DominariaRemasteredCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/dmr.html +// 1/4 of packs contain an old frame rare/mythic +// 6% of packs contain a borderles C/U and 17% of packs contain a borderless card +// using borderless for 1/3 of the printings of R/M with borderless variations works with those numbers +class DominariaRemasteredCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "56", "29", "226", "89", "150", "134", "45", "19", "98", "167", "114", "43", "23", "104", "173", "128", "52", "1", "97", "168", "143", "57", "17", "87", "160", "141", "55", "15", "83", "184", "125", "66", "30", "102", "156", "146", "63", "35", "94", "176", "130", "58", "21", "76", "174", "124", "38", "3", "106", "182", "117", "65", "29", "109", "173", "118", "53", "24", "107", "158", "112", "56", "9", "89", "150", "134", "43", "15", "98", "167", "141", "45", "1", "104", "180", "128", "52", "23", "87", "168", "143", "57", "17", "97", "160", "114", "63", "19", "76", "184", "125", "66", "21", "102", "156", "146", "55", "30", "94", "176", "124", "58", "35", "83", "174", "130", "38", "3", "106", "182", "112", "65", "9", "109", "158", "118", "53", "24", "107", "180", "117"); + private final CardRun commonB = new CardRun(true, "226", "152", "140", "41", "22", "228", "257", "88", "178", "131", "61", "18", "256", "232", "81", "172", "137", "20", "254", "85", "74", "163", "72", "16", "245", "90", "152", "145", "33", "46", "222", "259", "183", "99", "123", "233", "61", "140", "253", "18", "169", "103", "131", "41", "22", "257", "228", "88", "178", "51", "11", "113", "256", "232", "81", "172", "137", "74", "254", "85", "163", "20", "145", "72", "16", "245", "90", "152", "46", "33", "123", "233", "259", "99", "183", "113", "51", "11", "222", "253", "103", "169", "131", "41", "22", "228", "257", "88", "178", "140", "61", "18", "254", "232", "81", "172", "137", "46", "20", "256", "85", "163", "16", "72", "245", "90", "169", "145", "74", "33", "233", "253", "99", "183", "123", "51", "11", "222", "113", "259", "103"); + private final CardRun uncommonA = new CardRun(true, "251", "164", "44", "207", "129", "170", "80", "122", "221", "14", "157", "8", "93", "234", "213", "119", "2", "54", "260", "220", "120", "154", "49", "215", "10", "231", "153", "42", "252", "219", "84", "216", "70", "225", "214", "79", "255", "64", "210", "142", "251", "44", "207", "164", "129", "170", "80", "221", "122", "157", "14", "93", "8", "213", "119", "234", "54", "2", "260", "49", "120", "154", "220", "215", "10", "42", "153", "231", "252", "219", "84", "216", "70", "225", "214", "79", "255", "64", "210", "142", "251", "44", "164", "207", "129", "170", "80", "221", "122", "14", "157", "8", "93", "213", "234", "119", "54", "2", "260", "220", "215", "154", "49", "120", "10", "231", "153", "42", "219", "252", "84", "216", "70", "225", "214", "79", "255", "64", "210", "142"); + private final CardRun uncommonB = new CardRun(true, "246", "48", "155", "209", "40", "86", "28", "208", "115", "212", "34", "196", "190", "240", "199", "105", "197", "25", "162", "242", "116", "189", "243", "138", "181", "111", "202", "78", "68", "31", "147", "235", "204", "100", "203", "177", "4", "211", "192", "246", "195", "48", "209", "40", "155", "212", "28", "208", "115", "196", "86", "34", "190", "240", "199", "105", "162", "25", "197", "243", "189", "116", "242", "138", "181", "111", "202", "68", "78", "203", "147", "235", "204", "100", "31", "177", "211", "4", "192", "195", "246", "48", "155", "209", "40", "86", "28", "208", "115", "212", "34", "196", "190", "240", "199", "105", "162", "25", "197", "243", "189", "116", "242", "138", "181", "111", "202", "68", "78", "203", "147", "235", "204", "100", "31", "177", "4", "211", "192", "195"); + private final CardRun uncommonC = new CardRun(false, "425", "432", "419", "446", "429"); + private final CardRun rare = new CardRun(false, "186", "186", "186", "186", "443", "443", "149", "149", "149", "149", "438", "438", "187", "187", "187", "187", "187", "187", "39", "39", "39", "39", "39", "39", "151", "151", "151", "151", "439", "439", "75", "75", "75", "75", "75", "75", "77", "77", "77", "77", "424", "424", "241", "241", "241", "241", "241", "241", "217", "217", "217", "217", "217", "217", "218", "218", "218", "218", "218", "218", "244", "244", "454", "188", "188", "188", "188", "444", "444", "47", "47", "47", "47", "417", "417", "5", "5", "5", "5", "5", "5", "6", "6", "6", "6", "412", "412", "82", "82", "82", "82", "426", "426", "159", "159", "159", "159", "159", "159", "50", "50", "418", "161", "161", "161", "161", "161", "161", "121", "121", "121", "121", "433", "433", "223", "223", "447", "247", "247", "247", "247", "455", "455", "7", "7", "7", "7", "7", "7", "126", "126", "126", "126", "434", "434", "224", "224", "224", "224", "448", "448", "248", "248", "248", "248", "248", "248", "191", "191", "445", "249", "249", "249", "249", "249", "249", "227", "227", "227", "227", "449", "449", "165", "165", "165", "165", "165", "165", "166", "166", "166", "127", "127", "435", "229", "229", "450", "12", "12", "12", "12", "12", "12", "230", "230", "230", "230", "451", "451", "13", "13", "413", "250", "250", "250", "250", "456", "456", "91", "91", "91", "91", "91", "91", "60", "60", "60", "60", "421", "421", "59", "59", "59", "59", "420", "420", "92", "92", "92", "92", "92", "92", "95", "95", "427", "171", "171", "440", "62", "62", "62", "62", "62", "62", "132", "132", "132", "132", "132", "132", "96", "96", "96", "96", "428", "428", "133", "133", "133", "133", "133", "133", "193", "193", "193", "193", "193", "193", "194", "194", "194", "194", "194", "194", "198", "198", "198", "198", "198", "198", "101", "101", "101", "101", "101", "101", "175", "175", "175", "175", "175", "175", "26", "26", "26", "27", "27", "27", "27", "27", "27", "135", "135", "135", "135", "135", "135", "136", "136", "136", "136", "436", "436", "139", "139", "139", "200", "200", "200", "200", "200", "200", "201", "201", "201", "201", "201", "201", "67", "67", "67", "67", "67", "67", "258", "258", "258", "258", "258", "258", "144", "144", "144", "144", "144", "144", "179", "179", "441", "32", "32", "414", "69", "69", "422", "236", "236", "236", "236", "452", "452", "237", "237", "237", "237", "237", "237", "71", "71", "423", "238", "238", "238", "238", "238", "238", "239", "239", "453", "108", "108", "430", "73", "73", "73", "73", "73", "73", "36", "36", "36", "36", "415", "415", "261", "261", "261", "261", "261", "261", "148", "148", "437", "185", "185", "185", "185", "442", "442", "37", "37", "37", "37", "416", "416", "205", "205", "205", "205", "205", "205", "110", "110", "431", "206", "206", "206", "206", "206", "206"); + private final CardRun rareOld = new CardRun(false, "354", "354", "335", "335", "355", "355", "280", "280", "336", "336", "298", "298", "299", "299", "393", "393", "375", "375", "376", "376", "394", "356", "356", "282", "282", "262", "262", "263", "263", "304", "304", "339", "339", "284", "341", "341", "321", "321", "378", "395", "395", "264", "264", "324", "324", "379", "379", "396", "396", "359", "397", "397", "381", "381", "343", "343", "344", "325", "383", "265", "265", "384", "384", "266", "398", "398", "305", "305", "289", "289", "288", "288", "306", "306", "308", "347", "290", "290", "327", "327", "309", "309", "328", "328", "361", "361", "362", "362", "366", "366", "310", "310", "348", "348", "272", "273", "273", "329", "329", "330", "330", "331", "368", "368", "369", "369", "293", "293", "400", "400", "332", "332", "350", "276", "294", "389", "389", "390", "390", "296", "391", "391", "392", "314", "297", "297", "278", "278", "401", "401", "334", "353", "353", "279", "279", "373", "373", "315", "374", "374"); + private final CardRun retro = new CardRun(false, "300", "316", "281", "377", "301", "337", "317", "357", "302", "303", "338", "318", "340", "283", "319", "320", "410", "285", "322", "358", "323", "286", "380", "287", "342", "404", "382", "345", "267", "385", "399", "326", "268", "408", "360", "346", "307", "386", "291", "292", "402", "363", "364", "365", "269", "270", "367", "271", "274", "370", "349", "311", "406", "275", "371", "312", "387", "372", "388", "295", "313", "333", "277", "351", "352"); + private final CardRun common = new CardRun(false, "38", "1", "112", "41", "150", "152", "76", "113", "3", "43", "114", "45", "46", "222", "245", "81", "156", "117", "158", "118", "83", "160", "85", "51", "163", "52", "123", "124", "87", "125", "53", "55", "88", "89", "9", "90", "56", "226", "228", "167", "11", "168", "57", "128", "169", "130", "58", "232", "131", "15", "16", "94", "17", "61", "18", "233", "63", "19", "172", "65", "20", "97", "98", "99", "253", "173", "21", "22", "254", "23", "134", "174", "24", "176", "137", "256", "257", "66", "140", "141", "29", "178", "102", "143", "30", "145", "180", "259", "103", "104", "146", "106", "107", "72", "33", "182", "35", "183", "184", "74", "109"); + private final CardRun land = new CardRun(false, "402", "403", "404", "405", "406", "407", "408", "409", "410", "411"); + + private final BoosterStructure AAAAAABBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB + ); + private final BoosterStructure AAAAABBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB + ); + private final BoosterStructure AAA = new BoosterStructure(uncommonA, uncommonA, uncommonA); + private final BoosterStructure BBB = new BoosterStructure(uncommonB, uncommonB, uncommonB); + private final BoosterStructure AAC = new BoosterStructure(uncommonA, uncommonA, uncommonC); + private final BoosterStructure BBC = new BoosterStructure(uncommonB, uncommonB, uncommonC); + + private final BoosterStructure OR = new BoosterStructure(retro,rare); + private final BoosterStructure CO = new BoosterStructure(common,rareOld); + private final BoosterStructure L1 = new BoosterStructure(land); + + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAAABBB, AAAAAABBB, + AAAAABBBB, AAAAABBBB, AAAAABBBB + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, + AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, + AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, + AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, + AAA, AAA, AAA, AAA, AAA, AAA, AAA, + BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, + BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, + BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, + BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, BBB, + BBB, BBB, BBB, BBB, BBB, BBB, BBB, + AAC, AAC, AAC, + BBC, BBC, BBC + ); + + private final RarityConfiguration rareRuns = new RarityConfiguration(OR,OR,OR,CO); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 1b304b8f2f6..941be07fedd 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -149,6 +149,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Murky Sewer", 263, Rarity.COMMON, mage.cards.m.MurkySewer.class)); cards.add(new SetCardInfo("Nashi, Searcher in the Dark", 223, Rarity.RARE, mage.cards.n.NashiSearcherInTheDark.class)); cards.add(new SetCardInfo("Neglected Manor", 264, Rarity.COMMON, mage.cards.n.NeglectedManor.class)); + cards.add(new SetCardInfo("Niko, Light of Hope", 224, Rarity.MYTHIC, mage.cards.n.NikoLightOfHope.class)); cards.add(new SetCardInfo("Norin, Swift Survivalist", 145, Rarity.UNCOMMON, mage.cards.n.NorinSwiftSurvivalist.class)); cards.add(new SetCardInfo("Oblivious Bookworm", 225, Rarity.UNCOMMON, mage.cards.o.ObliviousBookworm.class)); cards.add(new SetCardInfo("Optimistic Scavenger", 21, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index c12c4a2d15d..aefc2b6eddb 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -72,7 +72,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Command Tower", 96, Rarity.COMMON, mage.cards.c.CommandTower.class)); cards.add(new SetCardInfo("Commander's Sphere", 244, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); cards.add(new SetCardInfo("Convert to Slime", 37, Rarity.RARE, mage.cards.c.ConvertToSlime.class)); - cards.add(new SetCardInfo("Counterspell", 114, Rarity.COMMON, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Counterspell", 114, Rarity.UNCOMMON, mage.cards.c.Counterspell.class)); cards.add(new SetCardInfo("Crawling Sensation", 173, Rarity.UNCOMMON, mage.cards.c.CrawlingSensation.class)); cards.add(new SetCardInfo("Crypt Ghast", 368, Rarity.MYTHIC, mage.cards.c.CryptGhast.class)); cards.add(new SetCardInfo("Culling Ritual", 85, Rarity.RARE, mage.cards.c.CullingRitual.class)); @@ -134,7 +134,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Harsh Mentor", 165, Rarity.RARE, mage.cards.h.HarshMentor.class)); cards.add(new SetCardInfo("Haywire Mite", 247, Rarity.UNCOMMON, mage.cards.h.HaywireMite.class)); cards.add(new SetCardInfo("Hinterland Harbor", 284, Rarity.RARE, mage.cards.h.HinterlandHarbor.class)); - cards.add(new SetCardInfo("Hornet Queen", 184, Rarity.MYTHIC, mage.cards.h.HornetQueen.class)); + cards.add(new SetCardInfo("Hornet Queen", 184, Rarity.RARE, mage.cards.h.HornetQueen.class)); cards.add(new SetCardInfo("Hydra Omnivore", 185, Rarity.MYTHIC, mage.cards.h.HydraOmnivore.class)); cards.add(new SetCardInfo("Infernal Grasp", 143, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class)); cards.add(new SetCardInfo("Inkshield", 221, Rarity.RARE, mage.cards.i.Inkshield.class)); @@ -257,6 +257,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Temur War Shaman", 200, Rarity.RARE, mage.cards.t.TemurWarShaman.class)); cards.add(new SetCardInfo("Terminus", 70, Rarity.RARE, mage.cards.t.Terminus.class)); cards.add(new SetCardInfo("The Eldest Reborn", 139, Rarity.UNCOMMON, mage.cards.t.TheEldestReborn.class)); + cards.add(new SetCardInfo("The Lord of Pain", 3, Rarity.MYTHIC, mage.cards.t.TheLordOfPain.class)); cards.add(new SetCardInfo("Theater of Horrors", 236, Rarity.RARE, mage.cards.t.TheaterOfHorrors.class)); cards.add(new SetCardInfo("They Came from the Pipes", 14, Rarity.RARE, mage.cards.t.TheyCameFromThePipes.class)); cards.add(new SetCardInfo("Thirst for Meaning", 129, Rarity.COMMON, mage.cards.t.ThirstForMeaning.class)); @@ -292,5 +293,6 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Yavimaya Coast", 327, Rarity.RARE, mage.cards.y.YavimayaCoast.class)); cards.add(new SetCardInfo("Yavimaya Elder", 208, Rarity.COMMON, mage.cards.y.YavimayaElder.class)); cards.add(new SetCardInfo("Yedora, Grave Gardener", 209, Rarity.UNCOMMON, mage.cards.y.YedoraGraveGardener.class)); + cards.add(new SetCardInfo("Zimone, Mystery Unraveler", 8, Rarity.MYTHIC, mage.cards.z.ZimoneMysteryUnraveler.class)); } } diff --git a/Mage.Sets/src/mage/sets/Lorwyn.java b/Mage.Sets/src/mage/sets/Lorwyn.java index a40bb52b2b0..072258c03e7 100644 --- a/Mage.Sets/src/mage/sets/Lorwyn.java +++ b/Mage.Sets/src/mage/sets/Lorwyn.java @@ -3,6 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; /** * @author North @@ -328,4 +335,62 @@ public final class Lorwyn extends ExpansionSet { cards.add(new SetCardInfo("Wydwen, the Biting Gale", 253, Rarity.RARE, mage.cards.w.WydwenTheBitingGale.class)); cards.add(new SetCardInfo("Zephyr Net", 98, Rarity.COMMON, mage.cards.z.ZephyrNet.class)); } + + @Override + public BoosterCollator createCollator() { + return new LorwynCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/lrw.html +// order not known for uncommon runs, but card division is +// pack distribution for uncommons not specified, assuming AAB ABB as in Onslaught +class LorwynCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "91", "191", "128", "197", "177", "35", "77", "108", "260", "231", "32", "84", "189", "214", "126", "23", "181", "134", "77", "231", "258", "156", "33", "189", "90", "128", "211", "126", "35", "96", "227", "148", "48", "91", "161", "103", "229", "260", "181", "32", "78", "227", "156", "143", "23", "96", "103", "148", "211", "258", "20", "84", "191", "108", "197", "33", "78", "177", "134", "229", "161", "48", "90", "143", "214", "20"); + private final CardRun commonB = new CardRun(true, "118", "57", "17", "212", "110", "190", "68", "201", "34", "265", "118", "151", "242", "25", "115", "76", "184", "228", "147", "27", "57", "183", "223", "257", "4", "146", "184", "212", "50", "137", "43", "201", "194", "265", "87", "4", "198", "110", "185", "261", "232", "17", "68", "146", "190", "223", "43", "76", "147", "185", "242", "261", "27", "115", "151", "228", "87", "34", "137", "198", "50", "194", "257", "25", "232", "183"); + private final CardRun commonC = new CardRun(true, "16", "62", "132", "152", "206", "12", "54", "129", "163", "234", "16", "97", "166", "111", "241", "152", "8", "81", "99", "273", "205", "170", "29", "89", "141", "166", "93", "234", "45", "132", "163", "62", "241", "29", "141", "154", "81", "218", "45", "129", "157", "93", "206", "54", "8", "154", "111", "218", "170", "12", "89", "99", "205", "97", "157"); + private final CardRun commonD = new CardRun(true, "22", "225", "153", "109", "79", "217", "140", "39", "164", "204", "51", "24", "138", "225", "41", "79", "100", "80", "164", "140", "52", "24", "236", "153", "47", "215", "101", "98", "100", "22", "70", "186", "40", "204", "47", "52", "127", "98", "138", "215", "80", "180", "51", "236", "109", "39", "127", "186", "41", "217", "70", "101", "180", "40", "273"); + private final CardRun uncommonA = new CardRun(false, "5", "102", "155", "7", "55", "11", "162", "59", "207", "208", "13", "112", "64", "165", "167", "169", "67", "18", "174", "117", "119", "26", "224", "226", "122", "72", "73", "130", "136", "188", "82", "42", "237", "239", "94", "46", "275", "277", "279", "243"); + private final CardRun uncommonB = new CardRun(false, "53", "199", "158", "160", "9", "200", "10", "58", "60", "61", "63", "113", "168", "114", "116", "171", "172", "216", "19", "220", "221", "182", "222", "28", "124", "125", "74", "36", "235", "38", "139", "86", "142", "193", "144", "195", "276", "278", "49", "245"); + private final CardRun rare = new CardRun(false, "1", "266", "2", "150", "149", "267", "3", "104", "6", "246", "105", "159", "202", "106", "254", "56", "203", "255", "256", "247", "107", "209", "210", "65", "14", "66", "248", "15", "213", "268", "69", "173", "175", "21", "249", "176", "269", "219", "178", "179", "71", "120", "121", "123", "230", "30", "31", "75", "270", "250", "131", "187", "133", "233", "135", "37", "259", "83", "271", "85", "272", "88", "274", "192", "92", "251", "262", "145", "44", "263", "238", "264", "240", "280", "95", "196", "281", "252", "244", "253"); + + private final BoosterStructure AAAAAACCCCC = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonC, commonC, commonC, commonC, commonC + ); + private final BoosterStructure AAAAAADDDDD = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonD, commonD, commonD, commonD, commonD + ); + private final BoosterStructure BBBBBBCCCCC = new BoosterStructure( + commonB, commonB, commonB, commonB, commonB, commonB, + commonC, commonC, commonC, commonC, commonC + ); + private final BoosterStructure BBBBBBDDDDD = new BoosterStructure( + commonB, commonB, commonB, commonB, commonB, commonB, + commonD, commonD, commonD, commonD, commonD + ); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAAACCCCC, AAAAAADDDDD, BBBBBBCCCCC, BBBBBBDDDDD + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, ABB + ); + + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/NewPhyrexia.java b/Mage.Sets/src/mage/sets/NewPhyrexia.java index c5dc6ca2c8c..e056f13aeb0 100644 --- a/Mage.Sets/src/mage/sets/NewPhyrexia.java +++ b/Mage.Sets/src/mage/sets/NewPhyrexia.java @@ -3,6 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; public final class NewPhyrexia extends ExpansionSet { @@ -198,4 +205,62 @@ public final class NewPhyrexia extends ExpansionSet { cards.add(new SetCardInfo("Wing Splicer", 50, Rarity.UNCOMMON, mage.cards.w.WingSplicer.class)); cards.add(new SetCardInfo("Xenograft", 51, Rarity.RARE, mage.cards.x.Xenograft.class)); } + + @Override + public BoosterCollator createCollator() { + return new NewPhyrexiaCollator(); + } } + +// Booster collation info from https://www.lethe.xyz/mtg/collation/nph.html +class NewPhyrexiaCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "34", "99", "111", "131", "66", "30", "22", "89", "139", "113", "55", "2", "48", "131", "114", "93", "25", "76", "35", "138", "111", "83", "11", "55", "120", "99", "43", "150", "125", "22", "83", "75", "21", "34", "113", "140", "14", "60", "94", "11", "124", "48", "139", "120", "25", "75", "94", "30", "125", "140", "2", "145", "66", "89", "43", "114", "14", "138", "60", "35", "93", "124", "76", "21", "145", "150"); + private final CardRun commonB = new CardRun(true, "122", "45", "69", "84", "163", "26", "96", "110", "67", "40", "100", "24", "149", "36", "69", "116", "79", "61", "26", "151", "46", "92", "13", "108", "45", "63", "100", "67", "163", "24", "29", "79", "46", "96", "63", "19", "40", "52", "110", "149", "84", "136", "29", "13", "122", "61", "151", "108", "36", "92", "52", "116", "136", "19"); + private final CardRun uncommonA = new CardRun(true, "5", "56", "134", "126", "102", "28", "53", "8", "134", "107", "154", "56", "118", "156", "102", "12", "133", "107", "33", "165", "53", "154", "126", "91", "142", "8", "33", "112", "77", "90", "129", "15", "39", "152", "64", "90", "142", "12", "112", "32", "77", "129", "101", "117", "156", "72", "5", "101", "39", "117", "165", "3", "64", "133", "147", "91", "3", "32", "72", "147", "86", "15", "118", "28", "152", "86"); + private final CardRun uncommonB = new CardRun(true, "160", "105", "144", "123", "59", "27", "47", "103", "144", "97", "141", "157", "121", "38", "82", "57", "23", "157", "16", "49", "82", "58", "10", "85", "155", "103", "38", "153", "78", "59", "155", "27", "7", "160", "50", "58", "141", "78", "123", "50", "85", "57", "97", "23", "47", "70", "7", "105", "10", "49", "70", "153", "121", "16"); + private final CardRun rare = new CardRun(false, "130", "104", "104", "4", "4", "80", "80", "132", "132", "6", "6", "54", "54", "81", "81", "31", "31", "106", "106", "9", "135", "109", "109", "62", "62", "137", "137", "87", "87", "37", "128", "128", "1", "143", "143", "65", "65", "115", "115", "88", "88", "146", "146", "17", "17", "148", "148", "41", "41", "42", "42", "68", "119", "119", "18", "18", "71", "71", "44", "44", "20", "20", "73", "95", "95", "158", "158", "159", "159", "74", "74", "161", "162", "162", "164", "164", "98", "127", "51", "51"); + private final CardRun land = new CardRun(false, "166", "167", "168", "169", "170", "171", "172", "173", "174", "175"); + + private final BoosterStructure AAAAAABBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB + ); + private final BoosterStructure AAAAABBBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB + ); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 5.5 A uncommons (11 / 2) + // 4.5 B uncommons (9 / 2) + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAAABBBB, AAAAABBBBB + ); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOne.java b/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOne.java index 9ec1ebe22b2..00b975ce555 100644 --- a/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOne.java +++ b/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOne.java @@ -3,7 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; +import java.util.ArrayList; +import java.util.List; /** * @author TheElk801 */ @@ -508,8 +514,81 @@ public final class PhyrexiaAllWillBeOne extends ExpansionSet { cards.add(new SetCardInfo("Zopandrel, Hunger Dominus", 458, Rarity.MYTHIC, mage.cards.z.ZopandrelHungerDominus.class, NON_FULL_USE_VARIOUS)); } -// @Override -// public BoosterCollator createCollator() { -// return new PhyrexiaAllWillBeOneCollator(); -// } + @Override + public BoosterCollator createCollator() { + return new PhyrexiaAllWillBeOneCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/one.html +// Using Japanese collation for common, no collation for uncommons +// Using 2:1 for each rare:mythic which results in (1/7) 14.29% mythic packs, matching the advertised ~14% +class PhyrexiaAllWillBeOneCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "54", "251", "44", "77", "243", "49", "228", "45", "231", "55", "260", "58", "60", "238", "65", "187", "54", "253", "59", "252", "52", "44", "224", "62", "251", "50", "139", "48", "236", "77", "238", "59", "53", "253", "65", "50", "187", "60", "228", "48", "260", "55", "251", "53", "231", "58", "252", "49", "243", "45", "224", "52", "236", "54", "139", "62", "44", "252", "58", "48", "238", "55", "243", "59", "139", "52", "260", "62", "54", "231", "50", "228", "44", "253", "77", "224", "49", "251", "65", "60", "236", "53", "187", "45", "228", "55", "52", "253", "50", "260", "77", "236", "58", "238", "45", "252", "65", "139", "49", "187", "59", "243", "48", "62", "231", "60", "53", "224"); + private final CardRun commonB = new CardRun(true, "16", "94", "111", "15", "7", "97", "12", "96", "102", "20", "120", "110", "9", "25", "103", "6", "40", "94", "4", "247", "117", "13", "137", "88", "39", "225", "109", "22", "151", "116", "28", "226", "92", "21", "12", "97", "8", "20", "111", "261", "39", "89", "28", "36", "117", "21", "120", "116", "8", "247", "103", "13", "40", "110", "22", "16", "102", "225", "4", "88", "151", "9", "109", "226", "7", "96", "137", "25", "92", "36", "6", "116", "261", "15", "109", "21", "39", "110", "16", "13", "102", "22", "137", "94", "25", "15", "111", "247", "8", "89", "12", "225", "117", "9", "120", "97", "7", "261", "103", "36", "40", "88", "28", "20", "92", "226", "4", "96", "151", "6", "89"); + private final CardRun commonC = new CardRun(true, "81", "165", "147", "188", "286", "126", "162", "173", "130", "180", "66", "122", "164", "157", "123", "160", "85", "156", "174", "237", "121", "170", "130", "188", "123", "185", "290", "122", "181", "179", "133", "157", "81", "135", "180", "182", "148", "66", "170", "126", "32", "162", "156", "235", "173", "141", "183", "80", "147", "174", "160", "131", "165", "164", "135", "32", "177", "155", "289", "180", "121", "181", "80", "131", "179", "235", "133", "188", "81", "130", "170", "121", "174", "80", "123", "177", "148", "182", "292", "165", "86", "141", "160", "237", "155", "185", "173", "148", "235", "164", "122", "287", "162", "135", "86", "157", "156", "183", "141", "177", "237", "133", "179", "185", "126", "85", "295", "155", "182", "183", "131"); + private final CardRun uncommon = new CardRun(false, "1", "79", "2", "3", "158", "41", "223", "119", "83", "5", "197", "124", "161", "198", "199", "87", "127", "200", "46", "91", "167", "168", "129", "93", "51", "132", "134", "136", "14", "230", "171", "172", "17", "18", "140", "64", "234", "142", "99", "212", "100", "101", "176", "26", "143", "178", "29", "30", "239", "106", "144", "67", "31", "146", "240", "107", "215", "68", "108", "216", "35", "184", "217", "69", "70", "72", "73", "74", "190", "76", "191", "152", "112", "113", "37", "193", "194", "220", "221", "78"); + private final CardRun rare = new CardRun(false, "118", "82", "82", "222", "222", "196", "248", "248", "84", "84", "42", "42", "159", "159", "43", "43", "125", "163", "163", "249", "249", "250", "250", "128", "128", "90", "10", "47", "47", "166", "166", "201", "201", "95", "95", "202", "202", "229", "229", "169", "169", "56", "57", "203", "203", "204", "204", "98", "98", "205", "205", "19", "19", "206", "206", "138", "138", "207", "208", "208", "209", "209", "61", "61", "210", "210", "63", "63", "232", "232", "254", "254", "23", "233", "233", "211", "175", "24", "24", "213", "213", "104", "104", "105", "27", "257", "257", "145", "145", "214", "214", "258", "258", "33", "33", "34", "34", "149", "149", "150", "241", "241", "242", "244", "245", "245", "71", "11", "11", "227", "227", "255", "255", "256", "256", "259", "259", "186", "186", "189", "218", "218", "75", "75", "153", "153", "192", "192", "219", "219", "154", "154", "114", "114", "115", "38", "38", "246", "246", "195"); + private final CardRun land = new CardRun(false, "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "365", "366", "367", "368", "369"); + + private final BoosterStructure AABBBBCCCC = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, commonB, + commonC, commonC, commonC, commonC + ); + private final BoosterStructure AAABBBCCCC = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC, commonC, commonC, commonC + ); + private final BoosterStructure AAABBBBCCC = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC, commonC, commonC + ); + private final BoosterStructure U3 = new BoosterStructure(uncommon, uncommon, uncommon); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 2.67 A commons (270 / 101) + // 3.66 B commons (370 / 101) + // 3.66 C commons (370 / 101) + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AABBBBCCCC, AABBBBCCCC, AABBBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, AAABBBCCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, + AAABBBBCCC, AAABBBBCCC, AAABBBBCCC, AAABBBBCCC + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration(U3); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/Shadowmoor.java b/Mage.Sets/src/mage/sets/Shadowmoor.java index 4b4f24c5d4c..9ce4d513486 100644 --- a/Mage.Sets/src/mage/sets/Shadowmoor.java +++ b/Mage.Sets/src/mage/sets/Shadowmoor.java @@ -3,6 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; /** * @author North @@ -328,4 +335,62 @@ public final class Shadowmoor extends ExpansionSet { cards.add(new SetCardInfo("Wound Reflection", 81, Rarity.RARE, mage.cards.w.WoundReflection.class)); cards.add(new SetCardInfo("Zealous Guardian", 157, Rarity.COMMON, mage.cards.z.ZealousGuardian.class)); } + + @Override + public BoosterCollator createCollator() { + return new ShadowmoorCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/shm.html +// order not known for uncommon runs, but card division is +// pack distribution for uncommons not specified, assuming AAB ABB as in Onslaught +class ShadowmoorCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "238", "263", "230", "39", "133", "67", "102", "212", "249", "11", "30", "196", "82", "125", "242", "41", "182", "100", "113", "11", "178", "196", "22", "102", "238", "270", "232", "39", "173", "86", "133", "60", "148", "178", "194", "85", "125", "30", "22", "32", "182", "82", "212", "270", "242", "44", "67", "85", "235", "215", "230", "41", "60", "86", "113", "263", "232", "44", "194", "100", "235", "249", "148", "32", "173", "215"); + private final CardRun commonB = new CardRun(true, "20", "121", "218", "77", "177", "247", "149", "109", "136", "164", "195", "150", "239", "124", "213", "80", "177", "268", "138", "216", "66", "200", "139", "149", "259", "110", "152", "187", "166", "165", "20", "216", "101", "64", "45", "268", "3", "224", "195", "66", "139", "166", "152", "121", "101", "164", "150", "259", "138", "110", "218", "64", "124", "136", "239", "224", "213", "77", "45", "247", "3", "109", "200", "165", "80", "187"); + private final CardRun commonC = new CardRun(true, "265", "13", "52", "95", "115", "79", "27", "46", "88", "132", "59", "13", "246", "52", "105", "55", "10", "161", "193", "116", "56", "1", "54", "130", "265", "105", "240", "55", "27", "170", "207", "246", "185", "116", "10", "46", "95", "240", "79", "4", "170", "193", "115", "258", "59", "1", "161", "88", "130", "185", "4", "54", "207", "132", "56"); + private final CardRun commonD = new CardRun(true, "107", "78", "262", "24", "36", "175", "96", "51", "8", "118", "191", "217", "226", "6", "157", "71", "91", "15", "210", "36", "258", "84", "211", "155", "175", "250", "31", "91", "78", "118", "51", "6", "107", "120", "71", "262", "217", "76", "8", "48", "191", "211", "31", "24", "120", "96", "250", "15", "157", "76", "84", "210", "48", "155", "226"); + private final CardRun uncommonA = new CardRun(false, "28", "57", "83", "58", "61", "63", "34", "225", "35", "160", "112", "38", "206", "208", "253", "69", "94", "168", "9", "273", "255", "256", "274", "231", "171", "14", "275", "276", "17", "103", "127", "236", "19", "261", "128", "279", "106", "197", "267", "108"); + private final CardRun uncommonB = new CardRun(false, "2", "180", "29", "203", "62", "37", "205", "92", "114", "141", "117", "251", "189", "119", "254", "70", "167", "97", "229", "190", "43", "144", "192", "145", "18", "146", "147", "174", "241", "23", "264", "219", "220", "154", "199", "131", "266", "179", "269", "244"); + private final CardRun rare = new CardRun(false, "181", "137", "202", "5", "248", "158", "33", "87", "89", "183", "204", "184", "159", "111", "65", "90", "140", "186", "271", "227", "188", "93", "162", "163", "142", "272", "7", "252", "228", "68", "209", "40", "42", "98", "99", "122", "12", "169", "72", "143", "123", "277", "172", "233", "16", "234", "257", "73", "74", "126", "47", "75", "104", "260", "278", "237", "49", "214", "21", "50", "129", "198", "280", "151", "176", "153", "53", "25", "201", "221", "222", "243", "245", "26", "134", "281", "135", "156", "223", "81"); + + private final BoosterStructure AAAAAACCCCC = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonC, commonC, commonC, commonC, commonC + ); + private final BoosterStructure AAAAAADDDDD = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonD, commonD, commonD, commonD, commonD + ); + private final BoosterStructure BBBBBBCCCCC = new BoosterStructure( + commonB, commonB, commonB, commonB, commonB, commonB, + commonC, commonC, commonC, commonC, commonC + ); + private final BoosterStructure BBBBBBDDDDD = new BoosterStructure( + commonB, commonB, commonB, commonB, commonB, commonB, + commonD, commonD, commonD, commonD, commonD + ); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAAACCCCC, AAAAAADDDDD, BBBBBBCCCCC, BBBBBBDDDDD + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, ABB + ); + + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index 59285b500f5..392cf4d1165 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -6,6 +6,10 @@ import mage.cards.repository.CardInfo; import mage.constants.Rarity; import mage.constants.SetType; import mage.util.RandomUtil; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import java.util.ArrayList; import java.util.List; @@ -509,20 +513,92 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { } @Override - protected List findSpecialCardsByRarity(Rarity rarity) { - if (rarity == Rarity.RARE || rarity == Rarity.MYTHIC) { - return new ArrayList<>(); // Rare/Mythic DFCs are not special cards here - } else { - return super.findSpecialCardsByRarity(rarity); - // this accounts for 7 caves in the land slot, as well as the common/uncommon DFCs - } + public BoosterCollator createCollator() { + return new TheLostCavernsOfIxalanCollator(); } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/lci.html +// Using Japanese collation +// Using 1:2 ratio for individual dfc uncommon:common - as per shown sheet - differs from text description +// Using 5:3 ratio for individual nonbasic:basic - matches both text desciption and shown sheet +class TheLostCavernsOfIxalanCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "174", "248", "175", "166", "192", "206", "199", "205", "177", "138", "203", "210", "200", "192", "175", "206", "201", "250", "177", "174", "200", "205", "203", "210", "136", "202", "214", "182", "138", "218", "190", "248", "199", "166", "210", "192", "214", "174", "218", "182", "259", "190", "199", "248", "200", "203", "138", "177", "250", "202", "205", "166", "175", "259", "201", "136", "206", "250", "192", "203", "177", "248", "199", "174", "136", "205", "214", "166", "206", "201", "218", "202", "138", "200", "175", "182", "259", "190", "210", "192", "218", "166", "200", "174", "199", "250", "205", "190", "210", "201", "136", "206", "182", "138", "202", "175", "248", "177", "259", "214", "203", "201", "250", "190", "182", "218", "259", "202", "136", "214"); + private final CardRun commonB = new CardRun(true, "46", "140", "49", "131", "77", "149", "209", "75", "167", "84", "169", "66", "172", "71", "73", "163", "253", "68", "144", "57", "82", "131", "246", "45", "132", "64", "207", "151", "85", "154", "49", "140", "46", "168", "72", "70", "142", "53", "159", "69", "160", "85", "167", "207", "64", "151", "46", "132", "73", "163", "71", "75", "154", "253", "70", "160", "49", "82", "131", "246", "84", "142", "77", "209", "168", "68", "172", "69", "140", "53", "149", "72", "66", "169", "45", "159", "57", "144", "82", "168", "209", "66", "149", "84", "142", "85", "172", "69", "77", "160", "246", "68", "154", "57", "73", "144", "253", "45", "132", "75", "207", "169", "70", "163", "71", "159", "72", "151", "53", "64", "167"); + private final CardRun commonC = new CardRun(true, "116", "2", "99", "15", "90", "13", "118", "18", "101", "40", "130", "31", "106", "37", "110", "9", "117", "28", "89", "24", "100", "11", "95", "35", "114", "38", "104", "4", "109", "3", "90", "11", "255", "105", "15", "89", "31", "110", "40", "118", "30", "119", "13", "99", "28", "117", "2", "100", "7", "106", "3", "116", "38", "101", "18", "112", "24", "130", "27", "109", "4", "104", "9", "114", "35", "95", "37", "118", "31", "255", "104", "28", "106", "3", "130", "9", "90", "4", "99", "40", "119", "18", "105", "38", "116", "27", "109", "7", "100", "2", "114", "35", "95", "11", "110", "24", "112", "30", "117", "13", "101", "15", "89", "37", "119", "27", "255", "105", "30", "112", "7"); + private final CardRun dfc = new CardRun(false, "6", "146", "108", "155", "155", "60", "60", "195", "197", "197", "62", "233", "29", "29", "164", "36", "262", "128", "128", "217", "129", "83"); + private final CardRun uncommonA = new CardRun(false, "65", "65", "65", "74", "74", "74", "76", "76", "76", "78", "78", "405", "79", "79", "79", "86", "86", "86"); + private final CardRun uncommonB = new CardRun(true, "254", "33", "48", "23", "58", "286", "96", "251", "25", "111", "51", "5", "125", "8", "124", "186", "22", "102", "261", "87", "272", "107", "59", "120", "278", "19", "103", "42", "93", "21", "247", "33", "48", "111", "10", "50", "96", "23", "54", "286", "58", "17", "254", "213", "124", "8", "252", "59", "102", "25", "247", "51", "5", "125", "251", "107", "42", "93", "19", "103", "186", "22", "87", "278", "21", "261", "120", "272", "254", "17", "54", "252", "50", "10", "213", "48", "33", "58", "286", "96", "23", "51", "251", "124", "8", "125", "5", "102", "25", "59", "120", "247", "107", "186", "22", "272", "87", "261", "103", "278", "19", "93", "21", "111", "42", "252", "17", "54", "213", "10", "50"); + private final CardRun uncommonC = new CardRun(true, "245", "141", "301", "180", "230", "150", "220", "147", "194", "227", "148", "178", "139", "184", "260", "322", "263", "150", "187", "303", "198", "232", "183", "139", "270", "97", "173", "16", "187", "263", "198", "141", "298", "162", "232", "91", "173", "230", "143", "180", "170", "184", "242", "226", "147", "215", "320", "224", "187", "260", "236", "263", "143", "184", "242", "270", "245", "183", "147", "216", "236", "133", "224", "215", "310", "152", "302", "143", "331", "97", "178", "306", "323", "304", "198", "162", "329", "133", "312", "148", "215", "91", "170", "260", "179", "226", "194", "165", "16", "216", "97", "227", "152", "270", "150", "179", "139", "91", "170", "194", "165", "16", "141", "180", "152", "179", "220", "178", "173", "325", "148"); + private final CardRun rare = new CardRun(false, "219", "219", "1", "1", "88", "44", "44", "221", "221", "223", "223", "176", "176", "92", "134", "47", "47", "135", "135", "137", "137", "94", "94", "269", "249", "98", "98", "181", "181", "228", "228", "52", "52", "145", "145", "271", "271", "12", "12", "14", "14", "185", "229", "188", "188", "153", "153", "189", "191", "191", "156", "156", "193", "193", "196", "196", "231", "231", "61", "61", "20", "20", "157", "157", "63", "63", "256", "256", "234", "234", "158", "204", "67", "26", "237", "237", "161", "161", "113", "113", "208", "208", "115", "115", "238", "32", "280", "280", "281", "281", "282", "282", "283", "283", "284", "284", "258", "258", "239", "34", "34", "211", "211", "121", "121", "240", "241", "241", "122", "122", "123", "123", "80", "80", "285", "285", "126", "126", "264", "264", "127", "127", "222", "225", "225", "55", "56", "56", "257", "235", "212", "39", "39", "265", "265", "266", "266", "81", "81", "267", "267", "171", "171", "41", "41", "243", "244", "244", "43", "43"); + private final CardRun land = new CardRun(false, "268", "268", "268", "268", "268", "273", "273", "273", "273", "273", "274", "274", "274", "274", "274", "275", "275", "275", "275", "275", "276", "276", "276", "276", "276", "277", "277", "277", "277", "277", "279", "279", "279", "279", "279", "287", "287", "287", "288", "288", "288", "289", "289", "289", "290", "290", "290", "291", "291", "291", "393", "393", "393", "394", "394", "394", "395", "395", "395", "396", "396", "396", "397", "397", "397", "398", "398", "398", "399", "399", "399", "400", "400", "400", "401", "401", "401", "402", "402", "402"); + + private final BoosterStructure AABBBCCCC = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, + commonC, commonC, commonC, commonC + ); + private final BoosterStructure AABBBBCCC = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, commonB, + commonC, commonC, commonC + ); + private final BoosterStructure AAABBBCCC = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC, commonC, commonC + ); + private final BoosterStructure BCC = new BoosterStructure(uncommonB, uncommonC, uncommonC); + private final BoosterStructure BBC = new BoosterStructure(uncommonB, uncommonB, uncommonC); + private final BoosterStructure ABC = new BoosterStructure(uncommonA, uncommonB, uncommonC); + + private final BoosterStructure D1 = new BoosterStructure(dfc); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 2.06250 A commons (66 / 32) + // 3.46875 B commons (111 / 32) + // 3.46875 C commons (111 / 32) + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, + AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, + AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, + AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, + AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, + AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, AABBBBCCC, + AAABBBCCC, AAABBBCCC + ); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 0.2250 A uncommons (18 / 80) + // 1.3875 B uncommons (111 / 80) + // 1.3875 C uncommons (111 / 80) + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, + BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, + BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, BCC, + BCC, + BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, + BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, + BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, BBC, + BBC, + ABC, ABC, ABC, ABC, ABC, ABC, ABC, ABC, ABC, ABC, + ABC, ABC, ABC, ABC, ABC, ABC, ABC, ABC + ); + + private final RarityConfiguration dfcRuns = new RarityConfiguration(D1); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); @Override - protected void addDoubleFace(List booster) { - int rarityKey = RandomUtil.nextInt((5 * 3) + 12); // 5 C, 12 U - assume commons three times the frequency as uncommons - Rarity rarity = (rarityKey > 12) ? Rarity.COMMON : Rarity.UNCOMMON; - addToBooster(booster, getSpecialCardsByRarity(rarity)); + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(dfcRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java b/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java index e074d014b12..3e26046f505 100644 --- a/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java +++ b/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java @@ -20,20 +20,24 @@ public final class WildsOfEldraineCommander extends ExpansionSet { this.hasBasicLands = false; cards.add(new SetCardInfo("Ajani's Chosen", 59, Rarity.RARE, mage.cards.a.AjanisChosen.class)); - cards.add(new SetCardInfo("Alela, Cunning Conqueror", 3, Rarity.MYTHIC, mage.cards.a.AlelaCunningConqueror.class)); + cards.add(new SetCardInfo("Alela, Cunning Conqueror", 3, Rarity.MYTHIC, mage.cards.a.AlelaCunningConqueror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alela, Cunning Conqueror", 34, Rarity.MYTHIC, mage.cards.a.AlelaCunningConqueror.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ancestral Mask", 119, Rarity.COMMON, mage.cards.a.AncestralMask.class)); cards.add(new SetCardInfo("Angelic Destiny", 60, Rarity.MYTHIC, mage.cards.a.AngelicDestiny.class)); cards.add(new SetCardInfo("Arcane Denial", 84, Rarity.COMMON, mage.cards.a.ArcaneDenial.class)); cards.add(new SetCardInfo("Arcane Signet", 145, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); - cards.add(new SetCardInfo("Archmage of Echoes", 9, Rarity.RARE, mage.cards.a.ArchmageOfEchoes.class)); + cards.add(new SetCardInfo("Archmage of Echoes", 9, Rarity.RARE, mage.cards.a.ArchmageOfEchoes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archmage of Echoes", 45, Rarity.RARE, mage.cards.a.ArchmageOfEchoes.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Archon of Sun's Grace", 61, Rarity.RARE, mage.cards.a.ArchonOfSunsGrace.class)); cards.add(new SetCardInfo("Aura Gnarlid", 120, Rarity.COMMON, mage.cards.a.AuraGnarlid.class)); cards.add(new SetCardInfo("Austere Command", 62, Rarity.RARE, mage.cards.a.AustereCommand.class)); cards.add(new SetCardInfo("Bear Umbra", 121, Rarity.RARE, mage.cards.b.BearUmbra.class)); - cards.add(new SetCardInfo("Blightwing Bandit", 13, Rarity.RARE, mage.cards.b.BlightwingBandit.class)); + cards.add(new SetCardInfo("Blightwing Bandit", 13, Rarity.RARE, mage.cards.b.BlightwingBandit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blightwing Bandit", 49, Rarity.RARE, mage.cards.b.BlightwingBandit.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bojuka Bog", 152, Rarity.COMMON, mage.cards.b.BojukaBog.class)); cards.add(new SetCardInfo("Brazen Borrower", 85, Rarity.MYTHIC, mage.cards.b.BrazenBorrower.class)); - cards.add(new SetCardInfo("Brenard, Ginger Sculptor", 27, Rarity.MYTHIC, mage.cards.b.BrenardGingerSculptor.class)); + cards.add(new SetCardInfo("Brenard, Ginger Sculptor", 27, Rarity.MYTHIC, mage.cards.b.BrenardGingerSculptor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brenard, Ginger Sculptor", 35, Rarity.MYTHIC, mage.cards.b.BrenardGingerSculptor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Canopy Vista", 153, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Careful Cultivation", 122, Rarity.COMMON, mage.cards.c.CarefulCultivation.class)); cards.add(new SetCardInfo("Castle Ardenvale", 154, Rarity.RARE, mage.cards.c.CastleArdenvale.class)); @@ -42,11 +46,16 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Cloud of Faeries", 86, Rarity.COMMON, mage.cards.c.CloudOfFaeries.class)); cards.add(new SetCardInfo("Command Tower", 156, Rarity.COMMON, mage.cards.c.CommandTower.class)); cards.add(new SetCardInfo("Consider", 87, Rarity.COMMON, mage.cards.c.Consider.class)); - cards.add(new SetCardInfo("Court of Ardenvale", 21, Rarity.RARE, mage.cards.c.CourtOfArdenvale.class)); - cards.add(new SetCardInfo("Court of Embereth", 24, Rarity.RARE, mage.cards.c.CourtOfEmbereth.class)); - cards.add(new SetCardInfo("Court of Garenbrig", 25, Rarity.RARE, mage.cards.c.CourtOfGarenbrig.class)); - cards.add(new SetCardInfo("Court of Locthwain", 23, Rarity.RARE, mage.cards.c.CourtOfLocthwain.class)); - cards.add(new SetCardInfo("Court of Vantress", 22, Rarity.RARE, mage.cards.c.CourtOfVantress.class)); + cards.add(new SetCardInfo("Court of Ardenvale", 21, Rarity.RARE, mage.cards.c.CourtOfArdenvale.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Ardenvale", 29, Rarity.RARE, mage.cards.c.CourtOfArdenvale.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Embereth", 24, Rarity.RARE, mage.cards.c.CourtOfEmbereth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Embereth", 32, Rarity.RARE, mage.cards.c.CourtOfEmbereth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Garenbrig", 25, Rarity.RARE, mage.cards.c.CourtOfGarenbrig.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Garenbrig", 33, Rarity.RARE, mage.cards.c.CourtOfGarenbrig.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Locthwain", 23, Rarity.RARE, mage.cards.c.CourtOfLocthwain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Locthwain", 31, Rarity.RARE, mage.cards.c.CourtOfLocthwain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Vantress", 22, Rarity.RARE, mage.cards.c.CourtOfVantress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Court of Vantress", 30, Rarity.RARE, mage.cards.c.CourtOfVantress.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Danitha Capashen, Paragon", 64, Rarity.UNCOMMON, mage.cards.d.DanithaCapashenParagon.class)); cards.add(new SetCardInfo("Darkwater Catacombs", 157, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); cards.add(new SetCardInfo("Daybreak Coronet", 65, Rarity.RARE, mage.cards.d.DaybreakCoronet.class)); @@ -57,12 +66,15 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Distant Melody", 89, Rarity.COMMON, mage.cards.d.DistantMelody.class)); cards.add(new SetCardInfo("Eidolon of Blossoms", 124, Rarity.RARE, mage.cards.e.EidolonOfBlossoms.class)); cards.add(new SetCardInfo("Eidolon of Countless Battles", 66, Rarity.RARE, mage.cards.e.EidolonOfCountlessBattles.class)); - cards.add(new SetCardInfo("Ellivere of the Wild Court", 2, Rarity.MYTHIC, mage.cards.e.EllivereOfTheWildCourt.class)); + cards.add(new SetCardInfo("Ellivere of the Wild Court", 2, Rarity.MYTHIC, mage.cards.e.EllivereOfTheWildCourt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ellivere of the Wild Court", 36, Rarity.MYTHIC, mage.cards.e.EllivereOfTheWildCourt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ellivere of the Wild Court", 57, Rarity.MYTHIC, mage.cards.e.EllivereOfTheWildCourt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Enchantress's Presence", 125, Rarity.RARE, mage.cards.e.EnchantresssPresence.class)); cards.add(new SetCardInfo("Ethereal Armor", 67, Rarity.COMMON, mage.cards.e.EtherealArmor.class)); cards.add(new SetCardInfo("Exotic Orchard", 159, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Fact or Fiction", 90, Rarity.UNCOMMON, mage.cards.f.FactOrFiction.class)); - cards.add(new SetCardInfo("Faerie Bladecrafter", 14, Rarity.RARE, mage.cards.f.FaerieBladecrafter.class)); + cards.add(new SetCardInfo("Faerie Bladecrafter", 14, Rarity.RARE, mage.cards.f.FaerieBladecrafter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Faerie Bladecrafter", 50, Rarity.RARE, mage.cards.f.FaerieBladecrafter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Faerie Conclave", 160, Rarity.UNCOMMON, mage.cards.f.FaerieConclave.class)); cards.add(new SetCardInfo("Faerie Formation", 91, Rarity.RARE, mage.cards.f.FaerieFormation.class)); cards.add(new SetCardInfo("Faerie Seer", 92, Rarity.COMMON, mage.cards.f.FaerieSeer.class)); @@ -71,10 +83,12 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Fortified Village", 161, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); cards.add(new SetCardInfo("Frantic Search", 93, Rarity.COMMON, mage.cards.f.FranticSearch.class)); cards.add(new SetCardInfo("Generous Gift", 68, Rarity.UNCOMMON, mage.cards.g.GenerousGift.class)); - cards.add(new SetCardInfo("Giant Inheritance", 17, Rarity.RARE, mage.cards.g.GiantInheritance.class)); + cards.add(new SetCardInfo("Giant Inheritance", 17, Rarity.RARE, mage.cards.g.GiantInheritance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Giant Inheritance", 53, Rarity.RARE, mage.cards.g.GiantInheritance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glen Elendra Archmage", 94, Rarity.RARE, mage.cards.g.GlenElendraArchmage.class)); cards.add(new SetCardInfo("Glen Elendra Liege", 138, Rarity.RARE, mage.cards.g.GlenElendraLiege.class)); - cards.add(new SetCardInfo("Gylwain, Casting Director", 4, Rarity.MYTHIC, mage.cards.g.GylwainCastingDirector.class)); + cards.add(new SetCardInfo("Gylwain, Casting Director", 4, Rarity.MYTHIC, mage.cards.g.GylwainCastingDirector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gylwain, Casting Director", 37, Rarity.MYTHIC, mage.cards.g.GylwainCastingDirector.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hall of Heliod's Generosity", 162, Rarity.RARE, mage.cards.h.HallOfHeliodsGenerosity.class)); cards.add(new SetCardInfo("Halo Forager", 139, Rarity.UNCOMMON, mage.cards.h.HaloForager.class)); cards.add(new SetCardInfo("Hullbreaker Horror", 95, Rarity.RARE, mage.cards.h.HullbreakerHorror.class)); @@ -85,25 +99,33 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Keep Watch", 98, Rarity.COMMON, mage.cards.k.KeepWatch.class)); cards.add(new SetCardInfo("Kenrith's Transformation", 128, Rarity.UNCOMMON, mage.cards.k.KenrithsTransformation.class)); cards.add(new SetCardInfo("Kindred Dominance", 113, Rarity.RARE, mage.cards.k.KindredDominance.class)); - cards.add(new SetCardInfo("Knickknack Ouphe", 18, Rarity.RARE, mage.cards.k.KnickknackOuphe.class)); + cards.add(new SetCardInfo("Knickknack Ouphe", 18, Rarity.RARE, mage.cards.k.KnickknackOuphe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knickknack Ouphe", 54, Rarity.RARE, mage.cards.k.KnickknackOuphe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kor Spiritdancer", 69, Rarity.RARE, mage.cards.k.KorSpiritdancer.class)); - cards.add(new SetCardInfo("Korvold, Gleeful Glutton", 26, Rarity.MYTHIC, mage.cards.k.KorvoldGleefulGlutton.class)); + cards.add(new SetCardInfo("Korvold, Gleeful Glutton", 26, Rarity.MYTHIC, mage.cards.k.KorvoldGleefulGlutton.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Korvold, Gleeful Glutton", 38, Rarity.MYTHIC, mage.cards.k.KorvoldGleefulGlutton.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Krosan Verge", 163, Rarity.UNCOMMON, mage.cards.k.KrosanVerge.class)); - cards.add(new SetCardInfo("Liberated Livestock", 5, Rarity.RARE, mage.cards.l.LiberatedLivestock.class)); - cards.add(new SetCardInfo("Loamcrafter Faun", 19, Rarity.RARE, mage.cards.l.LoamcrafterFaun.class)); - cards.add(new SetCardInfo("Malleable Impostor", 10, Rarity.RARE, mage.cards.m.MalleableImpostor.class)); + cards.add(new SetCardInfo("Liberated Livestock", 5, Rarity.RARE, mage.cards.l.LiberatedLivestock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Liberated Livestock", 41, Rarity.RARE, mage.cards.l.LiberatedLivestock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loamcrafter Faun", 19, Rarity.RARE, mage.cards.l.LoamcrafterFaun.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loamcrafter Faun", 55, Rarity.RARE, mage.cards.l.LoamcrafterFaun.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malleable Impostor", 10, Rarity.RARE, mage.cards.m.MalleableImpostor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malleable Impostor", 46, Rarity.RARE, mage.cards.m.MalleableImpostor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mantle of the Ancients", 70, Rarity.RARE, mage.cards.m.MantleOfTheAncients.class)); cards.add(new SetCardInfo("Midnight Clock", 99, Rarity.RARE, mage.cards.m.MidnightClock.class)); cards.add(new SetCardInfo("Mind Stone", 148, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); - cards.add(new SetCardInfo("Misleading Signpost", 11, Rarity.RARE, mage.cards.m.MisleadingSignpost.class)); + cards.add(new SetCardInfo("Misleading Signpost", 11, Rarity.RARE, mage.cards.m.MisleadingSignpost.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Misleading Signpost", 47, Rarity.RARE, mage.cards.m.MisleadingSignpost.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Myriad Landscape", 164, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); - cards.add(new SetCardInfo("Nettling Nuisance", 15, Rarity.RARE, mage.cards.n.NettlingNuisance.class)); + cards.add(new SetCardInfo("Nettling Nuisance", 15, Rarity.RARE, mage.cards.n.NettlingNuisance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nettling Nuisance", 51, Rarity.RARE, mage.cards.n.NettlingNuisance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nightmare Unmaking", 114, Rarity.RARE, mage.cards.n.NightmareUnmaking.class)); cards.add(new SetCardInfo("Nightveil Sprite", 100, Rarity.UNCOMMON, mage.cards.n.NightveilSprite.class)); cards.add(new SetCardInfo("Nymris, Oona's Trickster", 141, Rarity.RARE, mage.cards.n.NymrisOonasTrickster.class)); cards.add(new SetCardInfo("Oona, Queen of the Fae", 142, Rarity.RARE, mage.cards.o.OonaQueenOfTheFae.class)); cards.add(new SetCardInfo("Opt", 101, Rarity.COMMON, mage.cards.o.Opt.class)); - cards.add(new SetCardInfo("Ox Drover", 6, Rarity.RARE, mage.cards.o.OxDrover.class)); + cards.add(new SetCardInfo("Ox Drover", 6, Rarity.RARE, mage.cards.o.OxDrover.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ox Drover", 42, Rarity.RARE, mage.cards.o.OxDrover.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Paradise Druid", 129, Rarity.UNCOMMON, mage.cards.p.ParadiseDruid.class)); cards.add(new SetCardInfo("Path of Ancestry", 165, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); cards.add(new SetCardInfo("Perplexing Test", 102, Rarity.RARE, mage.cards.p.PerplexingTest.class)); @@ -125,13 +147,15 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Scion of Oona", 109, Rarity.RARE, mage.cards.s.ScionOfOona.class)); cards.add(new SetCardInfo("Secluded Glen", 166, Rarity.RARE, mage.cards.s.SecludedGlen.class)); cards.add(new SetCardInfo("Setessan Champion", 132, Rarity.RARE, mage.cards.s.SetessanChampion.class)); - cards.add(new SetCardInfo("Shadow Puppeteers", 12, Rarity.RARE, mage.cards.s.ShadowPuppeteers.class)); + cards.add(new SetCardInfo("Shadow Puppeteers", 12, Rarity.RARE, mage.cards.s.ShadowPuppeteers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadow Puppeteers", 48, Rarity.RARE, mage.cards.s.ShadowPuppeteers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shalai, Voice of Plenty", 74, Rarity.RARE, mage.cards.s.ShalaiVoiceOfPlenty.class)); cards.add(new SetCardInfo("Siona, Captain of the Pyleas", 144, Rarity.UNCOMMON, mage.cards.s.SionaCaptainOfThePyleas.class)); cards.add(new SetCardInfo("Snake Umbra", 133, Rarity.UNCOMMON, mage.cards.s.SnakeUmbra.class)); cards.add(new SetCardInfo("Snap", 110, Rarity.COMMON, mage.cards.s.Snap.class)); cards.add(new SetCardInfo("Sol Ring", 149, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); - cards.add(new SetCardInfo("Songbirds' Blessing", 7, Rarity.RARE, mage.cards.s.SongbirdsBlessing.class)); + cards.add(new SetCardInfo("Songbirds' Blessing", 7, Rarity.RARE, mage.cards.s.SongbirdsBlessing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Songbirds' Blessing", 43, Rarity.RARE, mage.cards.s.SongbirdsBlessing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sower of Temptation", 111, Rarity.RARE, mage.cards.s.SowerOfTemptation.class)); cards.add(new SetCardInfo("Spectral Steel", 75, Rarity.UNCOMMON, mage.cards.s.SpectralSteel.class)); cards.add(new SetCardInfo("Starfield Mystic", 76, Rarity.RARE, mage.cards.s.StarfieldMystic.class)); @@ -142,19 +166,26 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Sylvan Ranger", 134, Rarity.COMMON, mage.cards.s.SylvanRanger.class)); cards.add(new SetCardInfo("Tainted Isle", 169, Rarity.UNCOMMON, mage.cards.t.TaintedIsle.class)); cards.add(new SetCardInfo("Talisman of Dominance", 150, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); - cards.add(new SetCardInfo("Tegwyll, Duke of Splendor", 1, Rarity.MYTHIC, mage.cards.t.TegwyllDukeOfSplendor.class)); + cards.add(new SetCardInfo("Tegwyll, Duke of Splendor", 1, Rarity.MYTHIC, mage.cards.t.TegwyllDukeOfSplendor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tegwyll, Duke of Splendor", 39, Rarity.MYTHIC, mage.cards.t.TegwyllDukeOfSplendor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tegwyll, Duke of Splendor", 58, Rarity.MYTHIC, mage.cards.t.TegwyllDukeOfSplendor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tegwyll's Scouring", 16, Rarity.RARE, mage.cards.t.TegwyllsScouring.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tegwyll's Scouring", 52, Rarity.RARE, mage.cards.t.TegwyllsScouring.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Temple of Deceit", 170, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); cards.add(new SetCardInfo("Temple of Plenty", 171, Rarity.RARE, mage.cards.t.TempleOfPlenty.class)); cards.add(new SetCardInfo("Temple of the False God", 172, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); cards.add(new SetCardInfo("Theoretical Duplication", 112, Rarity.RARE, mage.cards.t.TheoreticalDuplication.class)); cards.add(new SetCardInfo("Thrilling Encore", 118, Rarity.RARE, mage.cards.t.ThrillingEncore.class)); - cards.add(new SetCardInfo("Throne of Eldraine", 28, Rarity.RARE, mage.cards.t.ThroneOfEldraine.class)); - cards.add(new SetCardInfo("Timber Paladin", 20, Rarity.RARE, mage.cards.t.TimberPaladin.class)); + cards.add(new SetCardInfo("Throne of Eldraine", 28, Rarity.RARE, mage.cards.t.ThroneOfEldraine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Throne of Eldraine", 40, Rarity.RARE, mage.cards.t.ThroneOfEldraine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Timber Paladin", 20, Rarity.RARE, mage.cards.t.TimberPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Timber Paladin", 56, Rarity.RARE, mage.cards.t.TimberPaladin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Timely Ward", 79, Rarity.RARE, mage.cards.t.TimelyWard.class)); cards.add(new SetCardInfo("Tithe Taker", 80, Rarity.RARE, mage.cards.t.TitheTaker.class)); cards.add(new SetCardInfo("Transcendent Envoy", 81, Rarity.COMMON, mage.cards.t.TranscendentEnvoy.class)); cards.add(new SetCardInfo("Umbra Mystic", 82, Rarity.RARE, mage.cards.u.UmbraMystic.class)); - cards.add(new SetCardInfo("Unfinished Business", 8, Rarity.RARE, mage.cards.u.UnfinishedBusiness.class)); + cards.add(new SetCardInfo("Unfinished Business", 8, Rarity.RARE, mage.cards.u.UnfinishedBusiness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unfinished Business", 44, Rarity.RARE, mage.cards.u.UnfinishedBusiness.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Utopia Sprawl", 135, Rarity.COMMON, mage.cards.u.UtopiaSprawl.class)); cards.add(new SetCardInfo("Verdant Embrace", 136, Rarity.RARE, mage.cards.v.VerdantEmbrace.class)); cards.add(new SetCardInfo("Vitu-Ghazi, the City-Tree", 173, Rarity.UNCOMMON, mage.cards.v.VituGhaziTheCityTree.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index 9c13397692f..4c01474b53c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -22,7 +22,7 @@ import java.util.Set; import java.util.UUID; /** - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class CopySpellTest extends CardTestPlayerBase { @@ -905,4 +905,56 @@ public class CopySpellTest extends CardTestPlayerBase { Assert.fail(infoPrefix + " - " + "cards must have same zone and zcc: " + zcc1 + " - " + zone1 + " != " + zcc2 + " - " + zone2); } } + + /** + * Bug: If Swan Song is used to counter a copied spell, no tokens are created #12883 + */ + @Test + public void test_LKI() { + // Counter target enchantment, instant, or sorcery spell. + // Its controller creates a 2/2 blue Bird creature token with flying. + addCard(Zone.HAND, playerA, "Swan Song", 1); // {U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + // + // Until end of turn, whenever a player casts an instant or sorcery spell, that player copies it and + // may choose new targets for the copy. + addCard(Zone.HAND, playerA, "Bonus Round", 1); // {1}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); // {R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Augmenting Automaton", 1); + + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, "Bird Token", 0); + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerB, "Grizzly Bears", 1); + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerB, "Augmenting Automaton", 1); + + // prepare copy effect + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bonus Round"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // cast and duplicate bolt + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + addTarget(playerA, "Grizzly Bears"); // original target + setChoice(playerA, true); // use new target + addTarget(playerA, "Augmenting Automaton"); // copy target + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); // resolve copy trigger + checkStackObject("on copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", 2); + + // counter copy and save Augmenting Automaton + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swan Song", "Lightning Bolt[only copy]", "Lightning Bolt", StackClause.WHILE_COPY_ON_STACK); + setChoice(playerA, false); // no change target for duplicated counter spell (non-relevant here) + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, "Bird Token", 1); + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerB, "Grizzly Bears", 0); + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerB, "Augmenting Automaton", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/TheMindskinnerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/TheMindskinnerTest.java new file mode 100644 index 00000000000..ff076cfbc91 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/TheMindskinnerTest.java @@ -0,0 +1,39 @@ +package org.mage.test.cards.replacement.prevent; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class TheMindskinnerTest extends CardTestPlayerBase { + + private static final String mindskinner = "The Mindskinner"; // 10/1 can't be blocked + // If a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards. + + private static final String piker = "Goblin Piker"; // 2/1 + + private static final String bolt = "Lightning Bolt"; + + @Test + public void testPreventionAndMill() { + addCard(Zone.BATTLEFIELD, playerA, mindskinner); + addCard(Zone.BATTLEFIELD, playerA, piker); + addCard(Zone.HAND, playerA, bolt); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + + attack(1, playerA, piker, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerA, 1); // bolt + assertGraveyardCount(playerB, 5); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index b9c5d2c9e1a..57fc6ec6d13 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1291,7 +1291,7 @@ public class TestPlayer implements Player { private void printStack(Game game) { System.out.println("Stack objects: " + game.getStack().size()); game.getStack().forEach(stack -> { - System.out.println(stack.getStackAbility().toString()); + System.out.println(stack.getStackAbility().toString() + (stack.isCopy() ? " [copy]" : "")); }); } diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java index a1272a2f066..97c8b2acf2b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java @@ -514,8 +514,9 @@ public class BoosterGenerationTest extends MageTestPlayerBase { @Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081 @Test public void test_CollectBoosterStats() { - ExpansionSet setToAnalyse = ModernHorizons3.getInstance(); - int openBoosters = 10000; + ExpansionSet setToAnalyse = NewPhyrexia.getInstance(); + // Takes about a minute for 100,000 boosters + int openBoosters = 100000; Map resRatio = new HashMap<>(); int totalCards = 0; @@ -523,11 +524,23 @@ public class BoosterGenerationTest extends MageTestPlayerBase { List booster = setToAnalyse.createBooster(); totalCards += booster.size(); booster.forEach(card -> { - String code = String.format("%s %s %s", card.getExpansionSetCode(), card.getRarity().getCode(), card.getName()); + String code = String.format("%s %s %s", card.getExpansionSetCode(), card.getRarity().toString().charAt(0), card.getName()); resRatio.putIfAbsent(code, 0); resRatio.computeIfPresent(code, (u, count) -> count + 1); }); } + System.out.println(setToAnalyse.getName() + " - boosters opened: " + openBoosters + ". Found cards: " + totalCards + "\n"); + for (char rarity : Arrays.asList('C', 'U', 'R', 'M', 'S', 'L', 'B')) { + List rarityCounts = resRatio.entrySet().stream() + .filter(e -> e.getKey().charAt(4) == rarity) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + if (!rarityCounts.isEmpty()) { + System.out.println(rarity + String.format(": %s unique, min %s, max %s, total %s", + rarityCounts.size(), Collections.min(rarityCounts), Collections.max(rarityCounts), + rarityCounts.stream().mapToInt(x -> x).sum())); + } + } List info = resRatio.entrySet().stream() .sorted((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue())) .map(e -> String.format("%s: %d", @@ -535,8 +548,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase { e.getValue() )) .collect(Collectors.toList()); - System.out.println(setToAnalyse.getName() + " - boosters opened: " + openBoosters + ". Found cards: " + totalCards + "\n" - + String.join("\n", info)); + System.out.println("\n" + String.join("\n", info)); } @Ignore // debug only diff --git a/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java b/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java index 7fc7d21b35c..0ba8afb443a 100644 --- a/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java @@ -1,6 +1,7 @@ package mage.abilities.common; import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.cards.Card; @@ -14,14 +15,21 @@ import mage.util.CardUtil; */ public class PayMoreToCastAsThoughtItHadFlashAbility extends SpellAbility { - private final ManaCosts costsToAdd; + private final Cost costsToAdd; - public PayMoreToCastAsThoughtItHadFlashAbility(Card card, ManaCosts costsToAdd) { - super(card.getSpellAbility().getManaCosts().copy(), card.getName() + " as though it had flash", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); + public PayMoreToCastAsThoughtItHadFlashAbility(Card card, Cost costsToAdd) { + super(card.getSpellAbility().getManaCosts().copy(), card.getName(), Zone.HAND, SpellAbilityType.BASE_ALTERNATE); this.costsToAdd = costsToAdd; this.timing = TimingRule.INSTANT; this.setRuleAtTheTop(true); - CardUtil.increaseCost(this, costsToAdd); + + if(costsToAdd instanceof ManaCosts) { + ManaCosts manaCosts = (ManaCosts) costsToAdd; + CardUtil.increaseCost(this, manaCosts); + } + else { + this.addCost(costsToAdd); + } } protected PayMoreToCastAsThoughtItHadFlashAbility(final PayMoreToCastAsThoughtItHadFlashAbility ability) { @@ -38,10 +46,13 @@ public class PayMoreToCastAsThoughtItHadFlashAbility extends SpellAbility { public String getRule(boolean all) { return getRule(); } - @Override public String getRule() { - return "You may cast {this} as though it had flash if you pay " + costsToAdd.getText() + " more to cast it. (You may cast it any time you could cast an instant.)"; + if (costsToAdd instanceof ManaCosts) { + return "You may cast {this} as though it had flash if you pay " + costsToAdd.getText() + " more to cast it. (You may cast it any time you could cast an instant.)"; + } else { + return "You may cast {this} as though it had flash by " + costsToAdd.getText() + " in addition to paying its other costs."; + } } -} +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/SpellCastAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SpellCastAllTriggeredAbility.java index b14269dd126..957b9d9038e 100644 --- a/Mage/src/main/java/mage/abilities/common/SpellCastAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SpellCastAllTriggeredAbility.java @@ -60,7 +60,8 @@ public class SpellCastAllTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Spell spell = game.getStack().getSpell(event.getTargetId()); - if (!filter.match(spell, getControllerId(), this, game)) { + if (!filter.match(spell, getControllerId(), this, game) + || !game.getState().getPlayersInRange(getControllerId(), game, false).contains(event.getPlayerId())) { return false; } getEffects().setValue("spellCast", spell); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index c1456292c37..3b0cddcbf60 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -74,6 +74,8 @@ public interface ContinuousEffect extends Effect { boolean isYourNextEndStep(Game game); + boolean isTheNextEndStep(Game game); + boolean isYourNextUpkeepStep(Game game); @Override diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 156e9400251..901dc44d102 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -56,9 +56,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu // until your next turn or until end of your next turn private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability) + private UUID activePlayerId; // Player whose turn the effect started on private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active private int effectStartingOnTurn = 0; // turn the effect started - private int effectStartingEndStep = 0; + private int effectControllerStartingEndStep = 0; + private int effectActivePlayerStartingEndStep = 0; private int nextTurnNumber = Integer.MAX_VALUE; // effect is waiting for a step during your next turn, we store it if found. // set to the turn number on your next turn. private int effectStartingStepNum = 0; // Some continuous are waiting for the next step of a kind. @@ -93,7 +95,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.startingControllerId = effect.startingControllerId; this.startingTurnWasActive = effect.startingTurnWasActive; this.effectStartingOnTurn = effect.effectStartingOnTurn; - this.effectStartingEndStep = effect.effectStartingEndStep; + this.effectControllerStartingEndStep = effect.effectControllerStartingEndStep; this.dependencyTypes = effect.dependencyTypes; this.dependendToTypes = effect.dependendToTypes; this.characterDefining = effect.characterDefining; @@ -251,10 +253,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) { this.startingControllerId = startingController; + this.activePlayerId = activePlayerId; this.startingTurnWasActive = activePlayerId != null && activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too this.effectStartingOnTurn = game.getTurnNum(); - this.effectStartingEndStep = EndStepCountWatcher.getCount(startingController, game); + this.effectControllerStartingEndStep = EndStepCountWatcher.getCount(startingController, game); + this.effectActivePlayerStartingEndStep = EndStepCountWatcher.getCount(activePlayerId, game); this.effectStartingStepNum = game.getState().getStepNum(); } @@ -266,7 +270,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public boolean isYourNextEndStep(Game game) { - return EndStepCountWatcher.getCount(startingControllerId, game) > effectStartingEndStep; + return EndStepCountWatcher.getCount(startingControllerId, game) > effectControllerStartingEndStep; + } + + @Override + public boolean isTheNextEndStep(Game game) { + return EndStepCountWatcher.getCount(activePlayerId, game) > effectActivePlayerStartingEndStep; } public boolean isEndCombatOfYourNextTurn(Game game) { @@ -298,6 +307,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu case UntilYourNextTurn: case UntilEndOfYourNextTurn: case UntilYourNextEndStep: + case UntilTheNextEndStep: case UntilEndCombatOfYourNextTurn: case UntilYourNextUpkeepStep: break; @@ -342,6 +352,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return this.isYourNextEndStep(game); } break; + case UntilTheNextEndStep: + if (player != null && player.isInGame()) { + return this.isTheNextEndStep(game); + } case UntilEndCombatOfYourNextTurn: if (player != null && player.isInGame()) { return this.isEndCombatOfYourNextTurn(game); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java index c8707a7289f..a75c476971b 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java @@ -156,6 +156,7 @@ public class ContinuousEffectsList extends ArrayList case UntilEndOfYourNextTurn: case UntilEndCombatOfYourNextTurn: case UntilYourNextEndStep: + case UntilTheNextEndStep: case UntilYourNextUpkeepStep: // until your turn effects continue until real turn reached, their used it's own inactive method // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents diff --git a/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java b/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java index dd89bb9ad8f..40f0d879b58 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java @@ -18,21 +18,28 @@ public class IfAbilityHasResolvedXTimesEffect extends OneShotEffect { private final int resolutionNumber; private final Effects effects; + private final boolean orMore; public IfAbilityHasResolvedXTimesEffect(int resolutionNumber, Effect effect) { this(effect.getOutcome(), resolutionNumber, effect); } public IfAbilityHasResolvedXTimesEffect(Outcome outcome, int resolutionNumber, Effect... effects) { + this(outcome, resolutionNumber, false, effects); + } + + public IfAbilityHasResolvedXTimesEffect(Outcome outcome, int resolutionNumber, boolean orMore, Effect... effects) { super(outcome); this.resolutionNumber = resolutionNumber; this.effects = new Effects(effects); + this.orMore = orMore; } private IfAbilityHasResolvedXTimesEffect(final IfAbilityHasResolvedXTimesEffect effect) { super(effect); this.resolutionNumber = effect.resolutionNumber; this.effects = effect.effects.copy(); + this.orMore = effect.orMore; } @Override @@ -42,7 +49,8 @@ public class IfAbilityHasResolvedXTimesEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (AbilityResolvedWatcher.getResolutionCount(game, source) != resolutionNumber) { + int resolutionCount = AbilityResolvedWatcher.getResolutionCount(game, source); + if (resolutionCount < resolutionNumber || (!orMore && resolutionCount > resolutionNumber)) { return false; } boolean result = false; @@ -62,6 +70,9 @@ public class IfAbilityHasResolvedXTimesEffect extends OneShotEffect { if (staticText != null && !staticText.isEmpty()) { return staticText; } + if (orMore) { + return "otherwise, " + effects.getText(mode); + } return "if this is the " + CardUtil.numberToOrdinalText(resolutionNumber) + " time this ability has resolved this turn, " + effects.getText(mode); } diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index c1678cd031c..1649aca17b9 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -112,7 +112,8 @@ public class EmergeAbility extends SpellAbility { Permanent creature = game.getPermanent(target.getFirstTarget()); if (creature != null) { CardUtil.reduceCost(this, creature.getManaValue()); - if (super.activate(game, allowedIdentifiers, false)) { + boolean reducedToZero = this.getManaCostsToPay().isEmpty(); + if (super.activate(game, allowedIdentifiers, reducedToZero)) { MageObjectReference mor = new MageObjectReference(creature, game); if (creature.sacrifice(this, game)) { this.setCostsTag(EMERGE_ACTIVATION_CREATURE_REFERENCE, mor); //Can access with LKI afterwards diff --git a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java index f6ae95d8ef3..5e85e5d211d 100644 --- a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java +++ b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java @@ -20,6 +20,7 @@ public class CardNameUtil { .replace("û", "u") .replace("í", "i") .replace("ï", "i") + .replace("î", "i") .replace("â", "a") .replace("á", "a") .replace("à", "a") diff --git a/Mage/src/main/java/mage/cards/repository/TokenRepository.java b/Mage/src/main/java/mage/cards/repository/TokenRepository.java index eeed1273346..ed25e535d39 100644 --- a/Mage/src/main/java/mage/cards/repository/TokenRepository.java +++ b/Mage/src/main/java/mage/cards/repository/TokenRepository.java @@ -33,6 +33,7 @@ public enum TokenRepository { public static final String XMAGE_IMAGE_NAME_NIGHT = "Night"; public static final String XMAGE_IMAGE_NAME_THE_MONARCH = "The Monarch"; public static final String XMAGE_IMAGE_NAME_RADIATION = "Radiation"; + public static final String XMAGE_IMAGE_NAME_THE_RING = "The Ring"; public static final String XMAGE_IMAGE_NAME_HELPER_EMBLEM = "Helper Emblem"; private static final Logger logger = Logger.getLogger(TokenRepository.class); @@ -306,6 +307,9 @@ public enum TokenRepository { // Radiation (for trigger) res.add(createXmageToken(XMAGE_IMAGE_NAME_RADIATION, 1, "https://api.scryfall.com/cards/tpip/22/en?format=image")); + // The Ring + res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_RING, 1, "https://api.scryfall.com/cards/tltr/H13/en?format=image")); + // Helper emblem (for global card hints) // use backface for it res.add(createXmageToken(XMAGE_IMAGE_NAME_HELPER_EMBLEM, 1, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg")); diff --git a/Mage/src/main/java/mage/constants/Duration.java b/Mage/src/main/java/mage/constants/Duration.java index dd14431a6b5..da37dd1e973 100644 --- a/Mage/src/main/java/mage/constants/Duration.java +++ b/Mage/src/main/java/mage/constants/Duration.java @@ -13,6 +13,7 @@ public enum Duration { EndOfTurn("until end of turn", true, true), UntilYourNextTurn("until your next turn", true, true), UntilYourNextEndStep("until your next end step", true, true), + UntilTheNextEndStep("until your next end step", true, true), UntilEndCombatOfYourNextTurn("until end of combat on your next turn", true, true), UntilYourNextUpkeepStep("until your next upkeep", true, true), UntilEndOfYourNextTurn("until the end of your next turn", true, true), diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 92a1f4d5093..400daff1cd7 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -583,11 +583,10 @@ public abstract class GameImpl implements Game { if (emblem != null) { return emblem; } + TheRingEmblem newEmblem = new TheRingEmblem(playerId); - - // TODO: add image info - state.addCommandObject(newEmblem); + return newEmblem; } @@ -1966,16 +1965,13 @@ public abstract class GameImpl implements Game { @Override public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) { Emblem newEmblem = emblem.copy(); - newEmblem.setSourceObject(sourceObject); + newEmblem.setSourceObjectAndInitImage(sourceObject); newEmblem.setControllerId(toPlayerId); newEmblem.assignNewId(); newEmblem.getAbilities().newId(); for (Ability ability : newEmblem.getAbilities()) { ability.setSourceId(newEmblem.getId()); } - - // image info setup in setSourceObject - state.addCommandObject(newEmblem); } @@ -1997,17 +1993,15 @@ public abstract class GameImpl implements Game { } } Plane newPlane = plane.copy(); - newPlane.setSourceObject(); + newPlane.setSourceObjectAndInitImage(); newPlane.setControllerId(toPlayerId); newPlane.assignNewId(); newPlane.getAbilities().newId(); for (Ability ability : newPlane.getAbilities()) { ability.setSourceId(newPlane.getId()); } - - // image info setup in setSourceObject - state.addCommandObject(newPlane); + informPlayers("You have planeswalked to " + newPlane.getLogName()); // Fire off the planeswalked event @@ -2028,7 +2022,6 @@ public abstract class GameImpl implements Game { @Override public Dungeon addDungeon(Dungeon dungeon, UUID playerId) { dungeon.setControllerId(playerId); - dungeon.setSourceObject(); state.addCommandObject(dungeon); return dungeon; } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 13499c2fb69..e9238b39ab5 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -1239,6 +1239,11 @@ public class GameState implements Serializable, Copyable { this.isPlaneChase = isPlaneChase; } + /** + * Add object to command zone. + *

+ * Warning, all object data must be initialized before adding, including image info + */ public void addCommandObject(CommandObject commandObject) { getCommand().add(commandObject); setZone(commandObject.getId(), Zone.COMMAND); diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index 71efbbb617e..b79ee37e33d 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -154,24 +154,35 @@ public class Dungeon extends CommandObjectImpl { } public static Dungeon createDungeon(String name, boolean isNameMustExists) { + Dungeon res; switch (name) { case "Tomb of Annihilation": - return new TombOfAnnihilationDungeon(); + res = new TombOfAnnihilationDungeon(); + break; case "Lost Mine of Phandelver": - return new LostMineOfPhandelverDungeon(); + res = new LostMineOfPhandelverDungeon(); + break; case "Dungeon of the Mad Mage": - return new DungeonOfTheMadMageDungeon(); + res = new DungeonOfTheMadMageDungeon(); + break; default: if (isNameMustExists) { throw new UnsupportedOperationException("A dungeon should have been chosen"); } else { - return null; + res = null; } } + + // dungeon don't have source, so image data can be initialized immediately + if (res != null) { + res.setSourceObjectAndInitImage(); + } + + return res; } - public void setSourceObject() { - // choose set code due source + public void setSourceObjectAndInitImage() { + // image TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForClass(this.getClass().getName(), null); if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index d760b30bb00..9d1c5742b12 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -58,7 +58,7 @@ public abstract class Emblem extends CommandObjectImpl { return frameStyle; } - public void setSourceObject(MageObject sourceObject) { + public void setSourceObjectAndInitImage(MageObject sourceObject) { this.sourceObject = sourceObject; // choose set code due source diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java index 86614955838..aabb34b63b9 100644 --- a/Mage/src/main/java/mage/game/command/Plane.java +++ b/Mage/src/main/java/mage/game/command/Plane.java @@ -66,7 +66,7 @@ public abstract class Plane extends CommandObjectImpl { return frameStyle; } - public void setSourceObject() { + public void setSourceObjectAndInitImage() { this.sourceObject = null; // choose set code due source diff --git a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java index 422ddc5af4c..ad9f4f4c560 100644 --- a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java +++ b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java @@ -94,7 +94,7 @@ public final class EmblemOfCard extends Emblem { } @Override - public void setSourceObject(MageObject sourceObject) { + public void setSourceObjectAndInitImage(MageObject sourceObject) { this.sourceObject = sourceObject; // super method would try and fail to find the emblem image here // (not sure why that would be setSoureObject's job; we get our image during construction) diff --git a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java index 37f58a14114..8202226d44c 100644 --- a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java @@ -45,7 +45,7 @@ public class RadiationEmblem extends Emblem { this.setImageFileName(""); // use default this.setImageNumber(foundInfo.getImageNumber()); } else { - // how-to fix: add emblem to the tokens-database TokenRepository->loadXmageTokens + // how-to fix: add image to the tokens-database TokenRepository->loadXmageTokens throw new IllegalArgumentException("Wrong code usage: can't find xmage token info for: " + TokenRepository.XMAGE_IMAGE_NAME_RADIATION); } } diff --git a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java index 1bbc6e5b6b7..4722a39bd76 100644 --- a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java @@ -12,6 +12,8 @@ import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.cards.repository.TokenInfo; +import mage.cards.repository.TokenRepository; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; @@ -41,6 +43,19 @@ public final class TheRingEmblem extends Emblem { public TheRingEmblem(UUID controllerId) { super("The Ring"); this.setControllerId(controllerId); + + // ring don't have source, so image can be initialized immediately + TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_THE_RING, null); + if (foundInfo != null) { + this.setExpansionSetCode(foundInfo.getSetCode()); + this.setUsesVariousArt(false); + this.setCardNumber(""); + this.setImageFileName(""); // use default + this.setImageNumber(foundInfo.getImageNumber()); + } else { + // how-to fix: add image to the tokens-database TokenRepository->loadXmageTokens + throw new IllegalArgumentException("Wrong code usage: can't find xmage token info for: " + TokenRepository.XMAGE_IMAGE_NAME_THE_RING); + } } private TheRingEmblem(final TheRingEmblem card) { diff --git a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java index 1fa47c17fbc..d4f396adaa8 100644 --- a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java @@ -28,7 +28,7 @@ public class XmageHelperEmblem extends Emblem { this.setImageFileName(""); // use default this.setImageNumber(foundInfo.getImageNumber()); } else { - // how-to fix: add emblem to the tokens-database TokenRepository->loadXmageTokens + // how-to fix: add image to the tokens-database TokenRepository->loadXmageTokens throw new IllegalArgumentException("Wrong code usage: can't find xmage token info for: " + TokenRepository.XMAGE_IMAGE_NAME_HELPER_EMBLEM); } } diff --git a/Mage/src/main/java/mage/game/stack/SpellStack.java b/Mage/src/main/java/mage/game/stack/SpellStack.java index 1cbd03da71e..83458c5980b 100644 --- a/Mage/src/main/java/mage/game/stack/SpellStack.java +++ b/Mage/src/main/java/mage/game/stack/SpellStack.java @@ -81,7 +81,9 @@ public class SpellStack extends ArrayDeque { counteredObjectName = "Ability (" + stackObject.getStackAbility().getRule(targetSourceName) + ") of " + targetSourceName; } if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, source, stackObject.getControllerId()))) { - if (!(stackObject instanceof Spell)) { // spells are removed from stack by the card movement + // spells are removed from stack by the card movement + if (!(stackObject instanceof Spell) + || stackObject.isCopy()) { // !ensure that copies of stackobjects have their history recorded ie: Swan Song this.remove(stackObject, game); game.rememberLKI(Zone.STACK, stackObject); } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index bc67863096f..20208502c32 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1035,6 +1035,7 @@ public final class CardUtil { || text.startsWith("any ") || text.startsWith("{this} ") || text.startsWith("your ") + || text.startsWith("their ") || text.startsWith("one ")) { return text; }