From 9d7bf27d38944772eb1616c998e4177372852523 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:04:17 +0200 Subject: [PATCH] [PIP] Implement Rad Counters mechanic (#12087) --- .../java/mage/client/game/PlayerPanelExt.java | 296 +++++++++--------- .../black-and-white-radiation-symbol-yhs.png | Bin 0 -> 12422 bytes Mage.Client/src/main/resources/info/rad.png | Bin 0 -> 1392 bytes .../src/mage/cards/s/SurvivorsMedKit.java | 3 +- Mage.Sets/src/mage/sets/Fallout.java | 7 - .../cards/copy/FeldonOfTheThirdPathTest.java | 3 +- .../cards/triggers/MephidrossVampireTest.java | 9 +- .../cards/triggers/RadCounterTriggerTest.java | 109 +++++++ .../mage/abilities/TriggeredAbilities.java | 3 +- .../cards/repository/TokenRepository.java | 4 + Mage/src/main/java/mage/game/GameImpl.java | 14 + Mage/src/main/java/mage/game/GameState.java | 34 +- .../main/java/mage/game/command/Emblem.java | 2 +- .../game/command/emblems/RadiationEmblem.java | 112 +++++++ 14 files changed, 420 insertions(+), 176 deletions(-) create mode 100644 Mage.Client/src/main/resources/info/purepng/black-and-white-radiation-symbol-yhs.png create mode 100644 Mage.Client/src/main/resources/info/rad.png create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/RadCounterTriggerTest.java create mode 100644 Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 19c92a8c344..13195e60e26 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -1,6 +1,5 @@ package mage.client.game; -import mage.cards.decks.importer.DckDeckImporter; import mage.client.MageFrame; import mage.client.SessionHandler; import mage.client.cards.BigCard; @@ -8,7 +7,6 @@ import mage.client.components.HoverButton; import mage.client.components.MageRoundPane; import mage.client.components.ext.dlg.DialogManager; import mage.client.dialog.PreferencesDialog; -import mage.client.themes.ThemeType; import mage.client.util.CardsViewUtil; import mage.client.util.ImageHelper; import mage.client.util.gui.BufferedImageBuilder; @@ -16,6 +14,7 @@ import mage.client.util.gui.countryBox.CountryUtil; import mage.components.ImagePanel; import mage.components.ImagePanelStyle; import mage.constants.CardType; +import static mage.constants.Constants.*; import mage.constants.ManaType; import mage.counters.Counter; import mage.counters.CounterType; @@ -33,12 +32,10 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.util.*; -import static mage.constants.Constants.*; - /** * Game GUI: player panel with avatar and icons * - * @author nantuko, JayDi85 + * @author nantuko, JayDi85, Susucr */ public class PlayerPanelExt extends javax.swing.JPanel { @@ -53,8 +50,8 @@ public class PlayerPanelExt extends javax.swing.JPanel { private static final String DEFAULT_AVATAR_PATH = "/avatars/" + DEFAULT_AVATAR_ID + ".jpg"; private static final int PANEL_WIDTH = 94; - private static final int PANEL_HEIGHT = 262; - private static final int PANEL_HEIGHT_SMALL = 210; + private static final int PANEL_HEIGHT = 290; + private static final int PANEL_HEIGHT_SMALL = 238; private static final int PANEL_HEIGHT_EXTRA_FOR_ME = 25; private static final int MANA_LABEL_SIZE_HORIZONTAL = 20; @@ -232,6 +229,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { setTextForLabel("poison", poisonLabel, poison, player.getCounters().getCount(CounterType.POISON), false); setTextForLabel("energy", energyLabel, energy, player.getCounters().getCount(CounterType.ENERGY), false); setTextForLabel("experience", experienceLabel, experience, player.getCounters().getCount(CounterType.EXPERIENCE), false); + setTextForLabel("rad", radLabel, rad, player.getCounters().getCount(CounterType.RAD), false); setTextForLabel("hand zone", handLabel, hand, player.getHandCount(), true); int libraryCards = player.getLibraryCount(); if (libraryCards > 99) { @@ -493,6 +491,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { poisonLabel = new JLabel(); energyLabel = new JLabel(); experienceLabel = new JLabel(); + radLabel = new JLabel(); graveLabel = new JLabel(); commandLabel = new JLabel(); libraryLabel = new JLabel(); @@ -523,14 +522,17 @@ public class PlayerPanelExt extends javax.swing.JPanel { // life area r = new Rectangle(18, 18); lifeLabel.setToolTipText("Life"); + lifeLabel.setHorizontalAlignment(SwingConstants.CENTER); Image imageLife = ImageHelper.getImageFromResources("/info/life.png"); BufferedImage resizedLife = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(imageLife, BufferedImage.TYPE_INT_ARGB), r); life = new ImagePanel(resizedLife, ImagePanelStyle.ACTUAL); life.setToolTipText("Life"); life.setOpaque(false); + // hand area r = new Rectangle(18, 18); handLabel.setToolTipText("Hand"); + handLabel.setHorizontalAlignment(SwingConstants.CENTER); Image imageHand = ImageHelper.getImageFromResources("/info/hand.png"); BufferedImage resizedHand = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(imageHand, BufferedImage.TYPE_INT_ARGB), r); hand = new ImagePanel(resizedHand, ImagePanelStyle.ACTUAL); @@ -544,10 +546,12 @@ public class PlayerPanelExt extends javax.swing.JPanel { poison = new ImagePanel(resizedPoison, ImagePanelStyle.ACTUAL); poison.setOpaque(false); setTextForLabel("poison", poisonLabel, poison, 0, false); + poisonLabel.setHorizontalAlignment(SwingConstants.CENTER); // Library r = new Rectangle(19, 19); libraryLabel.setToolTipText("Library"); + libraryLabel.setHorizontalAlignment(SwingConstants.CENTER); Image imageLibrary = ImageHelper.getImageFromResources("/info/library.png"); BufferedImage resizedLibrary = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(imageLibrary, BufferedImage.TYPE_INT_ARGB), r); @@ -559,6 +563,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { // Grave count and open graveyard button r = new Rectangle(21, 21); graveLabel.setToolTipText("Card Types: 0"); + graveLabel.setHorizontalAlignment(SwingConstants.CENTER); Image imageGrave = ImageHelper.getImageFromResources("/info/grave.png"); BufferedImage resizedGrave = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(imageGrave, BufferedImage.TYPE_INT_ARGB), r); @@ -569,6 +574,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { exileLabel = new JLabel(); exileLabel.setToolTipText("Exile"); + exileLabel.setHorizontalAlignment(SwingConstants.CENTER); image = ImageHelper.getImageFromResources("/info/exile.png"); r = new Rectangle(21, 21); resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); @@ -619,12 +625,6 @@ public class PlayerPanelExt extends javax.swing.JPanel { toolHintsHelper.setBounds(3, 2 + 21 + 2, 73, 21); zonesPanel.add(toolHintsHelper); - energyExperiencePanel = new JPanel(); - energyExperiencePanel.setPreferredSize(new Dimension(100, 20)); - energyExperiencePanel.setSize(100, 20); - energyExperiencePanel.setLayout(null); - energyExperiencePanel.setOpaque(false); - // Energy count r = new Rectangle(18, 18); Image imageEnergy = ImageHelper.getImageFromResources("/info/energy.png"); @@ -633,6 +633,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { energy.setToolTipText("Energy"); energy.setOpaque(false); setTextForLabel("energy", energyLabel, energy, 0, false); + energyLabel.setHorizontalAlignment(SwingConstants.CENTER); // Experience count r = new Rectangle(18, 18); @@ -642,6 +643,17 @@ public class PlayerPanelExt extends javax.swing.JPanel { experience.setToolTipText("Experience"); experience.setOpaque(false); setTextForLabel("experience", experienceLabel, experience, 0, false); + experienceLabel.setHorizontalAlignment(SwingConstants.CENTER); + + // Rad count + r = new Rectangle(16, 16); + Image imageRad = ImageHelper.getImageFromResources("/info/rad.png"); + BufferedImage resizedRad = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(imageRad, BufferedImage.TYPE_INT_ARGB), r); + rad = new ImagePanel(resizedRad, ImagePanelStyle.ACTUAL); + rad.setToolTipText("Rad"); + rad.setOpaque(false); + setTextForLabel("rad", radLabel, rad, 0, false); + radLabel.setHorizontalAlignment(SwingConstants.CENTER); btnPlayer = new JButton(); btnPlayer.setText("Player"); @@ -672,6 +684,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { //*/ ///* JLabel manaCountLabelW = new JLabel(); + manaCountLabelW.setHorizontalAlignment(SwingConstants.CENTER); manaLabels.put(manaCountLabelW, ManaType.WHITE); r = new Rectangle(15, 15); BufferedImage imageManaW = ManaSymbols.getSizedManaSymbol("W", 15); @@ -684,6 +697,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { JLabel manaCountLabelU = new JLabel(); manaLabels.put(manaCountLabelU, ManaType.BLUE); + manaCountLabelU.setHorizontalAlignment(SwingConstants.CENTER); r = new Rectangle(15, 15); BufferedImage imageManaU = ManaSymbols.getSizedManaSymbol("U", 15); HoverButton btnBlueMana = new HoverButton(null, imageManaU, imageManaU, imageManaU, r); @@ -694,6 +708,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { JLabel manaCountLabelB = new JLabel(); manaLabels.put(manaCountLabelB, ManaType.BLACK); + manaCountLabelB.setHorizontalAlignment(SwingConstants.CENTER); r = new Rectangle(15, 15); BufferedImage imageManaB = ManaSymbols.getSizedManaSymbol("B", 15); HoverButton btnBlackMana = new HoverButton(null, imageManaB, imageManaB, imageManaB, r); @@ -704,6 +719,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { JLabel manaCountLabelR = new JLabel(); manaLabels.put(manaCountLabelR, ManaType.RED); + manaCountLabelR.setHorizontalAlignment(SwingConstants.CENTER); r = new Rectangle(15, 15); BufferedImage imageManaR = ManaSymbols.getSizedManaSymbol("R", 15); HoverButton btnRedMana = new HoverButton(null, imageManaR, imageManaR, imageManaR, r); @@ -714,6 +730,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { JLabel manaCountLabelG = new JLabel(); manaLabels.put(manaCountLabelG, ManaType.GREEN); + manaCountLabelG.setHorizontalAlignment(SwingConstants.CENTER); r = new Rectangle(15, 15); BufferedImage imageManaG = ManaSymbols.getSizedManaSymbol("G", 15); HoverButton btnGreenMana = new HoverButton(null, imageManaG, imageManaG, imageManaG, r); @@ -724,6 +741,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { JLabel manaCountLabelX = new JLabel(); manaLabels.put(manaCountLabelX, ManaType.COLORLESS); + manaCountLabelX.setHorizontalAlignment(SwingConstants.CENTER); r = new Rectangle(15, 15); BufferedImage imageManaX = ManaSymbols.getSizedManaSymbol("C", 15); HoverButton btnColorlessMana = new HoverButton(null, imageManaX, imageManaX, imageManaX, r); @@ -735,90 +753,6 @@ public class PlayerPanelExt extends javax.swing.JPanel { GroupLayout gl_panelBackground = new GroupLayout(panelBackground); gl_panelBackground.setHorizontalGroup( gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(9) - .addComponent(life, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) - .addGap(3) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(18) - .addComponent(hand, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) - .addComponent(lifeLabel, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)) - .addGap(4) - .addComponent(handLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(9) - .addComponent(poison, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) - .addGap(3) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(18) - .addComponent(library, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) - .addComponent(poisonLabel, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)) - .addGap(4) - .addComponent(libraryLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(9) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addComponent(energy, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(2) - .addComponent(btnWhiteMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(2) - .addComponent(btnBlueMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(2) - .addComponent(btnBlackMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addComponent(grave, GroupLayout.PREFERRED_SIZE, 21, GroupLayout.PREFERRED_SIZE) - ) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(18) - .addComponent(experience, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)) - .addComponent(energyLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(20) - .addComponent(btnRedMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(1) - .addComponent(manaCountLabelW, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE))) - .addGap(3) - .addComponent(manaCountLabelR, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(1) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addComponent(manaCountLabelB, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(19) - .addComponent(btnColorlessMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE))) - .addGap(5) - .addComponent(manaCountLabelX, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(20) - .addComponent(btnGreenMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(40) - .addComponent(manaCountLabelG, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(40) - .addComponent(experienceLabel, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(18) - .addComponent(exileZone, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE) - ) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(5) - .addComponent(graveLabel, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(40) - .addComponent(exileLabel, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(1) - .addComponent(manaCountLabelU, GroupLayout.PREFERRED_SIZE, MANA_LABEL_SIZE_HORIZONTAL, GroupLayout.PREFERRED_SIZE)))) .addGroup(gl_panelBackground.createSequentialGroup() .addGap(6) .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) @@ -826,11 +760,68 @@ public class PlayerPanelExt extends javax.swing.JPanel { .addComponent(timerLabel, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(avatar, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 80, Short.MAX_VALUE)) .addGap(8)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(9) + // The left column of icon+label + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(life, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(lifeLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(poison, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(poisonLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(energy, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(energyLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(rad, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(radLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnWhiteMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelW, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnBlueMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelU, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnBlackMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelB, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(grave, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(graveLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE))) + // The right column of icon+label + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(hand, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(handLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(library, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(libraryLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(experience, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(experienceLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnRedMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelR, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnGreenMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelG, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(2) + .addComponent(btnColorlessMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addComponent(manaCountLabelX, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addGroup(gl_panelBackground.createSequentialGroup() + .addComponent(exileZone, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) + .addComponent(exileLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE))) + .addGap(4)) .addGroup(gl_panelBackground.createSequentialGroup() .addGap(6) .addComponent(zonesPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(14)) - ); + .addGap(6))); gl_panelBackground.setVerticalGroup( gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() @@ -845,85 +836,77 @@ public class PlayerPanelExt extends javax.swing.JPanel { .addGroup(gl_panelBackground.createSequentialGroup() .addGap(1) .addComponent(life, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) + .addComponent(lifeLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addGroup(gl_panelBackground.createSequentialGroup() .addGap(1) .addComponent(hand, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) - .addComponent(lifeLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addComponent(handLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) // Poison & Library .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() .addGap(1) .addComponent(poison, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) + .addComponent(poisonLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addGroup(gl_panelBackground.createSequentialGroup() .addGap(1) .addComponent(library, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) - .addComponent(poisonLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addComponent(libraryLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) - .addGap(1) - // Poison + // Energy & Experience .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(2) - .addComponent(energy, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE) - .addGap(2) - .addComponent(btnWhiteMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) - .addGap(2) - .addComponent(btnBlueMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) - .addGap(2) - .addComponent(btnBlackMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) - .addGap(3) - .addComponent(grave, GroupLayout.PREFERRED_SIZE, 21, GroupLayout.PREFERRED_SIZE) - ) + .addGap(1) + .addComponent(energy, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) + .addComponent(energyLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addGroup(gl_panelBackground.createSequentialGroup() - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(1) - .addComponent(experience, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)) - .addComponent(energyLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) - .addGap(2) - .addComponent(btnRedMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(14) - .addComponent(manaCountLabelW, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(14) - .addComponent(manaCountLabelR, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE))) - .addGap(4) - .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) - .addComponent(manaCountLabelB, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(8) - .addComponent(btnColorlessMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) - .addComponent(manaCountLabelX, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE))) + .addGap(1) + .addComponent(experience, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) + .addComponent(experienceLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + // Rad & + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(39) + .addGap(1) + .addComponent(rad, GroupLayout.PREFERRED_SIZE, 18, GroupLayout.PREFERRED_SIZE)) + .addComponent(radLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + // W & R + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(1) + .addComponent(btnWhiteMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelW, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(1) + .addComponent(btnRedMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelR, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)) + // U & G + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(1) + .addComponent(btnBlueMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelU, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE) + .addGroup(gl_panelBackground.createSequentialGroup() + .addGap(1) .addComponent(btnGreenMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelG, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)) + // B & X + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(31) - .addComponent(manaCountLabelG, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE)) - .addComponent(experienceLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) + .addGap(1) + .addComponent(btnBlackMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelB, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(76) - .addComponent(exileZone, GroupLayout.PREFERRED_SIZE, 21, GroupLayout.PREFERRED_SIZE) - ) + .addGap(1) + .addComponent(btnColorlessMana, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)) + .addComponent(manaCountLabelX, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)) + // grave & exile + .addGroup(gl_panelBackground.createParallelGroup(Alignment.LEADING) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(76) - .addComponent(graveLabel, GroupLayout.PREFERRED_SIZE, 21, GroupLayout.PREFERRED_SIZE)) + .addComponent(grave, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addComponent(graveLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE) .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(76) - .addComponent(exileLabel, GroupLayout.PREFERRED_SIZE, 21, GroupLayout.PREFERRED_SIZE)) - .addGroup(gl_panelBackground.createSequentialGroup() - .addGap(31) - .addComponent(manaCountLabelU, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE) - ) - ) + .addComponent(exileZone, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) + .addComponent(exileLabel, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)) .addGap(2) - .addComponent(zonesPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) - ) - ); + .addComponent(zonesPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE))); panelBackground.setLayout(gl_panelBackground); GroupLayout groupLayout = new GroupLayout(this); groupLayout.setHorizontalGroup( @@ -1013,6 +996,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { private ImagePanel poison; private ImagePanel energy; private ImagePanel experience; + private ImagePanel rad; private ImagePanel hand; private HoverButton grave; private HoverButton library; @@ -1027,12 +1011,12 @@ public class PlayerPanelExt extends javax.swing.JPanel { private JLabel poisonLabel; private JLabel energyLabel; private JLabel experienceLabel; + private JLabel radLabel; private JLabel graveLabel; private JLabel commandLabel; private JLabel exileLabel; private JPanel zonesPanel; - private JPanel energyExperiencePanel; private HoverButton exileZone; private HoverButton commandZone; // End of variables declaration//GEN-END:variables diff --git a/Mage.Client/src/main/resources/info/purepng/black-and-white-radiation-symbol-yhs.png b/Mage.Client/src/main/resources/info/purepng/black-and-white-radiation-symbol-yhs.png new file mode 100644 index 0000000000000000000000000000000000000000..a2535f509559e0f0ce5ad19fae4700d47d5f8496 GIT binary patch literal 12422 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 unfinished = Arrays.asList("Acquired Mutation", "Bloatfly Swarm", "Contaminated Drink", "Feral Ghoul", "Glowing One", "Harold and Bob, First Numens", "Infesting Radroach", "Mariposa Military Base", "Megaton's Fate", "Mirelurk Queen", "Nightkin Ambusher", "Nuclear Fallout", "Nuka-Nuke Launcher", "Screeching Scorchbeast", "Strong, the Brutish Thespian", "Struggle for Project Purity", "Survivor's Med Kit", "Tato Farmer", "The Master, Transcendent", "The Wise Mothman", "Vault 12: The Necropolis", "Vexing Radgull"); private static final Fallout instance = new Fallout(); public static Fallout getInstance() { @@ -365,7 +360,5 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Windbrisk Heights", 315, Rarity.RARE, mage.cards.w.WindbriskHeights.class)); cards.add(new SetCardInfo("Winding Constrictor", 223, Rarity.UNCOMMON, mage.cards.w.WindingConstrictor.class)); cards.add(new SetCardInfo("Woodland Cemetery", 316, Rarity.RARE, mage.cards.w.WoodlandCemetery.class)); - - cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java index 6bd0e6e4654..97634c343bc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java @@ -53,7 +53,8 @@ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase { assertLife(playerB, 18); // -2 from Robber // possible bug: triggers from destroyed permanents keeps in game state (e.g. 2 triggers in game state) - Assert.assertEquals("game state must have only 1 trigger from original card", 1, currentGame.getState().getTriggers().size()); + // (Since the introduction of Rad Counters, there is one inherent trigger per player in the state, hence the "+ 2") + Assert.assertEquals("game state must have only 1 trigger from original card", 1 + 2, currentGame.getState().getTriggers().size()); } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MephidrossVampireTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MephidrossVampireTest.java index a483ce0caea..fb03b0d7481 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MephidrossVampireTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MephidrossVampireTest.java @@ -8,7 +8,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ @@ -16,7 +15,6 @@ public class MephidrossVampireTest extends CardTestPlayerBase { /** * Checks if only one triggered ability is handleded in the pool - * */ @Test public void testMultiTriggers() { @@ -35,9 +33,10 @@ public class MephidrossVampireTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); assertPermanentCount(playerB, "Mephidross Vampire", 1); - assertPowerToughness(playerB, "Mephidross Vampire", 4, 5); + assertPowerToughness(playerB, "Mephidross Vampire", 4, 5); + + // (Since the introduction of Rad Counters, there is one inherent trigger per player in the state, hence the "+ 2") + Assert.assertEquals("There should only be one triggered ability in the list of triggers of the State", 1 + 2, currentGame.getState().getTriggers().size()); - Assert.assertEquals("There should only be one triggered ability in the list of triggers of the State", 1, currentGame.getState().getTriggers().size()); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RadCounterTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RadCounterTriggerTest.java new file mode 100644 index 00000000000..f7dbdcfdc47 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/RadCounterTriggerTest.java @@ -0,0 +1,109 @@ +package org.mage.test.cards.single.pip; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.players.Player; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * > 725.1. Rad counters are a kind of counter a player can have (see rule 122, "Counters"). + * There is an inherent triggered ability associated with rad counters. This ability has no + * source and is controlled by the active player. This is an exception to rule 113.8. The full + * text of this ability is "At the beginning of each player's precombat main phase, if that + * player has one or more rad counters, that player mills a number of cards equal to the number + * of rad counters they have. For each nonland card milled this way, that player loses 1 life + * and removes one rad counter from themselves." + * + * @author Susucr + */ +public class RadCounterTriggerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.n.NuclearFallout} {X}{B}{B} + * Sorcery + * Each creature gets twice -X/-X until end of turn. Each player gets X rad counters. + */ + private static final String fallout = "Nuclear Fallout"; + + private static void checkRadCounterCount(String message, Player player, int expected) { + Assert.assertEquals(message, expected, player.getCounters().getCount(CounterType.RAD)); + } + + private static void checkGraveyardSize(String message, Player player, int expected) { + Assert.assertEquals(message, expected, player.getGraveyard().size()); + } + + @Test + public void test_Fallout_3_Multiple_Turns() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, fallout); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + //addCard(Zone.BATTLEFIELD, playerB, "Blinkmoth Urn"); + + addCard(Zone.BATTLEFIELD, playerA, "Yawgmoth's Bargain"); // Skip draw step, just to simplify library setup. + addCard(Zone.BATTLEFIELD, playerB, "Yawgmoth's Bargain"); // Skip draw step, just to simplify library setup. + + // Library setup for player A: + // Turn 7, 1 rad counter, mill 1 land, final check: 1 rad counter + addCard(Zone.LIBRARY, playerA, "Tundra"); + // Turn 5, 3 rad counters, mill 1 land 2 nonland + addCard(Zone.LIBRARY, playerA, "Akoum Warrior"); + addCard(Zone.LIBRARY, playerA, "Tropical Island"); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); + // Turn 3, 3 rad counters, mill only lands + addCard(Zone.LIBRARY, playerA, "Bayou", 3); + + // Library setup for player B: + // Turn 6, 0 rad counter, no trigger. + // Turn 4, 2 rad counters, mill 2 nonlands + addCard(Zone.LIBRARY, playerB, "Elite Vanguard"); + addCard(Zone.LIBRARY, playerB, "Fire // Ice"); + // Turn 2, 3 rad counters, mill 2 lands, 1 nonland + addCard(Zone.LIBRARY, playerB, "Brainstorm"); + addCard(Zone.LIBRARY, playerB, "Plains", 2); + + setChoice(playerA, "X=3"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fallout); + + runCode("rad count playerA turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkRadCounterCount(info, player, 3)); + runCode("rad count playerB turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkRadCounterCount(info, player, 3)); + + // Turn 2 -- start 0 gy, 3 rad, mill 1 nonland 2 land + runCode("rad count playerB turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkRadCounterCount(info, player, 2)); + checkLife("life playerB turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, 19); + runCode("graveyard count playerB turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkGraveyardSize(info, player, 3)); + + // Turn 3 -- start 1 gy, 3 rad, mill 3 nonland + runCode("rad count playerA turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkRadCounterCount(info, player, 3)); + checkLife("life playerA turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); + runCode("graveyard count playerA turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkGraveyardSize(info, player, 4)); + + // Turn 4 -- start 3gy, 2 rad, mill 2 nonland + runCode("rad count playerB turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkRadCounterCount(info, player, 0)); + checkLife("life playerB turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, 17); + runCode("graveyard count playerB turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkGraveyardSize(info, player, 5)); + + // Turn 5 -- start 4 gy, 3 rad, mill 2 nonland 1 land + runCode("rad count playerA turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkRadCounterCount(info, player, 1)); + checkLife("life playerA turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, 18); + runCode("graveyard count playerA turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkGraveyardSize(info, player, 7)); + + // Turn 6 -- start 5gy, no rad + runCode("rad count playerB turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkRadCounterCount(info, player, 0)); + checkLife("life playerB turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerB, 17); + runCode("graveyard count playerB turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> checkGraveyardSize(info, player, 5)); + + // Turn 7 -- start 7 gy, 1 rad, mill 1 land + runCode("rad count playerA turn 7", 7, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkRadCounterCount(info, player, 1)); + checkLife("life playerA turn 7", 7, PhaseStep.POSTCOMBAT_MAIN, playerA, 18); + runCode("graveyard count playerA turn 7", 7, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> checkGraveyardSize(info, player, 8)); + + setStopAt(7, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index 239438ac6ac..d45f0482257 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -308,10 +308,9 @@ public class TriggeredAbilities extends LinkedHashMap public void removeAbilitiesOfNonExistingSources(Game game) { // e.g. Token that had triggered abilities - entrySet().removeIf(entry -> game.getObject(entry.getValue().getSourceId()) == null + && game.getState().getInherentEmblems().stream().noneMatch(emblem -> emblem.getId().equals(entry.getValue().getSourceId())) && game.getState().getDesignations().stream().noneMatch(designation -> designation.getId().equals(entry.getValue().getSourceId()))); - } @Override diff --git a/Mage/src/main/java/mage/cards/repository/TokenRepository.java b/Mage/src/main/java/mage/cards/repository/TokenRepository.java index 24d3ac76966..361d961a4af 100644 --- a/Mage/src/main/java/mage/cards/repository/TokenRepository.java +++ b/Mage/src/main/java/mage/cards/repository/TokenRepository.java @@ -31,6 +31,7 @@ public enum TokenRepository { public static final String XMAGE_IMAGE_NAME_DAY = "Day"; 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"; private static final Logger logger = Logger.getLogger(TokenRepository.class); @@ -296,6 +297,9 @@ public enum TokenRepository { res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image")); res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 3, "https://api.scryfall.com/cards/tltc/15/en?format=image")); + // Radiation (for trigger) + res.add(createXmageToken(XMAGE_IMAGE_NAME_RADIATION, 1, "https://api.scryfall.com/cards/tpip/22/en?format=image")); + return res; } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index f59d047318c..3bc1d7003c2 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -46,6 +46,7 @@ import mage.game.combat.CombatGroup; import mage.game.command.*; import mage.game.command.dungeons.UndercityDungeon; import mage.game.command.emblems.EmblemOfCard; +import mage.game.command.emblems.RadiationEmblem; import mage.game.command.emblems.TheRingEmblem; import mage.game.events.*; import mage.game.events.TableEvent.EventType; @@ -443,6 +444,11 @@ public abstract class GameImpl implements Game { return designation; } } + for (Emblem emblem : state.getInherentEmblems()) { + if (emblem.getId().equals(objectId)) { + return emblem; + } + } // can be an ability of a sacrificed Token trying to get it's source object object = getLastKnownInformation(objectId, Zone.BATTLEFIELD); } @@ -1366,6 +1372,14 @@ public abstract class GameImpl implements Game { } } } + + // Rad counter mechanic for every player + for (UUID playerId : state.getPlayerList(startingPlayerId)) { + // This is not a real emblem. Just a fake source for the + // inherent trigger ability related to Rad counters + // Faking a source just to display something on the stack ability. + state.addInherentEmblem(new RadiationEmblem(), playerId); + } } public void initGameDefaultWatchers() { diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 0175f48fd7f..2e328147438 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -17,6 +17,7 @@ import mage.game.combat.Combat; import mage.game.combat.CombatGroup; import mage.game.command.Command; import mage.game.command.CommandObject; +import mage.game.command.Emblem; import mage.game.command.Plane; import mage.game.events.*; import mage.game.permanent.Battlefield; @@ -85,6 +86,7 @@ public class GameState implements Serializable, Copyable { private boolean isPlaneChase; private List seenPlanes = new ArrayList<>(); private List designations = new ArrayList<>(); + private List inherentEmblems = new ArrayList<>(); private Exile exile; private Battlefield battlefield; private int turnNum = 1; @@ -157,6 +159,7 @@ public class GameState implements Serializable, Copyable { this.isPlaneChase = state.isPlaneChase; this.seenPlanes.addAll(state.seenPlanes); this.designations.addAll(state.designations); + this.inherentEmblems = CardUtil.deepCopyObject(state.inherentEmblems); this.exile = state.exile.copy(); this.battlefield = state.battlefield.copy(); this.turnNum = state.turnNum; @@ -204,6 +207,7 @@ public class GameState implements Serializable, Copyable { exile.clear(); command.clear(); designations.clear(); + inherentEmblems.clear(); seenPlanes.clear(); isPlaneChase = false; revealed.clear(); @@ -245,6 +249,7 @@ public class GameState implements Serializable, Copyable { this.isPlaneChase = state.isPlaneChase; this.seenPlanes = state.seenPlanes; this.designations = state.designations; + this.inherentEmblems = state.inherentEmblems; this.exile = state.exile; this.battlefield = state.battlefield; this.turnNum = state.turnNum; @@ -506,6 +511,10 @@ public class GameState implements Serializable, Copyable { return designations; } + public List getInherentEmblems() { + return inherentEmblems; + } + public Plane getCurrentPlane() { if (command != null && command.size() > 0) { for (CommandObject cobject : command) { @@ -1135,6 +1144,25 @@ public class GameState implements Serializable, Copyable { } } + /** + * Inherent triggers (Rad counters) in the rules have no source. + * However to fit better with the engine, we make a fake emblem source, + * which is not displayed in any game zone. That allows the trigger to + * have a source, which helps with a bunch of situation like hosting, + * rather than having a trigger. + *

+ * Should not be used except in very specific situations + */ + public void addInherentEmblem(Emblem emblem, UUID controllerId) { + getInherentEmblems().add(emblem); + emblem.setControllerId(controllerId); + for (Ability ability : emblem.getInitAbilities()) { + ability.setControllerId(controllerId); + ability.setSourceId(emblem.getId()); + addAbility(ability, null, emblem); + } + } + public void addDesignation(Designation designation, Game game, UUID controllerId) { getDesignations().add(designation); for (Ability ability : designation.getInitAbilities()) { @@ -1414,7 +1442,7 @@ public class GameState implements Serializable, Copyable { /** * Store the tags of source ability using the MOR as a reference */ - void storePermanentCostsTags(MageObjectReference permanentMOR, Ability source){ + void storePermanentCostsTags(MageObjectReference permanentMOR, Ability source) { if (source.getCostsTagMap() != null) { permanentCostsTags.put(permanentMOR, CardUtil.deepCopyObject(source.getCostsTagMap())); } @@ -1424,9 +1452,9 @@ public class GameState implements Serializable, Copyable { * Removes the cost tags if the corresponding permanent is no longer on the battlefield. * Only use if the stack is empty and nothing can refer to them anymore (such as at EOT, the current behavior) */ - public void cleanupPermanentCostsTags(Game game){ + public void cleanupPermanentCostsTags(Game game) { getPermanentCostsTags().entrySet().removeIf(entry -> - !(entry.getKey().getZoneChangeCounter() == game.getState().getZoneChangeCounter(entry.getKey().getSourceId())-1) + !(entry.getKey().getZoneChangeCounter() == game.getState().getZoneChangeCounter(entry.getKey().getSourceId()) - 1) ); // The stored MOR is the stack-moment MOR so need to subtract one from the permanent's ZCC for the check } diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index cdd76315611..2461f6f8548 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -49,7 +49,7 @@ public abstract class Emblem extends CommandObjectImpl { this.controllerId = emblem.controllerId; this.sourceObject = emblem.sourceObject; this.copy = emblem.copy; - this.copyFrom = (emblem.copyFrom != null ? emblem.copyFrom : null); + this.copyFrom = emblem.copyFrom; this.abilites = emblem.abilites.copy(); } diff --git a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java new file mode 100644 index 00000000000..8d300950be9 --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java @@ -0,0 +1,112 @@ +package mage.game.command.emblems; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfPreCombatMainTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Cards; +import mage.cards.repository.TokenInfo; +import mage.cards.repository.TokenRepository; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.command.Emblem; +import mage.players.Player; + +/** + * Special emblem to enable the Rad Counter inherent trigger + * with an actual source, to display image on the stack. + * + * @author Susucr + */ +public class RadiationEmblem extends Emblem { + + public RadiationEmblem() { + super("Radiation"); + + this.getAbilities().add(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfPreCombatMainTriggeredAbility(Zone.ALL, new RadiationEffect(), TargetController.YOU, false, false), + RadiationCondition.instance, + "At the beginning of your precombat main phase, if you have any rad counters, " + + "mill that many cards. For each nonland card milled this way, you lose 1 life and a rad counter." + )); + + TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_RADIATION, null); + if (foundInfo != null) { + this.setExpansionSetCode(foundInfo.getSetCode()); + this.setCardNumber(""); + this.setImageFileName(""); // use default + this.setImageNumber(foundInfo.getImageNumber()); + } else { + // how-to fix: add emblem to the tokens-database + throw new IllegalArgumentException("Wrong code usage: can't find xmage token info for: " + TokenRepository.XMAGE_IMAGE_NAME_RADIATION); + } + } + + private RadiationEmblem(final RadiationEmblem card) { + super(card); + } + + @Override + public RadiationEmblem copy() { + return new RadiationEmblem(this); + } +} + +enum RadiationCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.getCounters().getCount(CounterType.RAD) > 0; + } +} + +/** + * 725.1. Rad counters are a kind of counter a player can have (see rule 122, "Counters"). + * There is an inherent triggered ability associated with rad counters. This ability has no + * source and is controlled by the active player. This is an exception to rule 113.8. The + * full text of this ability is "At the beginning of each player's precombat main phase, if + * that player has one or more rad counters, that player mills a number of cards equal to + * the number of rad counters they have. For each nonland card milled this way, that player + * loses 1 life and removes one rad counter from themselves." + */ +class RadiationEffect extends OneShotEffect { + + RadiationEffect() { + super(Outcome.Neutral); + staticText = "mill that many cards. For each nonland card milled this way, " + + "you lose 1 life and remove one rad counter."; + } + + private RadiationEffect(final RadiationEffect effect) { + super(effect); + } + + @Override + public RadiationEffect copy() { + return new RadiationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int amount = player.getCounters().getCount(CounterType.RAD); + Cards milled = player.millCards(amount, source, game); + int countNonLand = milled.count(StaticFilters.FILTER_CARD_NON_LAND, player.getId(), source, game); + if (countNonLand > 0) { + // TODO: support gaining life instead with [[Strong, the Brutish Thespian]] + player.loseLife(countNonLand, game, source, false); + player.removeCounters(CounterType.RAD.getName(), countNonLand, source, game); + } + return true; + } +} \ No newline at end of file