Merge pull request 'master' (#26) from External/mage:master into master
All checks were successful
/ build_release (push) Successful in 14m41s

Reviewed-on: #26
This commit is contained in:
Failure 2025-05-19 22:03:43 -07:00
commit 9a2382cd2c
788 changed files with 29882 additions and 3734 deletions

View file

@ -83,7 +83,7 @@ public enum CombatManager {
UUID defenderId = group.getDefenderId(); UUID defenderId = group.getDefenderId();
if (defenderId != null) { if (defenderId != null) {
// if attacker was blocked then use another arrow color // if attacker was blocked then use another arrow color
Color attackColor = group.getBlockers().isEmpty() ? ARROW_COLOR_ATTACKER : ARROW_COLOR_BLOCKED_ATTACKER; Color attackColor = group.isBlocked() ? ARROW_COLOR_BLOCKED_ATTACKER : ARROW_COLOR_ATTACKER;
parentPoint = getParentPoint(attackerCard); parentPoint = getParentPoint(attackerCard);
PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId); PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId);
if (p != null) { if (p != null) {

View file

@ -326,6 +326,13 @@ public class TestCardRenderDialog extends MageDialog {
possibleTargets.add(playerYou.getId()); possibleTargets.add(playerYou.getId());
} }
// chosen target
Set<UUID> chosenTargets = null;
if (false) { // TODO: add GUI's checkbox for checkPlayerAsChosen
chosenTargets = new LinkedHashSet<>();
chosenTargets.add(playerYou.getId());
}
// player's panel // player's panel
if (this.player == null) { if (this.player == null) {
// create new panel // create new panel
@ -345,7 +352,7 @@ public class TestCardRenderDialog extends MageDialog {
.findFirst() .findFirst()
.orElse(null); .orElse(null);
this.player.init(this.game.getId(), playerYou.getId(), isMe, this.bigCard, 0); this.player.init(this.game.getId(), playerYou.getId(), isMe, this.bigCard, 0);
this.player.update(gameView, currentPlayerView, possibleTargets); this.player.update(gameView, currentPlayerView, possibleTargets, chosenTargets);
this.player.sizePlayerPanel(smallMode); this.player.sizePlayerPanel(smallMode);
// update CARDS // update CARDS

View file

@ -218,6 +218,22 @@ public final class GamePanel extends javax.swing.JPanel {
}); });
} }
public List<UUID> getPossibleTargets() {
if (options != null && options.containsKey("possibleTargets")) {
return (List<UUID>) options.get("possibleTargets");
} else {
return Collections.emptyList();
}
}
public Set<UUID> getChosenTargets() {
if (options != null && options.containsKey("chosenTargets")) {
return new HashSet<>((List<UUID>) options.get("chosenTargets"));
} else {
return Collections.emptySet();
}
}
public CardView findCard(UUID id) { public CardView findCard(UUID id) {
return this.allCardsIndex.getOrDefault(id, null); return this.allCardsIndex.getOrDefault(id, null);
} }
@ -658,7 +674,7 @@ public final class GamePanel extends javax.swing.JPanel {
// see test render dialog for refresh commands order // see test render dialog for refresh commands order
playPanel.getPlayerPanel().fullRefresh(GUISizeHelper.playerPanelGuiScale); playPanel.getPlayerPanel().fullRefresh(GUISizeHelper.playerPanelGuiScale);
playPanel.init(player, bigCard, gameId, player.getPriorityTimeLeftSecs()); playPanel.init(player, bigCard, gameId, player.getPriorityTimeLeftSecs());
playPanel.update(lastGameData.game, player, lastGameData.targets); playPanel.update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets());
playPanel.getPlayerPanel().sizePlayerPanel(false); playPanel.getPlayerPanel().sizePlayerPanel(false);
} }
}); });
@ -1186,7 +1202,7 @@ public final class GamePanel extends javax.swing.JPanel {
} }
} }
} }
players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets); players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets());
if (player.getPlayerId().equals(playerId)) { if (player.getPlayerId().equals(playerId)) {
skipButtons.updateFromPlayer(player); skipButtons.updateFromPlayer(player);
} }
@ -1802,12 +1818,7 @@ public final class GamePanel extends javax.swing.JPanel {
needZone = (Zone) lastGameData.options.get("targetZone"); needZone = (Zone) lastGameData.options.get("targetZone");
} }
List<UUID> needChosen; Set<UUID> needChosen = lastGameData.getChosenTargets();
if (lastGameData.options != null && lastGameData.options.containsKey("chosenTargets")) {
needChosen = (List<UUID>) lastGameData.options.get("chosenTargets");
} else {
needChosen = new ArrayList<>();
}
Set<UUID> needSelectable; Set<UUID> needSelectable;
if (lastGameData.targets != null) { if (lastGameData.targets != null) {
@ -2037,7 +2048,7 @@ public final class GamePanel extends javax.swing.JPanel {
private void prepareSelectableWindows( private void prepareSelectableWindows(
Collection<CardInfoWindowDialog> windows, Collection<CardInfoWindowDialog> windows,
Set<UUID> needSelectable, Set<UUID> needSelectable,
List<UUID> needChosen, Set<UUID> needChosen,
PlayableObjectsList needPlayable PlayableObjectsList needPlayable
) { ) {
// lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it // lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it

View file

@ -57,7 +57,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
// data init // data init
init(player, bigCard, gameId, priorityTime); init(player, bigCard, gameId, priorityTime);
update(null, player, null); update(null, player, null, null);
playerPanel.sizePlayerPanel(isSmallMode()); playerPanel.sizePlayerPanel(isSmallMode());
// init popup menu (must run after data init) // init popup menu (must run after data init)
@ -510,8 +510,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
this.isMe = player.getControlled(); this.isMe = player.getControlled();
} }
public final void update(GameView game, PlayerView player, Set<UUID> possibleTargets) { public final void update(GameView game, PlayerView player, Set<UUID> possibleTargets, Set<UUID> chosenTargets) {
this.playerPanel.update(game, player, possibleTargets); this.playerPanel.update(game, player, possibleTargets, chosenTargets);
this.battlefieldPanel.update(player.getBattlefield()); this.battlefieldPanel.update(player.getBattlefield());
if (this.allowViewHandCardsMenuItem != null) { if (this.allowViewHandCardsMenuItem != null) {
this.allowViewHandCardsMenuItem.setSelected(player.getUserData().isAllowRequestHandToAll()); this.allowViewHandCardsMenuItem.setSelected(player.getUserData().isAllowRequestHandToAll());

View file

@ -247,7 +247,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
.orElse(0); .orElse(0);
} }
public void update(GameView game, PlayerView player, Set<UUID> possibleTargets) { public void update(GameView game, PlayerView player, Set<UUID> possibleTargets, Set<UUID> chosenTargets) {
this.player = player; this.player = player;
int pastLife = player.getLife(); int pastLife = player.getLife();
if (playerLives != null) { if (playerLives != null) {
@ -427,6 +427,12 @@ public class PlayerPanelExt extends javax.swing.JPanel {
this.btnPlayer.setBorder(YELLOW_BORDER); this.btnPlayer.setBorder(YELLOW_BORDER);
} }
// selected targeting (draw as priority)
if (chosenTargets != null && chosenTargets.contains(this.playerId)) {
this.avatar.setBorder(GREEN_BORDER); // TODO: use diff green color for chosen targeting and current priority?
this.btnPlayer.setBorder(GREEN_BORDER);
}
update(player.getManaPool()); update(player.getManaPool());
} }

View file

@ -10,6 +10,7 @@ import mage.client.draft.DraftPanel;
import mage.client.game.GamePanel; import mage.client.game.GamePanel;
import mage.client.plugins.impl.Plugins; import mage.client.plugins.impl.Plugins;
import mage.client.util.DeckUtil; import mage.client.util.DeckUtil;
import mage.client.util.GUISizeHelper;
import mage.client.util.IgnoreList; import mage.client.util.IgnoreList;
import mage.client.util.audio.AudioManager; import mage.client.util.audio.AudioManager;
import mage.client.util.object.SaveObjectUtil; import mage.client.util.object.SaveObjectUtil;
@ -22,9 +23,12 @@ import mage.util.DebugUtil;
import mage.view.*; import mage.view.*;
import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.MessageType;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.mage.card.arcane.ManaSymbols;
import javax.swing.*; import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.List;
import java.util.*; import java.util.*;
/** /**
@ -203,11 +207,7 @@ public class CallbackClientImpl implements CallbackClient {
case SERVER_MESSAGE: { case SERVER_MESSAGE: {
if (callback.getData() != null) { if (callback.getData() != null) {
ChatMessage message = (ChatMessage) callback.getData(); ChatMessage message = (ChatMessage) callback.getData();
if (message.getColor() == ChatMessage.MessageColor.RED) { showMessageDialog(null, message.getMessage(), "Server message");
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.WARNING_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.INFORMATION_MESSAGE);
}
} }
break; break;
} }
@ -401,7 +401,7 @@ public class CallbackClientImpl implements CallbackClient {
case SHOW_USERMESSAGE: { case SHOW_USERMESSAGE: {
List<String> messageData = (List<String>) callback.getData(); List<String> messageData = (List<String>) callback.getData();
if (messageData.size() == 2) { if (messageData.size() == 2) {
JOptionPane.showMessageDialog(null, messageData.get(1), messageData.get(0), JOptionPane.WARNING_MESSAGE); showMessageDialog(null, messageData.get(1), messageData.get(0));
} }
break; break;
} }
@ -420,8 +420,7 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData(); GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId()); GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) { if (panel != null) {
JOptionPane.showMessageDialog(panel, message.getMessage(), "Game message", showMessageDialog(panel, message.getMessage(), "Game message");
JOptionPane.INFORMATION_MESSAGE);
} }
break; break;
} }
@ -510,6 +509,18 @@ public class CallbackClientImpl implements CallbackClient {
}); });
} }
/**
* Show modal message box, so try to use it only for critical errors or global message. As less as possible.
*/
private void showMessageDialog(Component parentComponent, String message, String title) {
// convert to html
// message - supported
// title - not supported
message = ManaSymbols.replaceSymbolsWithHTML(message, ManaSymbols.Type.DIALOG);
message = GUISizeHelper.textToHtmlWithSize(message, GUISizeHelper.dialogFont);
JOptionPane.showMessageDialog(parentComponent, message, title, JOptionPane.INFORMATION_MESSAGE);
}
private ActionData appendJsonEvent(String name, UUID gameId, Object value) { private ActionData appendJsonEvent(String name, UUID gameId, Object value) {
Session session = SessionHandler.getSession(); Session session = SessionHandler.getSession();
if (session.isJsonLogActive()) { if (session.isJsonLogActive()) {

View file

@ -6,6 +6,7 @@ import mage.client.util.gui.GuiDisplayUtil;
import org.mage.card.arcane.CardRenderer; import org.mage.card.arcane.CardRenderer;
import javax.swing.*; import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*; import java.awt.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Locale; import java.util.Locale;
@ -108,7 +109,7 @@ public final class GUISizeHelper {
// app - frame/window title // app - frame/window title
// nimbus's LaF limited to static title size, so font can't be too big (related code in SynthInternalFrameTitlePane, BasicInternalFrameTitlePane) // nimbus's LaF limited to static title size, so font can't be too big (related code in SynthInternalFrameTitlePane, BasicInternalFrameTitlePane)
UIManager.put("InternalFrame.titleFont", dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize()))); UIManager.put("InternalFrame.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize()))));
// app - tables // app - tables
tableFont = new java.awt.Font("Arial", 0, dialogFontSize); tableFont = new java.awt.Font("Arial", 0, dialogFontSize);
@ -150,7 +151,11 @@ public final class GUISizeHelper {
cardTooltipLargeImageHeight = 30 * tooltipFontSize; cardTooltipLargeImageHeight = 30 * tooltipFontSize;
cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50); cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50);
cardTooltipLargeTextHeight = Math.max(100, 12 * tooltipFontSize - 20); cardTooltipLargeTextHeight = Math.max(100, 12 * tooltipFontSize - 20);
UIManager.put("ToolTip.font", cardTooltipFont); UIManager.put("ToolTip.font", new FontUIResource(cardTooltipFont));
// app - information boxes (only title, text controls by content)
// TODO: doesn't work
//UIManager.put("OptionPane.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 1.3f * dialogFont.getSize()))));
// game - player panel // game - player panel
playerPanelGuiScale = (float) (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_PLAYER_PANEL_SIZE, 14) / 14.0); playerPanelGuiScale = (float) (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_PLAYER_PANEL_SIZE, 14) / 14.0);

View file

@ -474,7 +474,7 @@ public final class GuiDisplayUtil {
public static void refreshThemeSettings() { public static void refreshThemeSettings() {
// apply Nimbus's look and fill // apply Nimbus's look and fill
// possible settings: // possible settings:
// https://docs.oracle.com/en%2Fjava%2Fjavase%2F17%2Fdocs%2Fapi%2F%2F/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html // https://docs.oracle.com/en/java/javase/23/docs/api/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html
// enable nimbus // enable nimbus
try { try {

View file

@ -105,21 +105,25 @@ public class RetroCardRenderer extends CardRenderer {
// = cardWidth - 2 x totalContentInset // = cardWidth - 2 x totalContentInset
protected int contentWidth; protected int contentWidth;
// Width of art / text box // dimensions of art / text box
protected int innerContentWidth; protected int innerContentWidth;
// X position of inside content protected int artHeight;
private int innerContentStart;
// How tall the name / type lines and P/T box are // How tall the name / type lines and P/T box are
protected static final float BOX_HEIGHT_FRAC = 0.065f; // x cardHeight protected static final float BOX_HEIGHT_FRAC = 0.051f; // x cardHeight
protected static final int BOX_HEIGHT_MIN = 16; protected static final float PT_BOX_HEIGHT_FRAC = 0.065f; // x cardHeight
protected static final int BOX_HEIGHT_MIN = 8;
protected int boxHeight; protected int boxHeight;
protected int ptBoxHeight;
// How far down the card is the type line placed? // How far down the card is the type line placed?
protected static final float TYPE_LINE_Y_FRAC = 0.52f; // x cardHeight protected static final float TYPE_LINE_Y_FRAC = 0.525f; // x cardHeight
protected int typeLineY; protected int typeLineY;
// The left and right frame inset
protected static final float INSET_WIDTH_FRAC = .055f;
protected int insetWidth;
// Possible sizes of rules text font // Possible sizes of rules text font
protected static final int[] RULES_TEXT_FONT_SIZES = {24, 18, 15, 12, 9}; protected static final int[] RULES_TEXT_FONT_SIZES = {24, 18, 15, 12, 9};
@ -156,14 +160,18 @@ public class RetroCardRenderer extends CardRenderer {
borderWidth = (int) Math.max( borderWidth = (int) Math.max(
BORDER_WIDTH_MIN, BORDER_WIDTH_MIN,
0.042 * cardWidth); 0.048 * cardWidth);
frameInset = (int) Math.max( frameInset = (int) Math.max(
BORDER_WIDTH_MIN, BORDER_WIDTH_MIN,
0.012 * cardWidth); 0.012 * cardWidth);
insetWidth = (int) Math.max(
BORDER_WIDTH_MIN,
INSET_WIDTH_FRAC * cardWidth);
// Content inset, just equal to border width // Content inset, just equal to border width
contentInset = borderWidth - frameInset; contentInset = borderWidth + insetWidth;
// Total content inset helper // Total content inset helper
totalContentInset = borderWidth + contentInset; totalContentInset = borderWidth + contentInset;
@ -175,14 +183,20 @@ public class RetroCardRenderer extends CardRenderer {
boxHeight = (int) Math.max( boxHeight = (int) Math.max(
BOX_HEIGHT_MIN, BOX_HEIGHT_MIN,
BOX_HEIGHT_FRAC * cardHeight); BOX_HEIGHT_FRAC * cardHeight);
ptBoxHeight = (int) Math.max(
// Art / text box size BOX_HEIGHT_MIN * 2,
innerContentWidth = (int) (cardWidth * 0.81f); PT_BOX_HEIGHT_FRAC * cardHeight);
innerContentStart = (int) (cardWidth * 0.095f);
// Type line at // Type line at
typeLineY = (int) (TYPE_LINE_Y_FRAC * cardHeight); typeLineY = (int) (TYPE_LINE_Y_FRAC * cardHeight);
// Art / text box size
innerContentWidth = (int) (cardWidth * .8f);
if (innerContentWidth < 160) {
innerContentWidth += 2;
}
artHeight = typeLineY - (borderWidth + boxHeight);
// Box text height // Box text height
boxTextHeight = getTextHeightForBoxHeight(boxHeight); boxTextHeight = getTextHeightForBoxHeight(boxHeight);
boxTextOffset = (boxHeight - boxTextHeight) / 2; boxTextOffset = (boxHeight - boxTextHeight) / 2;
@ -190,8 +204,8 @@ public class RetroCardRenderer extends CardRenderer {
boxTextFontNarrow = new Font("Arial Narrow", Font.PLAIN, boxTextHeight); boxTextFontNarrow = new Font("Arial Narrow", Font.PLAIN, boxTextHeight);
// Box text height // Box text height
ptTextHeight = getPTTextHeightForLineHeight(boxHeight); ptTextHeight = getPTTextHeightForLineHeight(ptBoxHeight);
ptTextOffset = (boxHeight - ptTextHeight) / 2; ptTextOffset = (ptBoxHeight - ptTextHeight) / 2;
ptTextFont = new Font("Arial", Font.BOLD, ptTextHeight); ptTextFont = new Font("Arial", Font.BOLD, ptTextHeight);
// Inset Frame Colors // Inset Frame Colors
@ -251,7 +265,7 @@ public class RetroCardRenderer extends CardRenderer {
protected void drawArt(Graphics2D g) { protected void drawArt(Graphics2D g) {
if (artImage != null) { if (artImage != null) {
boolean shouldPreserveAspect = false; boolean shouldPreserveAspect = true;
Rectangle2D sourceRect = ArtRect.RETRO.rect; Rectangle2D sourceRect = ArtRect.RETRO.rect;
if (cardView.getFrameStyle() != FrameStyle.RETRO) { if (cardView.getFrameStyle() != FrameStyle.RETRO) {
sourceRect = new Rectangle2D.Double(sourceRect.getX(), sourceRect.getY() + .01, sourceRect.getWidth(), sourceRect.getHeight()); sourceRect = new Rectangle2D.Double(sourceRect.getX(), sourceRect.getY() + .01, sourceRect.getWidth(), sourceRect.getHeight());
@ -266,8 +280,8 @@ public class RetroCardRenderer extends CardRenderer {
// Normal drawing of art from a source part of the card frame into the rect // Normal drawing of art from a source part of the card frame into the rect
drawArtIntoRect(g, drawArtIntoRect(g,
innerContentStart + frameInset, innerContentStart + frameInset * 2, contentInset + frameInset, borderWidth + boxHeight + frameInset,
innerContentWidth - frameInset * 2, typeLineY - borderWidth * 2 - frameInset, innerContentWidth - frameInset * 2, artHeight - frameInset * 2,
sourceRect, shouldPreserveAspect); sourceRect, shouldPreserveAspect);
} }
@ -291,28 +305,28 @@ public class RetroCardRenderer extends CardRenderer {
// Draw the textbox fill // Draw the textbox fill
drawTextboxBackground(g, textboxPaint, frameColors, borderPaint, isOriginalDualLand()); drawTextboxBackground(g, textboxPaint, frameColors, borderPaint, isOriginalDualLand());
drawInsetFrame(g, innerContentStart, innerContentStart + frameInset, drawInsetFrame(g, contentInset, borderWidth + boxHeight,
innerContentWidth, typeLineY - borderWidth * 2 + frameInset, borderPaint, cardView.getCardTypes().contains(CardType.LAND)); innerContentWidth, artHeight, borderPaint, cardView.getCardTypes().contains(CardType.LAND));
drawTypeLine(g, attribs, getCardTypeLine(), drawTypeLine(g, attribs, getCardTypeLine(),
innerContentStart, typeLineY + frameInset, contentInset, typeLineY,
innerContentWidth, boxHeight + frameInset); innerContentWidth, boxHeight);
// Draw the transform circle // Draw the transform circle
int nameOffset = drawTransformationCircle(g, attribs, borderPaint); int nameOffset = drawTransformationCircle(g, attribs, borderPaint);
// Draw the name line // Draw the name line
drawNameLine(g, attribs, cardView.getDisplayName(), manaCostString, drawNameLine(g, attribs, cardView.getDisplayName(), manaCostString,
innerContentStart + nameOffset, totalContentInset / 2 - frameInset, contentInset + nameOffset, borderWidth,
contentWidth - nameOffset - borderWidth); innerContentWidth);
// Draw the textbox rules // Draw the textbox rules
drawRulesText(g, textboxKeywords, textboxRules, drawRulesText(g, textboxKeywords, textboxRules,
innerContentStart + 2, typeLineY + boxHeight + 2, contentInset + 2, typeLineY + boxHeight + 2,
innerContentWidth - 4, (int) ((cardHeight - borderWidth * 2) * 0.32f)); innerContentWidth - 4, (int) ((cardHeight - borderWidth * 2) * 0.32f));
// Draw the bottom right stuff // Draw the bottom right stuff
drawBottomRight(g, borderPaint, boxColor); drawBottomRight(g, attribs, borderPaint, boxColor);
} }
private void drawInsetFrame(Graphics2D g2, int x, int y, int width, int height, Paint borderPaint, boolean isLand) { private void drawInsetFrame(Graphics2D g2, int x, int y, int width, int height, Paint borderPaint, boolean isLand) {
@ -390,7 +404,7 @@ public class RetroCardRenderer extends CardRenderer {
private void drawTextboxBackground(Graphics2D g, Paint textboxPaint, ObjectColor frameColors, Paint borderPaint, boolean isOriginalDual) { private void drawTextboxBackground(Graphics2D g, Paint textboxPaint, ObjectColor frameColors, Paint borderPaint, boolean isOriginalDual) {
g.setPaint(textboxPaint); g.setPaint(textboxPaint);
int x = innerContentStart; int x = contentInset;
int backgroundHeight = (int) ((cardHeight - borderWidth * 2) * 0.33f); int backgroundHeight = (int) ((cardHeight - borderWidth * 2) * 0.33f);
if (cardView.getCardTypes().contains(CardType.LAND)) { if (cardView.getCardTypes().contains(CardType.LAND)) {
@ -526,12 +540,14 @@ public class RetroCardRenderer extends CardRenderer {
} }
// Draw the P/T and/or Loyalty boxes // Draw the P/T and/or Loyalty boxes
protected void drawBottomRight(Graphics2D g, Paint borderPaint, Color fill) { protected void drawBottomRight(Graphics2D g, CardPanelAttributes attribs, Paint borderPaint, Color fill) {
// No bottom right for abilities // No bottom right for abilities
if (cardView.isAbility()) { if (cardView.isAbility()) {
return; return;
} }
int contentInset = borderWidth - frameInset;
// Where to start drawing the things // Where to start drawing the things
int curY = cardHeight - (int) (0.03f * cardHeight); int curY = cardHeight - (int) (0.03f * cardHeight);
@ -559,15 +575,16 @@ public class RetroCardRenderer extends CardRenderer {
// Draw PT box // Draw PT box
CardRendererUtils.drawRoundedBox(g, CardRendererUtils.drawRoundedBox(g,
x, curY - boxHeight, x, curY - ptBoxHeight,
partBoxWidth, boxHeight, partBoxWidth, ptBoxHeight,
contentInset, contentInset,
borderPaint, borderPaint,
isVehicle ? BOX_VEHICLE : fill); isVehicle ? BOX_VEHICLE : fill);
// Draw text // Draw text
Color defaultTextColor = Color.black; Color defaultTextColor = Color.black;
boolean defaultTextLight = true; boolean defaultTextLight = cardView.getColor().isMulticolored() || cardView.getColor().equals(ObjectColor.RED)
|| cardView.getColor().equals(ObjectColor.COLORLESS);
g.setFont(ptTextFont); g.setFont(ptTextFont);
// real PT info // real PT info
@ -592,7 +609,7 @@ public class RetroCardRenderer extends CardRenderer {
g.setColor(defaultTextColor); g.setColor(defaultTextColor);
// Advance // Advance
curY -= boxHeight; curY -= ptBoxHeight;
} }
// Is it a walker? (But don't draw the box if it's a non-permanent view // Is it a walker? (But don't draw the box if it's a non-permanent view
@ -696,14 +713,14 @@ public class RetroCardRenderer extends CardRenderer {
// does it have damage on it? // does it have damage on it?
if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) {
int x = cardWidth - partBoxWidth - borderWidth; int x = cardWidth - partBoxWidth - borderWidth;
int y = curY - boxHeight; int y = curY - ptBoxHeight;
String damage = String.valueOf(((PermanentView) cardView).getDamage()); String damage = String.valueOf(((PermanentView) cardView).getDamage());
g.setFont(ptTextFont); g.setFont(ptTextFont);
int txWidth = g.getFontMetrics().stringWidth(damage); int txWidth = g.getFontMetrics().stringWidth(damage);
g.setColor(Color.red); g.setColor(Color.red);
g.fillRect(x, y, partBoxWidth, boxHeight); g.fillRect(x, y, partBoxWidth, ptBoxHeight);
g.setColor(Color.white); g.setColor(Color.white);
g.drawRect(x, y, partBoxWidth, boxHeight); g.drawRect(x, y, partBoxWidth, ptBoxHeight);
g.drawString(damage, x + (partBoxWidth - txWidth) / 2, curY - 1); g.drawString(damage, x + (partBoxWidth - txWidth) / 2, curY - 1);
} }
} }
@ -998,11 +1015,7 @@ public class RetroCardRenderer extends CardRenderer {
// Get the text height for a given box height // Get the text height for a given box height
protected static int getTextHeightForBoxHeight(int h) { protected static int getTextHeightForBoxHeight(int h) {
if (h < 15) { return Math.max(10, (int) Math.ceil(.95 * h));
return h - 3;
} else {
return (int) Math.ceil(.6 * h);
}
} }
protected static int getPTTextHeightForLineHeight(int h) { protected static int getPTTextHeightForLineHeight(int h) {
@ -1056,8 +1069,6 @@ public class RetroCardRenderer extends CardRenderer {
protected static BufferedImage getBackgroundTexture(ObjectColor colors, Collection<CardType> types) { protected static BufferedImage getBackgroundTexture(ObjectColor colors, Collection<CardType> types) {
if (types.contains(CardType.LAND)) { if (types.contains(CardType.LAND)) {
return BG_IMG_LAND; return BG_IMG_LAND;
} else if (types.contains(CardType.ARTIFACT)) {
return BG_IMG_ARTIFACT;
} else if (colors.isMulticolored()) { } else if (colors.isMulticolored()) {
return BG_IMG_GOLD; return BG_IMG_GOLD;
} else if (colors.isWhite()) { } else if (colors.isWhite()) {
@ -1070,6 +1081,8 @@ public class RetroCardRenderer extends CardRenderer {
return BG_IMG_RED; return BG_IMG_RED;
} else if (colors.isGreen()) { } else if (colors.isGreen()) {
return BG_IMG_GREEN; return BG_IMG_GREEN;
} else if (types.contains(CardType.ARTIFACT)) {
return BG_IMG_ARTIFACT;
} else { } else {
// Colorless // Colorless
return BG_IMG_COLORLESS; return BG_IMG_COLORLESS;

View file

@ -28,6 +28,7 @@ import mage.target.common.TargetOpponent;
import mage.util.CardUtil; import mage.util.CardUtil;
import mage.util.MultiAmountMessage; import mage.util.MultiAmountMessage;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import mage.utils.testers.TestableDialogsRunner;
import java.io.File; import java.io.File;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -69,6 +70,7 @@ public final class SystemUtil {
private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add"; private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add";
private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take"; private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take";
private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give"; private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give";
private static final String COMMAND_SHOW_TEST_DIALOGS = "@show dialog";
private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented
private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented
private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand"; private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand";
@ -76,6 +78,7 @@ public final class SystemUtil {
private static final String COMMAND_SHOW_MY_HAND = "@show my hand"; private static final String COMMAND_SHOW_MY_HAND = "@show my hand";
private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library";
private static final Map<String, String> supportedCommands = new HashMap<>(); private static final Map<String, String> supportedCommands = new HashMap<>();
private static final TestableDialogsRunner testableDialogsRunner = new TestableDialogsRunner(); // for tests
static { static {
// special commands names in choose dialog // special commands names in choose dialog
@ -89,6 +92,7 @@ public final class SystemUtil {
supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY");
supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND");
supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY");
supportedCommands.put(COMMAND_SHOW_TEST_DIALOGS, "SHOW TEST DIALOGS");
} }
private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card]
@ -263,8 +267,13 @@ public final class SystemUtil {
public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) { public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) {
// fake test ability for triggers and events // fake test ability for triggers and events
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability"));
fakeSourceAbilityTemplate.setControllerId(feedbackPlayer.getId()); fakeSourceAbilityTemplate.setControllerId(feedbackPlayer.getId());
Card fakeSourceCard = feedbackPlayer.getLibrary().getFromTop(game);
if (fakeSourceCard != null) {
// set any existing card as source, so dialogs will show all GUI elements, including source and workable popup info
fakeSourceAbilityTemplate.setSourceId(fakeSourceCard.getId());
}
List<String> errorsList = new ArrayList<>(); List<String> errorsList = new ArrayList<>();
try { try {
@ -304,8 +313,9 @@ public final class SystemUtil {
// add default commands // add default commands
initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD)); initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD));
initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND)); initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND));
initLines.add(2, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE)); initLines.add(2, String.format("[%s]", COMMAND_SHOW_TEST_DIALOGS));
initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE)); initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE));
initLines.add(4, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE));
// collect all commands // collect all commands
CommandGroup currentGroup = null; CommandGroup currentGroup = null;
@ -438,7 +448,7 @@ public final class SystemUtil {
cardName = cardChoice.getChoice(); cardName = cardChoice.getChoice();
// amount // amount
int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", game); int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", null, game);
if (cardAmount == 0) { if (cardAmount == 0) {
break; break;
} }
@ -538,6 +548,11 @@ public final class SystemUtil {
break; break;
} }
case COMMAND_SHOW_TEST_DIALOGS: {
testableDialogsRunner.selectAndShowTestableDialog(feedbackPlayer, fakeSourceAbilityTemplate.copy(), game, opponent);
break;
}
default: { default: {
String mes = String.format("Unknown system command: %s", runGroup.name); String mes = String.format("Unknown system command: %s", runGroup.name);
errorsList.add(mes); errorsList.add(mes);

View file

@ -0,0 +1,65 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.announceX()
*
* @author JayDi85
*/
class AnnounceXTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
boolean isMana; // reason - for mana payment or another value
int min;
int max;
public AnnounceXTestableDialog(boolean isYou, boolean isMana, int min, int max) {
super(String.format("player.announceX(%s)", isYou ? "you" : "AI"),
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "");
this.isYou = isYou;
this.isMana = isMana;
this.min = min;
this.max = max;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String message = "<font color=green>message</font> with html";
int chooseRes;
chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
List<Boolean> isManas = Arrays.asList(false, true);
for (boolean isYou : isYous) {
for (boolean isMana : isManas) {
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10));
}
}
}
}

View file

@ -0,0 +1,74 @@
package mage.utils.testers;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetPermanentOrPlayer;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
abstract class BaseTestableDialog implements TestableDialog {
private final String group;
private final String name;
private final String description;
public BaseTestableDialog(String group, String name, String description) {
this.group = group;
this.name = name;
this.description = description;
}
@Override
final public String getGroup() {
return this.group;
}
@Override
final public String getName() {
return this.name;
}
@Override
final public String getDescription() {
return this.description;
}
@Override
final public void showResult(Player player, Game game, String result) {
// show message with result
game.informPlayer(player, result);
// reset game and gui (in most use cases it must return to player's priority)
game.firePriorityEvent(player.getId());
}
static Target createAnyTarget(int min, int max) {
return createAnyTarget(min, max, false);
}
private static Target createAnyTarget(int min, int max, boolean notTarget) {
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
}
static Target createCreatureTarget(int min, int max) {
return createCreatureTarget(min, max, false);
}
private static Target createCreatureTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max).withNotTarget(notTarget);
}
static Target createImpossibleTarget(int min, int max) {
return createImpossibleTarget(min, max, false);
}
private static Target createImpossibleTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max, new FilterCreaturePermanent(SubType.TROOPER, "rare type"), notTarget);
}
}

View file

@ -0,0 +1,111 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetAmount;
import mage.target.Targets;
import mage.target.common.TargetAnyTargetAmount;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.chooseTarget(amount)
*
* @author JayDi85
*/
class ChooseAmountTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
int distributeAmount;
int targetsMin;
int targetsMax;
public ChooseAmountTestableDialog(boolean isYou, String name, int distributeAmount, int targetsMin, int targetsMax) {
super(String.format("player.chooseTarget(%s, amount)", isYou ? "you" : "AI"),
name,
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax));
this.isYou = isYou;
this.distributeAmount = distributeAmount;
this.targetsMin = targetsMin;
this.targetsMax = targetsMax;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
Player choosingPlayer = this.isYou ? player : opponent;
// TODO: add "damage" word in ability text, so chooseTargetAmount an show diff dialog (due inner logic - distribute damage or 1/1)
boolean chooseRes = choosingPlayer.chooseTargetAmount(Outcome.Benefit, choosingTarget, source, game);
List<String> result = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// test game started with 2 players and 1 land on battlefield
// so it's better to use target limits like 0, 1, 3, 5, max
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
// up to
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5));
// need target
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5));
}
}
}

View file

@ -0,0 +1,111 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.Targets;
import mage.target.common.TargetCardInHand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choose(cards)
* - player.chooseTarget(cards)
*
* @author JayDi85
*/
class ChooseCardsTestableDialog extends BaseTestableDialog {
TargetCard target;
boolean isTargetChoice; // how to choose - by xxx.choose or xxx.chooseTarget
boolean isYou; // who choose - you or opponent
public ChooseCardsTestableDialog(boolean isTargetChoice, boolean notTarget, boolean isYou, String name, TargetCard target) {
super(String.format("%s(%s, %s, cards)",
isTargetChoice ? "player.chooseTarget" : "player.choose",
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString());
this.isTargetChoice = isTargetChoice;
this.target = target.withNotTarget(notTarget);
this.isYou = isYou;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
TargetCard choosingTarget = this.target.copy();
Player choosingPlayer = this.isYou ? player : opponent;
// make sure hand go first, so user can test diff type of targets
List<Card> all = new ArrayList<>();
all.addAll(choosingPlayer.getHand().getCards(game));
//all.addAll(choosingPlayer.getLibrary().getCards(game));
Cards choosingCards = new CardsImpl(all.stream().limit(100).collect(Collectors.toList()));
boolean chooseRes;
if (this.isTargetChoice) {
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingCards, choosingTarget, source, game);
} else {
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingCards, choosingTarget, source, game);
}
List<String> result = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// test game started with 2 players and 7 cards in hand and 1 draw
// so it's better to use target limits like 0, 1, 3, 9, max
FilterCard anyCard = StaticFilters.FILTER_CARD;
FilterCard impossibleCard = new FilterCard();
impossibleCard.add(SubType.TROOPER.getPredicate());
List<Boolean> notTargets = Arrays.asList(false, true);
List<Boolean> isYous = Arrays.asList(false, true);
List<Boolean> isTargetChoices = Arrays.asList(false, true);
for (boolean notTarget : notTargets) {
for (boolean isYou : isYous) {
for (boolean isTargetChoice : isTargetChoices) {
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0, X=0", new TargetCardInHand(0, 0, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1", new TargetCardInHand(1, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 3", new TargetCardInHand(3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 9", new TargetCardInHand(9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-1", new TargetCardInHand(0, 1, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-3", new TargetCardInHand(0, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-9", new TargetCardInHand(0, 9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand any", new TargetCardInHand(0, Integer.MAX_VALUE, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1-3", new TargetCardInHand(1, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-3", new TargetCardInHand(2, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-9", new TargetCardInHand(2, 9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 8-9", new TargetCardInHand(8, 9, anyCard)));
//
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0, X=0", new TargetCardInHand(0, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 1", new TargetCardInHand(1, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 3", new TargetCardInHand(3, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-1", new TargetCardInHand(0, 1, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-3", new TargetCardInHand(0, 3, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible any", new TargetCardInHand(0, Integer.MAX_VALUE, impossibleCard)));
}
}
}
}
}

View file

@ -0,0 +1,66 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.choices.*;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choose(choice)
*
* @author JayDi85
*/
class ChooseChoiceTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
Choice choice;
public ChooseChoiceTestableDialog(boolean isYou, String name, Choice choice) {
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"), name, choice.getClass().getSimpleName());
this.isYou = isYou;
this.choice = choice;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
Choice dialog = this.choice.copy();
boolean chooseRes = choosingPlayer.choose(Outcome.Benefit, dialog, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add("");
if (dialog.isKeyChoice()) {
String key = dialog.getChoiceKey();
result.add(String.format("* selected key: %s (%s)", key, dialog.getKeyChoices().getOrDefault(key, null)));
} else {
result.add(String.format("* selected value: %s", dialog.getChoice()));
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// TODO: add require option
// TODO: add ChoiceImpl with diff popup hints
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceBasicLandType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCardType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColor()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColorOrArtifact()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCreatureType(null, null))); // TODO: must be dynamic to pass game/source
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLandType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLeftOrRight()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoicePlaneswalkerType())); // TODO: must be dynamic to pass game/source
}
}
}

View file

@ -0,0 +1,69 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choosePile()
*
* @author JayDi85
*/
class ChoosePileTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
int pileSize1;
int pileSize2;
public ChoosePileTestableDialog(boolean isYou, int pileSize1, int pileSize2) {
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"), "pile sizes: " + pileSize1 + " and " + pileSize2, "");
this.isYou = isYou;
this.pileSize1 = pileSize1;
this.pileSize2 = pileSize2;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
// TODO: it's ok to show broken title - must add html support in windows's title someday
String mainMessage = "main <font color=green>message</font> with html" + CardUtil.getSourceLogName(game, source);
// random piles (make sure it contain good amount of cards)
List<Card> all = new ArrayList<>(game.getCards());
Collections.shuffle(all);
List<Card> pile1 = all.stream().limit(this.pileSize1).collect(Collectors.toList());
Collections.shuffle(all);
List<Card> pile2 = all.stream().limit(this.pileSize2).collect(Collectors.toList());
Player choosingPlayer = this.isYou ? player : opponent;
boolean chooseRes = choosingPlayer.choosePile(Outcome.Benefit, mainMessage, pile1, pile2, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
runner.registerDialog(new ChoosePileTestableDialog(isYou, 3, 5));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 10));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 30, 30));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 90, 90));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 10));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 0));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 0));
}
}
}

View file

@ -0,0 +1,123 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - target.choose()
* - target.chooseTarget()
* - player.choose(target)
* - player.chooseTarget(target)
*
* @author JayDi85
*/
class ChooseTargetTestableDialog extends BaseTestableDialog {
Target target;
boolean isPlayerChoice; // how to choose - by player.choose or by target.choose
boolean isTargetChoice; // how to choose - by xxx.choose or xxx.chooseTarget
boolean isYou; // who choose - you or opponent
public ChooseTargetTestableDialog(boolean isPlayerChoice, boolean isTargetChoice, boolean notTarget, boolean isYou, String name, Target target) {
super(String.format("%s%s(%s, %s)",
isPlayerChoice ? "player.choose" : "target.choose",
isTargetChoice ? "Target" : "", // chooseTarget or choose
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString());
this.isPlayerChoice = isPlayerChoice;
this.isTargetChoice = isTargetChoice;
this.target = target.withNotTarget(notTarget);
this.isYou = isYou;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Target choosingTarget = this.target.copy();
Player choosingPlayer = this.isYou ? player : opponent;
boolean chooseRes;
if (this.isPlayerChoice) {
// player.chooseXXX
if (this.isTargetChoice) {
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingTarget, source, game);
} else {
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingTarget, source, game);
}
} else {
// target.chooseXXX
if (this.isTargetChoice) {
chooseRes = choosingTarget.chooseTarget(Outcome.Benefit, choosingPlayer.getId(), source, game);
} else {
chooseRes = choosingTarget.choose(Outcome.Benefit, choosingPlayer.getId(), source, game);
}
}
List<String> result = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// test game started with 2 players and 1 land on battlefield
// so it's better to use target limits like 0, 1, 3, 5, max
List<Boolean> notTargets = Arrays.asList(false, true);
List<Boolean> isYous = Arrays.asList(false, true);
List<Boolean> isPlayerChoices = Arrays.asList(false, true);
List<Boolean> isTargetChoices = Arrays.asList(false, true);
for (boolean notTarget : notTargets) {
for (boolean isYou : isYous) {
for (boolean isTargetChoice : isTargetChoices) {
for (boolean isPlayerChoice : isPlayerChoices) {
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0))); // simulate X=0
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5))); // impossible on 3 targets
//
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)));
//
/*
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 0, e.g. X=0", createCreatureTarget(0, 0))); // simulate X=0
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 1", createCreatureTarget(1, 1)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 3", createCreatureTarget(3, 3)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 5", createCreatureTarget(5, 5)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures max", createCreatureTarget(0, Integer.MAX_VALUE)));
*/
}
}
}
}
}
}

View file

@ -0,0 +1,77 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.chooseUse()
*
* @author JayDi85
*/
class ChooseUseTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
String trueText;
String falseText;
String messageMain;
String messageAdditional;
public ChooseUseTestableDialog(boolean isYou, String name, String trueText, String falseText, String messageMain, String messageAdditional) {
super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"), name + buildName(trueText, falseText, messageMain, messageAdditional), "");
this.isYou = isYou;
this.trueText = trueText;
this.falseText = falseText;
this.messageMain = messageMain;
this.messageAdditional = messageAdditional;
}
private static String buildName(String trueText, String falseText, String messageMain, String messageAdditional) {
String buttonsInfo = (trueText == null ? "default" : "custom") + "/" + (falseText == null ? "default" : "custom");
String messagesInfo = (messageMain == null ? "-" : "main") + "/" + (messageAdditional == null ? "-" : "additional");
return String.format("buttons: %s, messages: %s", buttonsInfo, messagesInfo);
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
boolean chooseRes = choosingPlayer.chooseUse(
Outcome.Benefit,
messageMain,
messageAdditional == null ? null : messageAdditional + CardUtil.getSourceLogName(game, source),
trueText,
falseText,
source,
game
);
List<String> result = new ArrayList<>();
result.add(chooseRes ? "TRUE" : "FALSE");
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
String trueButton = "true button";
String falseButton = "false button";
String mainMessage = "main <font color=green>message</font> with html";
String additionalMessage = "additional main <font color=red>message</font> with html";
for (boolean isYou : isYous) {
runner.registerDialog(new ChooseUseTestableDialog(isYou, "", null, null, mainMessage, additionalMessage));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, falseButton, mainMessage, additionalMessage));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "", null, falseButton, mainMessage, additionalMessage));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, null, mainMessage, additionalMessage));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "error ", trueButton, falseButton, null, additionalMessage));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, falseButton, mainMessage, null));
runner.registerDialog(new ChooseUseTestableDialog(isYou, "error ", trueButton, falseButton, null, null));
}
}
}

View file

@ -0,0 +1,60 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.getAmount()
*
* @author JayDi85
*/
class GetAmountTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
int min;
int max;
public GetAmountTestableDialog(boolean isYou, int min, int max) {
super(String.format("player.getAmount(%s)", isYou ? "you" : "AI"),
String.format("from %d to %d", min, max), "");
this.isYou = isYou;
this.min = min;
this.max = max;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String message = "<font color=green>message</font> with html";
int chooseRes;
chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0));
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1));
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3));
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50));
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500));
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1));
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3));
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50));
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3));
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10));
runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10));
}
}
}

View file

@ -0,0 +1,31 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* How to use:
* - extends BaseTestableDialog
* - implement showDialog
* - create register with all possible sample dialogs
* - call register in main runner's constructor
*
* @author JayDi85
*/
interface TestableDialog {
String getGroup();
String getName();
String getDescription();
List<String> showDialog(Player player, Ability source, Game game, Player opponent);
void showResult(Player player, Game game, String result);
}

View file

@ -0,0 +1,202 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.choices.Choice;
import mage.choices.ChoiceHintType;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Helper class to create additional options in cheat menu - allow to test any game dialogs with any settings
* Allow to call game dialogs from you or from your opponent, e.g. from AI player
* <p>
* All existing human's choose dialogs (search by waitForResponse):
* <p>
* Support of single dialogs (can be called inside effects):
* [x] choose(target)
* [x] choose(cards)
* [x] choose(choice)
* [x] chooseTarget(target)
* [x] chooseTarget(cards)
* [x] chooseTargetAmount
* [x] chooseUse
* [x] choosePile
* [x] announceX
* [x] getAmount
* [ ] getMultiAmountWithIndividualConstraints // TODO: implement
* <p>
* Support of priority dialogs (can be called by game engine, some can be implemented in theory):
* --- priority
* --- playManaHandling
* --- activateSpecialAction
* --- activateAbility
* --- chooseAbilityForCast
* --- chooseLandOrSpellAbility
* --- chooseMode
* --- chooseMulligan
* --- chooseReplacementEffect
* --- chooseTriggeredAbility
* --- selectAttackers
* --- selectBlockers
* --- selectCombatGroup (part of selectBlockers)
* <p>
* Support of outdated dialogs (not used anymore)
* --- announceRepetitions (part of removed macro feature)
*
* @author JayDi85
*/
public class TestableDialogsRunner {
private final List<TestableDialog> dialogs = new ArrayList<>();
static final int LAST_SELECTED_GROUP_ID = 997;
static final int LAST_SELECTED_DIALOG_ID = 998;
// for better UX - save last selected options, so can return to it later
// it's ok to have it global, cause test mode for local and single user environment
static String lastSelectedGroup = null;
static TestableDialog lastSelectedDialog = null;
public TestableDialogsRunner() {
ChooseTargetTestableDialog.register(this);
ChooseCardsTestableDialog.register(this);
ChooseUseTestableDialog.register(this);
ChooseChoiceTestableDialog.register(this);
ChoosePileTestableDialog.register(this);
ChooseAmountTestableDialog.register(this);
AnnounceXTestableDialog.register(this);
GetAmountTestableDialog.register(this);
}
void registerDialog(TestableDialog dialog) {
this.dialogs.add(dialog);
}
public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) {
// select group or fast links
List<String> groups = this.dialogs.stream()
.map(TestableDialog::getGroup)
.distinct()
.sorted()
.collect(Collectors.toList());
Choice choice = prepareSelectGroupChoice(groups);
player.choose(Outcome.Benefit, choice, game);
String needGroup = null;
TestableDialog needDialog = null;
if (choice.getChoiceKey() != null) {
int needIndex = Integer.parseInt(choice.getChoiceKey());
if (needIndex == LAST_SELECTED_GROUP_ID && lastSelectedGroup != null) {
// fast link to group
needGroup = lastSelectedGroup;
} else if (needIndex == LAST_SELECTED_DIALOG_ID && lastSelectedDialog != null) {
// fast link to dialog
needGroup = lastSelectedDialog.getGroup();
needDialog = lastSelectedDialog;
} else if (needIndex < groups.size()) {
// group
needGroup = groups.get(needIndex);
}
}
if (needGroup == null) {
return;
}
// select dialog
if (needDialog == null) {
choice = prepareSelectDialogChoice(needGroup);
player.choose(Outcome.Benefit, choice, game);
if (choice.getChoiceKey() != null) {
int needIndex = Integer.parseInt(choice.getChoiceKey());
if (needIndex < this.dialogs.size()) {
needDialog = this.dialogs.get(needIndex);
}
}
}
if (needDialog == null) {
return;
}
// all fine, can show it and finish
lastSelectedGroup = needGroup;
lastSelectedDialog = needDialog;
List<String> resInfo = needDialog.showDialog(player, source, game, opponent);
needDialog.showResult(player, game, String.join("<br>", resInfo));
}
private Choice prepareSelectGroupChoice(List<String> groups) {
// try to choose group or fast links
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose dialogs group to run");
// main groups
int recNumber = 0;
for (int i = 0; i < groups.size(); i++) {
recNumber++;
String group = groups.get(i);
choice.withItem(
String.valueOf(i),
String.format("%02d. %s", recNumber, group),
recNumber,
ChoiceHintType.TEXT,
String.join("<br>", group)
);
}
// fast link to last group
String lastGroupInfo = String.format(" -> last group: %s", lastSelectedGroup == null ? "not used" : lastSelectedGroup);
choice.withItem(
String.valueOf(LAST_SELECTED_GROUP_ID),
lastGroupInfo,
-2,
ChoiceHintType.TEXT,
lastGroupInfo
);
// fast link to last dialog
String lastDialogName = (lastSelectedDialog == null ? "not used" : String.format("%s - %s",
lastSelectedDialog.getName(), lastSelectedDialog.getDescription()));
String lastDialogInfo = String.format(" -> last dialog: %s", lastDialogName);
choice.withItem(
String.valueOf(LAST_SELECTED_DIALOG_ID),
lastDialogInfo,
-1,
ChoiceHintType.TEXT,
lastDialogInfo
);
return choice;
}
private Choice prepareSelectDialogChoice(String needGroup) {
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose game dialog to run from " + needGroup);
int recNumber = 0;
for (int i = 0; i < this.dialogs.size(); i++) {
TestableDialog dialog = this.dialogs.get(i);
if (!dialog.getGroup().equals(needGroup)) {
continue;
}
recNumber++;
String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription());
choice.withItem(
String.valueOf(i),
String.format("%02d. %s", recNumber, info),
recNumber,
ChoiceHintType.TEXT,
String.join("<br>", info)
);
}
return choice;
}
}

View file

@ -19,6 +19,7 @@ public class CombatGroupView implements Serializable {
private final CardsView attackers = new CardsView(); private final CardsView attackers = new CardsView();
private final CardsView blockers = new CardsView(); private final CardsView blockers = new CardsView();
private final boolean isBlocked;
private String defenderName = ""; private String defenderName = "";
private final UUID defenderId; private final UUID defenderId;
@ -46,6 +47,7 @@ public class CombatGroupView implements Serializable {
blockers.put(id, new PermanentView(blocker, game.getCard(blocker.getId()), null, game)); blockers.put(id, new PermanentView(blocker, game.getCard(blocker.getId()), null, game));
} }
} }
isBlocked = combatGroup.getBlocked();
} }
public String getDefenderName() { public String getDefenderName() {
@ -63,4 +65,8 @@ public class CombatGroupView implements Serializable {
public UUID getDefenderId() { public UUID getDefenderId() {
return defenderId; return defenderId;
} }
public boolean isBlocked() {
return isBlocked;
}
} }

View file

@ -399,7 +399,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (effect != null if (effect != null
&& stackObject.getControllerId().equals(playerId)) { && stackObject.getControllerId().equals(playerId)) {
Target target = effect.getTarget(); Target target = effect.getTarget();
if (!target.doneChoosing(game)) { if (!target.isChoiceCompleted(game)) {
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) { for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
Game sim = game.createSimulationForAI(); Game sim = game.createSimulationForAI();
StackAbility newAbility = (StackAbility) stackObject.copy(); StackAbility newAbility = (StackAbility) stackObject.copy();
@ -848,10 +848,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (targets.isEmpty()) { if (targets.isEmpty()) {
return super.chooseTarget(outcome, cards, target, source, game); return super.chooseTarget(outcome, cards, target, source, game);
} }
if (!target.doneChoosing(game)) { if (!target.isChoiceCompleted(game)) {
for (UUID targetId : targets) { for (UUID targetId : targets) {
target.addTarget(targetId, source, game); target.addTarget(targetId, source, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(game)) {
targets.clear(); targets.clear();
return true; return true;
} }
@ -866,10 +866,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (targets.isEmpty()) { if (targets.isEmpty()) {
return super.choose(outcome, cards, target, source, game); return super.choose(outcome, cards, target, source, game);
} }
if (!target.doneChoosing(game)) { if (!target.isChoiceCompleted(game)) {
for (UUID targetId : targets) { for (UUID targetId : targets) {
target.add(targetId, game); target.add(targetId, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(game)) {
targets.clear(); targets.clear();
return true; return true;
} }

View file

@ -2,7 +2,6 @@ package mage.player.ai;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.Cards; import mage.cards.Cards;
@ -250,20 +249,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 {
} }
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
if (isUnderMe(game)) { if (isUnderMe(game)) {
return super.announceXMana(min, max, message, game, ability); return super.announceX(min, max, message, game, source, isManaPay);
} else { } else {
return getControllingPlayer(game).announceXMana(min, max, message, game, ability); return getControllingPlayer(game).announceX(min, max, message, game, source, isManaPay);
}
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
if (isUnderMe(game)) {
return super.announceXCost(min, max, message, game, ability, variableCost);
} else {
return getControllingPlayer(game).announceXCost(min, max, message, game, ability, variableCost);
} }
} }
@ -286,11 +276,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 {
} }
@Override @Override
public int getAmount(int min, int max, String message, Game game) { public int getAmount(int min, int max, String message, Ability source, Game game) {
if (isUnderMe(game)) { if (isUnderMe(game)) {
return super.getAmount(min, max, message, game); return super.getAmount(min, max, message, source, game);
} else { } else {
return getControllingPlayer(game).getAmount(min, max, message, game); return getControllingPlayer(game).getAmount(min, max, message, source, game);
} }
} }

View file

@ -160,7 +160,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
} }
newAbility.adjustTargets(game); newAbility.adjustTargets(game);
// add the different possible target option for the specific X value // add the different possible target option for the specific X value
if (!newAbility.getTargets().getUnchosen(game).isEmpty()) { if (newAbility.getTargets().getNextUnchosen(game) != null) {
addTargetOptions(options, newAbility, targetNum, game); addTargetOptions(options, newAbility, targetNum, game);
} }
} }

View file

@ -375,11 +375,11 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
} }
@Override @Override
public int getAmount(int min, int max, String message, Game game) { public int getAmount(int min, int max, String message, Ability source, Game game) {
if (this.isHuman()) { if (this.isHuman()) {
return RandomUtil.nextInt(max - min) + min; return RandomUtil.nextInt(max - min + 1) + min;
} }
return super.getAmount(min, max, message, game); return super.getAmount(min, max, message, source, game);
} }
} }

View file

@ -41,7 +41,6 @@ import mage.players.net.UserData;
import mage.target.Target; import mage.target.Target;
import mage.target.TargetAmount; import mage.target.TargetAmount;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetAttackingCreature; import mage.target.common.TargetAttackingCreature;
import mage.target.common.TargetDefender; import mage.target.common.TargetDefender;
import mage.target.targetpointer.TargetPointer; import mage.target.targetpointer.TargetPointer;
@ -387,9 +386,10 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean chooseMulligan(Game game) { public boolean chooseMulligan(Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
// TODO: rework to use existing chooseUse dialog, but do not remove chooseMulligan (AI must use special logic inside it)
while (canRespond()) { while (canRespond()) {
int nextHandSize = game.mulliganDownTo(playerId); int nextHandSize = game.mulliganDownTo(playerId);
String cardsCountInfo = nextHandSize + (nextHandSize == 1 ? " card" : " cards"); String cardsCountInfo = nextHandSize + (nextHandSize == 1 ? " card" : " cards");
@ -427,7 +427,11 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
}
if (message == null) {
throw new IllegalArgumentException("Wrong code usage: main message is null");
} }
MessageToClient messageToClient = new MessageToClient(message, secondMessage); MessageToClient messageToClient = new MessageToClient(message, secondMessage);
@ -620,7 +624,7 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean choose(Outcome outcome, Choice choice, Game game) { public boolean choose(Outcome outcome, Choice choice, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
if (choice.isKeyChoice() && choice.getKeyChoices().isEmpty()) { if (choice.isKeyChoice() && choice.getKeyChoices().isEmpty()) {
@ -683,7 +687,7 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) { public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
// choose one or multiple permanents // choose one or multiple permanents
@ -696,98 +700,85 @@ public class HumanPlayer extends PlayerImpl {
options = new HashMap<>(); options = new HashMap<>();
} }
while (canRespond()) { // stop on completed, e.g. X=0
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (possibleTargetIds == null || possibleTargetIds.isEmpty()) { return false;
return target.getTargets().size() >= target.getNumberOfTargets();
} }
while (canRespond()) {
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
if (target.getTargets().size() >= target.getNumberOfTargets()) {
// enable done button after min targets selected
if (target.getTargets().size() >= target.getMinNumberOfTargets()) {
required = false; required = false;
} }
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game); // stop on impossible selection
if (required && !target.canChoose(abilityControllerId, source, game)) {
break;
}
// responseId is null if a choice couldn't be automatically made // stop on nothing to choose
if (responseId == null) { Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
List<UUID> chosenTargets = target.getTargets(); if (required && possibleTargets.isEmpty()) {
options.put("chosenTargets", (Serializable) chosenTargets); break;
}
// MAKE A CHOICE
UUID autoChosenId = target.tryToAutoChoose(abilityControllerId, source, game);
if (autoChosenId != null && !target.contains(autoChosenId)) {
// auto-choose
target.add(autoChosenId, game);
// continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards)
} else {
// manual choose
options.put("chosenTargets", (Serializable) target.getTargets());
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), possibleTargetIds, required, getOptions(target, options)); game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), possibleTargets, required, getOptions(target, options));
} }
waitForResponse(game); waitForResponse(game);
responseId = getFixedResponseUUID(game); UUID responseId = getFixedResponseUUID(game);
}
if (responseId != null) { if (responseId != null) {
// selected some target // selected something
// remove selected // remove selected
if (target.getTargets().contains(responseId)) { if (target.contains(responseId)) {
target.remove(responseId); target.remove(responseId);
continue; continue;
} }
if (!possibleTargetIds.contains(responseId)) { if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) {
continue;
}
if (target instanceof TargetPermanent) {
if (((TargetPermanent) target).canTarget(abilityControllerId, responseId, source, game, false)) {
target.add(responseId, game); target.add(responseId, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(abilityControllerId, source, game)) {
return true; break;
} }
} }
} else { } else {
MageObject object = game.getObject(source); // stop on done/cancel button press
if (object instanceof Ability) { if (target.isChosen(game)) {
if (target.canTarget(responseId, (Ability) object, game)) { break;
if (target.getTargets().contains(responseId)) { // if already included remove it with
target.remove(responseId);
} else { } else {
target.addTarget(responseId, (Ability) object, game);
if (target.doneChoosing(game)) {
return true;
}
}
}
} else if (target.canTarget(responseId, game)) {
if (target.getTargets().contains(responseId)) { // if already included remove it with
target.remove(responseId);
} else {
target.addTarget(responseId, null, game);
if (target.doneChoosing(game)) {
return true;
}
}
}
}
} else {
// send other command like cancel or done (??sends other commands like concede??)
// auto-complete on all selected
if (target.getTargets().size() >= target.getNumberOfTargets()) {
return true;
}
// cancel/done button
if (!required) { if (!required) {
return false; // can stop at any moment
break;
} }
} }
} }
// continue to next target
}
}
return false; return target.isChosen(game) && target.getTargets().size() > 0;
} }
@Override @Override
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
// choose one or multiple targets // choose one or multiple targets
@ -799,64 +790,67 @@ public class HumanPlayer extends PlayerImpl {
Map<String, Serializable> options = new HashMap<>(); Map<String, Serializable> options = new HashMap<>();
while (canRespond()) { while (canRespond()) {
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
if (possibleTargetIds.isEmpty() if (possibleTargets.isEmpty()
|| target.getTargets().size() >= target.getNumberOfTargets()) { || target.getTargets().size() >= target.getMinNumberOfTargets()) {
required = false; required = false;
} }
// auto-choose
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game); UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
// responseId is null if a choice couldn't be automatically made // manual choice
if (responseId == null) { if (responseId == null) {
options.put("chosenTargets", (Serializable) target.getTargets());
List<UUID> chosenTargets = target.getTargets();
options.put("chosenTargets", (Serializable) chosenTargets);
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)),
possibleTargetIds, required, getOptions(target, options)); possibleTargets, required, getOptions(target, options));
} }
waitForResponse(game); waitForResponse(game);
responseId = getFixedResponseUUID(game); responseId = getFixedResponseUUID(game);
} }
if (responseId != null) { if (responseId != null) {
// remove selected // remove old target
if (target.getTargets().contains(responseId)) { if (target.contains(responseId)) {
target.remove(responseId); target.remove(responseId);
continue; continue;
} }
if (possibleTargetIds.contains(responseId)) { // add new target
if (possibleTargets.contains(responseId)) {
if (target.canTarget(abilityControllerId, responseId, source, game)) { if (target.canTarget(abilityControllerId, responseId, source, game)) {
target.addTarget(responseId, source, game); target.addTarget(responseId, source, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(abilityControllerId, source, game)) {
return true; return true;
} }
} }
} }
} else { } else {
if (target.getTargets().size() >= target.getNumberOfTargets()) { // done or cancel button pressed
return true; if (target.isChosen(game)) {
} // try to finish
if (!required) {
return false; return false;
} else {
if (!required) {
// can stop at any moment
return false;
}
} }
} }
} }
return false; return target.isChosen(game) && target.getTargets().size() > 0;
} }
private Map<String, Serializable> getOptions(Target target, Map<String, Serializable> options) { private Map<String, Serializable> getOptions(Target target, Map<String, Serializable> options) {
if (options == null) { if (options == null) {
options = new HashMap<>(); options = new HashMap<>();
} }
if (target.getTargets().size() >= target.getNumberOfTargets() if (target.getTargets().size() >= target.getMinNumberOfTargets()
&& !options.containsKey("UI.right.btn.text")) { && !options.containsKey("UI.right.btn.text")) {
options.put("UI.right.btn.text", "Done"); options.put("UI.right.btn.text", "Done");
} }
@ -867,10 +861,10 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
// choose one or multiple cards // ignore bad state
if (cards == null || cards.isEmpty()) { if (cards == null || cards.isEmpty()) {
return false; return false;
} }
@ -884,30 +878,37 @@ public class HumanPlayer extends PlayerImpl {
} }
while (canRespond()) { while (canRespond()) {
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
int count = cards.count(target.getFilter(), abilityControllerId, source, game);
if (count == 0
|| target.getTargets().size() >= target.getNumberOfTargets()) {
required = false;
}
List<UUID> chosenTargets = target.getTargets();
List<UUID> possibleTargets = new ArrayList<>(); List<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) { for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
possibleTargets.add(cardId); possibleTargets.add(cardId);
} }
} }
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
int count = cards.count(target.getFilter(), abilityControllerId, source, game);
if (count == 0
|| target.getTargets().size() >= target.getMinNumberOfTargets()) {
required = false;
}
// if nothing to choose then show dialog (user must see non selectable items and click on any of them) // if nothing to choose then show dialog (user must see non selectable items and click on any of them)
// TODO: need research - is it used?
if (required && possibleTargets.isEmpty()) { if (required && possibleTargets.isEmpty()) {
required = false; required = false;
} }
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets); // MAKE A CHOICE
UUID autoChosenId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
if (responseId == null) { if (autoChosenId != null && !target.contains(autoChosenId)) {
// auto-choose
target.add(autoChosenId, game);
// continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards)
} else {
// manual choose
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets); options.put("chosenTargets", (Serializable) target.getTargets());
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
} }
@ -918,29 +919,38 @@ public class HumanPlayer extends PlayerImpl {
} }
waitForResponse(game); waitForResponse(game);
responseId = getFixedResponseUUID(game); UUID responseId = getFixedResponseUUID(game);
}
if (responseId != null) { if (responseId != null) {
if (target.getTargets().contains(responseId)) { // if already included remove it with // selected something
// remove selected
if (target.contains(responseId)) {
target.remove(responseId); target.remove(responseId);
} else { continue;
if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { }
if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) {
target.add(responseId, game); target.add(responseId, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(abilityControllerId, source, game)) {
return true; return true;
} }
} }
}
} else { } else {
if (target.getTargets().size() >= target.getNumberOfTargets()) { // done or cancel button pressed
return true; if (target.isChosen(game)) {
} // try to finish
return false;
} else {
if (!required) { if (!required) {
// can stop at any moment
return false; return false;
} }
} }
} }
// continue to next target
}
}
return false; return false;
} }
@ -949,7 +959,7 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
if (cards == null || cards.isEmpty()) { if (cards == null || cards.isEmpty()) {
@ -968,7 +978,7 @@ public class HumanPlayer extends PlayerImpl {
boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source); boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source);
int count = cards.count(target.getFilter(), abilityControllerId, source, game); int count = cards.count(target.getFilter(), abilityControllerId, source, game);
if (count == 0 if (count == 0
|| target.getTargets().size() >= target.getNumberOfTargets()) { || target.getTargets().size() >= target.getMinNumberOfTargets()) {
required = false; required = false;
} }
@ -978,17 +988,16 @@ public class HumanPlayer extends PlayerImpl {
possibleTargets.add(cardId); possibleTargets.add(cardId);
} }
} }
// if nothing to choose then show dialog (user must see non selectable items and click on any of them) // if nothing to choose then show dialog (user must see non-selectable items and click on any of them)
if (required && possibleTargets.isEmpty()) { if (possibleTargets.isEmpty()) {
required = false; required = false;
} }
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets); UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
if (responseId == null) { if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets); options.put("chosenTargets", (Serializable) target.getTargets());
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
@ -1004,16 +1013,16 @@ public class HumanPlayer extends PlayerImpl {
} }
if (responseId != null) { if (responseId != null) {
if (target.getTargets().contains(responseId)) { // if already included remove it if (target.contains(responseId)) { // if already included remove it
target.remove(responseId); target.remove(responseId);
} else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { } else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) {
target.addTarget(responseId, source, game); target.addTarget(responseId, source, game);
if (target.doneChoosing(game)) { if (target.isChoiceCompleted(abilityControllerId, source, game)) {
return true; return true;
} }
} }
} else { } else {
if (target.getTargets().size() >= target.getNumberOfTargets()) { if (target.getTargets().size() >= target.getMinNumberOfTargets()) {
return true; return true;
} }
if (!required) { if (!required) {
@ -1030,7 +1039,7 @@ public class HumanPlayer extends PlayerImpl {
// choose amount // choose amount
// human can choose or un-choose MULTIPLE targets at once // human can choose or un-choose MULTIPLE targets at once
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
if (source == null) { if (source == null) {
@ -1057,11 +1066,12 @@ public class HumanPlayer extends PlayerImpl {
// 2. Distribute amount between selected targets // 2. Distribute amount between selected targets
// 1. Select targets // 1. Select targets
// TODO: rework to use existing chooseTarget instead custom select?
while (canRespond()) { while (canRespond()) {
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
boolean required = target.isRequired(source.getSourceId(), game); boolean required = target.isRequired(source.getSourceId(), game);
if (possibleTargetIds.isEmpty() if (possibleTargetIds.isEmpty()
|| target.getSize() >= target.getNumberOfTargets()) { || target.getSize() >= target.getMinNumberOfTargets()) {
required = false; required = false;
} }
@ -1069,7 +1079,6 @@ public class HumanPlayer extends PlayerImpl {
// responseId is null if a choice couldn't be automatically made // responseId is null if a choice couldn't be automatically made
if (responseId == null) { if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
List<UUID> possibleTargets = new ArrayList<>(); List<UUID> possibleTargets = new ArrayList<>();
for (UUID targetId : possibleTargetIds) { for (UUID targetId : possibleTargetIds) {
if (target.canTarget(abilityControllerId, targetId, source, game)) { if (target.canTarget(abilityControllerId, targetId, source, game)) {
@ -1083,7 +1092,7 @@ public class HumanPlayer extends PlayerImpl {
// selected // selected
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets); options.put("chosenTargets", (Serializable) target.getTargets());
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
} }
@ -1185,17 +1194,8 @@ public class HumanPlayer extends PlayerImpl {
// TODO: change pass and other states like passedUntilStackResolved for controlling player, not for "this" // TODO: change pass and other states like passedUntilStackResolved for controlling player, not for "this"
// TODO: check and change all "this" to controling player calls, many bugs with hand, mana, skips - https://github.com/magefree/mage/issues/2088 // TODO: check and change all "this" to controling player calls, many bugs with hand, mana, skips - https://github.com/magefree/mage/issues/2088
// TODO: use controlling player in all choose dialogs (and canRespond too, what's with take control of player AI?!) // TODO: use controlling player in all choose dialogs (and canRespond too, what's with take control of player AI?!)
UserData controllingUserData = this.userData; UserData controllingUserData = this.getControllingPlayersUserData(game);
if (canRespond()) { if (canRespond()) {
if (!isGameUnderControl()) {
Player player = game.getPlayer(getTurnControlledBy());
if (player instanceof HumanPlayer) {
controllingUserData = player.getUserData();
} else {
// TODO: add computer opponent here?!
}
}
// TODO: check that all skips and stops used from real controlling player // TODO: check that all skips and stops used from real controlling player
// like holdingPriority (is it a bug here?) // like holdingPriority (is it a bug here?)
if (getJustActivatedType() != null && !holdingPriority) { if (getJustActivatedType() != null && !holdingPriority) {
@ -1489,7 +1489,7 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public TriggeredAbility chooseTriggeredAbility(java.util.List<TriggeredAbility> abilities, Game game) { public TriggeredAbility chooseTriggeredAbility(java.util.List<TriggeredAbility> abilities, Game game) {
// choose triggered abilitity from list // choose triggered ability from list
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return abilities.isEmpty() ? null : abilities.get(0); return abilities.isEmpty() ? null : abilities.get(0);
} }
@ -1620,7 +1620,7 @@ public class HumanPlayer extends PlayerImpl {
protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) {
// choose mana to pay (from permanents or from pool) // choose mana to pay (from permanents or from pool)
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
// TODO: make canRespond cycle? // TODO: make canRespond cycle?
@ -1687,64 +1687,39 @@ public class HumanPlayer extends PlayerImpl {
} }
/** /**
* Gets the amount of mana the player want to spent for a x spell * Gets the amount of mana the player want to spend for an x spell
*
* @param min
* @param max
* @param message
* @param ability
* @param game
* @return
*/ */
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return min; return min;
} }
int xValue = 0; // fast calc on nothing to choose
while (canRespond()) { if (min >= max) {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, ability), min, max);
}
waitForResponse(game);
if (response.getInteger() != null) {
break;
}
// TODO: add response verify here
}
if (response.getInteger() != null) {
xValue = response.getInteger();
}
return xValue;
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
if (!canCallFeedback(game)) {
return min; return min;
} }
int xValue = 0; int xValue = min;
while (canRespond()) { while (canRespond()) {
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message, min, max); game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max);
} }
waitForResponse(game); waitForResponse(game);
if (response.getInteger() != null) { if (response.getInteger() == null) {
break; continue;
}
} }
if (response.getInteger() != null) {
xValue = response.getInteger(); xValue = response.getInteger();
if (xValue < min || xValue > max) {
continue;
} }
break;
}
return xValue; return xValue;
} }
@ -2183,28 +2158,37 @@ public class HumanPlayer extends PlayerImpl {
} }
@Override @Override
public int getAmount(int min, int max, String message, Game game) { public int getAmount(int min, int max, String message, Ability source, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return min; return min;
} }
// fast calc on nothing to choose
if (min >= max) {
return min;
}
int xValue = min;
while (canRespond()) { while (canRespond()) {
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message, min, max); game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max);
} }
waitForResponse(game); waitForResponse(game);
if (response.getInteger() != null) { if (response.getInteger() == null) {
break; continue;
}
} }
if (response.getInteger() != null) { xValue = response.getInteger();
return response.getInteger(); if (xValue < min || xValue > max) {
} else { continue;
return 0;
} }
break;
}
return xValue;
} }
@Override @Override
@ -2624,7 +2608,7 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean choosePile(Outcome outcome, String message, java.util.List<? extends Card> pile1, java.util.List<? extends Card> pile2, Game game) { public boolean choosePile(Outcome outcome, String message, java.util.List<? extends Card> pile1, java.util.List<? extends Card> pile2, Game game) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return true; return false;
} }
while (canRespond()) { while (canRespond()) {

View file

@ -0,0 +1,37 @@
package mage.cards.a;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.mana.AnyColorManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ARealmReborn extends CardImpl {
public ARealmReborn(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}");
// Other permanents you control have "{T}: Add one mana of any color."
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
new AnyColorManaAbility(), Duration.WhileOnBattlefield,
StaticFilters.FILTER_PERMANENTS, true
)));
}
private ARealmReborn(final ARealmReborn card) {
super(card);
}
@Override
public ARealmReborn copy() {
return new ARealmReborn(this);
}
}

View file

@ -0,0 +1,85 @@
package mage.cards.a;
import mage.MageInt;
import mage.MageItem;
import mage.MageObject;
import mage.abilities.common.CantBeCounteredSourceAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityControllerEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.ProtectionAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.game.Game;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AbsoluteVirtue extends CardImpl {
public AbsoluteVirtue(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{W}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(8);
this.toughness = new MageInt(8);
// This spell can't be countered.
this.addAbility(new CantBeCounteredSourceAbility());
// Flying
this.addAbility(FlyingAbility.getInstance());
// You have protection from each of your opponents.
this.addAbility(new SimpleStaticAbility(new GainAbilityControllerEffect(new AbsoluteVirtueAbility())));
}
private AbsoluteVirtue(final AbsoluteVirtue card) {
super(card);
}
@Override
public AbsoluteVirtue copy() {
return new AbsoluteVirtue(this);
}
}
class AbsoluteVirtueAbility extends ProtectionAbility {
public AbsoluteVirtueAbility() {
super(StaticFilters.FILTER_CARD);
}
private AbsoluteVirtueAbility(final AbsoluteVirtueAbility ability) {
super(ability);
}
@Override
public AbsoluteVirtueAbility copy() {
return new AbsoluteVirtueAbility(this);
}
@Override
public String getRule() {
return "protection from each of your opponents";
}
@Override
public boolean canTarget(MageObject source, Game game) {
return Optional
.ofNullable(source)
.map(MageItem::getId)
.map(game::getControllerId)
.map(uuid -> !game.getOpponents(this.getControllerId()).contains(uuid))
.orElse(true);
}
}

View file

@ -0,0 +1,37 @@
package mage.cards.a;
import java.util.UUID;
import mage.constants.SubType;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.mana.ColorlessManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
/**
* @author balazskristof
*/
public final class AdventurersInn extends CardImpl {
public AdventurersInn(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
this.subtype.add(SubType.TOWN);
// When this land enters, you gain 2 life.
this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2)));
// {T}: Add {C}.
this.addAbility(new ColorlessManaAbility());
}
private AdventurersInn(final AdventurersInn card) {
super(card);
}
@Override
public AdventurersInn copy() {
return new AdventurersInn(this);
}
}

View file

@ -0,0 +1,58 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.common.GainLifeControllerTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AerithGainsborough extends CardImpl {
private static final DynamicValue xValue = new CountersSourceCount(CounterType.P1P1);
public AerithGainsborough(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CLERIC);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Lifelink
this.addAbility(LifelinkAbility.getInstance());
// Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough.
this.addAbility(new GainLifeControllerTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())));
// When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough.
this.addAbility(new DiesSourceTriggeredAbility(new AddCountersAllEffect(
CounterType.P1P1.createInstance(), xValue, StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY
).setText("put X +1/+1 counters on each legendary creature you control, " +
"where X is the number of +1/+1 counters on {this}")));
}
private AerithGainsborough(final AerithGainsborough card) {
super(card);
}
@Override
public AerithGainsborough copy() {
return new AerithGainsborough(this);
}
}

View file

@ -0,0 +1,64 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.YouGainedLifeCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInYourGraveyard;
import mage.watchers.common.PlayerGainedLifeWatcher;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AerithLastAncient extends CardImpl {
private static final Condition condition = new YouGainedLifeCondition();
private static final Condition condition2 = new YouGainedLifeCondition(ComparisonType.MORE_THAN, 6);
public AerithLastAncient(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CLERIC);
this.subtype.add(SubType.DRUID);
this.power = new MageInt(3);
this.toughness = new MageInt(5);
// Lifelink
this.addAbility(LifelinkAbility.getInstance());
// Raise -- At the beginning of your end step, if you gained life this turn, return target creature card from your graveyard to your hand. If you gained 7 or more life this turn, return that card to the battlefield instead.
Ability ability = new BeginningOfEndStepTriggeredAbility(new ConditionalOneShotEffect(
new ReturnFromGraveyardToBattlefieldTargetEffect(), new ReturnFromGraveyardToHandTargetEffect(),
condition2, "return target creature card from your graveyard to your hand. " +
"If you gained 7 or more life this turn, return that card to the battlefield instead"
)).withInterveningIf(condition);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD));
this.addAbility(ability.withFlavorWord("Raise").addHint(ControllerGainedLifeCount.getHint()), new PlayerGainedLifeWatcher());
}
private AerithLastAncient(final AerithLastAncient card) {
super(card);
}
@Override
public AerithLastAncient copy() {
return new AerithLastAncient(this);
}
}

View file

@ -1,7 +1,5 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.EntersBattlefieldAbility;
@ -15,6 +13,8 @@ import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import java.util.UUID;
/** /**
* @author nantuko, BetaSteward_at_googlemail.com * @author nantuko, BetaSteward_at_googlemail.com
*/ */
@ -27,9 +27,6 @@ public final class AetherFigment extends CardImpl {
this.power = new MageInt(1); this.power = new MageInt(1);
this.toughness = new MageInt(1); this.toughness = new MageInt(1);
// Aether Figment can't be blocked.
this.addAbility(new CantBeBlockedSourceAbility());
// Kicker {3} // Kicker {3}
this.addAbility(new KickerAbility("{3}")); this.addAbility(new KickerAbility("{3}"));
@ -40,6 +37,9 @@ public final class AetherFigment extends CardImpl {
"If {this} was kicked, it enters with two +1/+1 counters on it.", "If {this} was kicked, it enters with two +1/+1 counters on it.",
""); "");
this.addAbility(ability); this.addAbility(ability);
// Aether Figment can't be blocked.
this.addAbility(new CantBeBlockedSourceAbility());
} }
private AetherFigment(final AetherFigment card) { private AetherFigment(final AetherFigment card) {

View file

@ -119,7 +119,7 @@ class AetherRefineryTokenEffect extends OneShotEffect {
return true; return true;
} }
int numberToPay = controller.getAmount(1, totalEnergy, int numberToPay = controller.getAmount(1, totalEnergy,
"Pay one or more {E}", game); "Pay one or more {E}", source, game);
Cost cost = new PayEnergyCost(numberToPay); Cost cost = new PayEnergyCost(numberToPay);
if (cost.pay(source, game, source, source.getControllerId(), true)) { if (cost.pay(source, game, source, source.getControllerId(), true)) {

View file

@ -33,7 +33,7 @@ public final class AetherRevolt extends CardImpl {
// Revolt -- As long as a permanent you controlled left the battlefield this turn, if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, it deals that much damage plus 2 instead. // Revolt -- As long as a permanent you controlled left the battlefield this turn, if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, it deals that much damage plus 2 instead.
this.addAbility(new SimpleStaticAbility(new ConditionalReplacementEffect( this.addAbility(new SimpleStaticAbility(new ConditionalReplacementEffect(
new AetherRevoltEffect(), RevoltCondition.instance new AetherRevoltEffect(), RevoltCondition.instance
).setText("As long as a permanent you controlled left the battlefield this turn, " ).setText("as long as a permanent left the battlefield under your control this turn, "
+ "if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, " + "if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, "
+ "it deals that much damage plus 2 instead") + "it deals that much damage plus 2 instead")
).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher());

View file

@ -68,7 +68,7 @@ class AetherSpikeEffect extends OneShotEffect {
} }
int numberToPay = controller.getAmount( int numberToPay = controller.getAmount(
0, controller.getCountersCount(CounterType.ENERGY), 0, controller.getCountersCount(CounterType.ENERGY),
"How many {E} do you want to pay?", game "How many {E} do you want to pay?", source, game
); );
Cost cost = new PayEnergyCost(numberToPay); Cost cost = new PayEnergyCost(numberToPay);
int numberPaid = 0; int numberPaid = 0;

View file

@ -89,7 +89,7 @@ class AetherbornMarauderEffect extends OneShotEffect {
int numberOfCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1); int numberOfCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1);
int numberToMove = 1; int numberToMove = 1;
if (numberOfCounters > 1) { if (numberOfCounters > 1) {
numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", game); numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", source, game);
} }
if (numberToMove > 0) { if (numberToMove > 0) {
fromPermanent.removeCounters(CounterType.P1P1.createInstance(numberToMove), source, game); fromPermanent.removeCounters(CounterType.P1P1.createInstance(numberToMove), source, game);

View file

@ -150,7 +150,6 @@ class AetherspoutsEffect extends OneShotEffect {
target = new TargetCard(Zone.BATTLEFIELD, new FilterCard("order to put on bottom of library (last chosen will be bottommost card)")); target = new TargetCard(Zone.BATTLEFIELD, new FilterCard("order to put on bottom of library (last chosen will be bottommost card)"));
while (player.canRespond() && cards.size() > 1) { while (player.canRespond() && cards.size() > 1) {
player.choose(Outcome.Neutral, cards, target, source, game); player.choose(Outcome.Neutral, cards, target, source, game);
Card card = cards.get(target.getFirstTarget(), game); Card card = cards.get(target.getFirstTarget(), game);
if (card != null) { if (card != null) {
cards.remove(card); cards.remove(card);
@ -158,8 +157,10 @@ class AetherspoutsEffect extends OneShotEffect {
if (permanent != null) { if (permanent != null) {
toLibrary.add(permanent); toLibrary.add(permanent);
} }
}
target.clearChosen(); target.clearChosen();
} else {
break;
}
} }
if (cards.size() == 1) { if (cards.size() == 1) {
Card card = cards.get(cards.iterator().next(), game); Card card = cards.get(cards.iterator().next(), game);

View file

@ -1,38 +1,38 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.condition.common.RevoltCondition; import mage.abilities.condition.common.RevoltCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.*; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.AbilityWord;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterPermanentCard;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil;
import mage.watchers.common.RevoltWatcher; import mage.watchers.common.RevoltWatcher;
import java.util.UUID;
/** /**
*
* @author fireshoes * @author fireshoes
*/ */
public final class AidFromTheCowl extends CardImpl { public final class AidFromTheCowl extends CardImpl {
private static final String ruleText = "<i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn, "
+ "reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, you may put it on the bottom of your library.";
public AidFromTheCowl(UUID ownerId, CardSetInfo setInfo) { public AidFromTheCowl(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}");
// <i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn, // <i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn,
// reveal the top card of your library. If it is a permanent card, you may put it onto the battlefield. Otherwise, put it on the bottom of your library. // reveal the top card of your library. If it is a permanent card, you may put it onto the battlefield. Otherwise, put it on the bottom of your library.
TriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(new AidFromTheCowlEffect()); this.addAbility(new BeginningOfEndStepTriggeredAbility(new AidFromTheCowlEffect())
this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, RevoltCondition.instance, ruleText).addHint(RevoltCondition.getHint()), new RevoltWatcher()); .withInterveningIf(RevoltCondition.instance)
.setAbilityWord(AbilityWord.REVOLT)
.addHint(RevoltCondition.getHint()), new RevoltWatcher());
} }
private AidFromTheCowl(final AidFromTheCowl card) { private AidFromTheCowl(final AidFromTheCowl card) {
@ -49,7 +49,8 @@ class AidFromTheCowlEffect extends OneShotEffect {
AidFromTheCowlEffect() { AidFromTheCowlEffect() {
super(Outcome.PutCreatureInPlay); super(Outcome.PutCreatureInPlay);
this.staticText = "reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, you may put that card on the bottom of your library"; this.staticText = "reveal the top card of your library. If it's a permanent card, " +
"you may put it onto the battlefield. Otherwise, you may put that card on the bottom of your library";
} }
private AidFromTheCowlEffect(final AidFromTheCowlEffect effect) { private AidFromTheCowlEffect(final AidFromTheCowlEffect effect) {
@ -64,26 +65,21 @@ class AidFromTheCowlEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source); if (controller == null || !controller.getLibrary().hasCards()) {
if (controller == null || sourceObject == null) {
return false; return false;
} }
if (controller.getLibrary().hasCards()) {
Card card = controller.getLibrary().getFromTop(game); Card card = controller.getLibrary().getFromTop(game);
Cards cards = new CardsImpl(card); if (card == null) {
controller.revealCards(sourceObject.getIdName(), cards, game); return false;
}
if (card != null) { controller.revealCards(CardUtil.getSourceIdName(game, source), new CardsImpl(card), game);
if (new FilterPermanentCard().match(card, game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) { if (card.isPermanent(game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game); controller.moveCards(card, Zone.BATTLEFIELD, source, game);
} else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) { } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) {
controller.putCardsOnBottomOfLibrary(cards, game, source, false); controller.putCardsOnBottomOfLibrary(card, game, source, false);
} else { } else {
game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library."); game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library.");
} }
}
}
return true; return true;
} }
} }

View file

@ -1,10 +1,8 @@
package mage.cards.a; package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.RevoltCondition; import mage.abilities.condition.common.RevoltCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -33,13 +31,10 @@ public final class AirdropAeronauts extends CardImpl {
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());
// <i>Revolt</i> &mdash; When Airdrop Aeronauts enters the battlefield, if a permanent you controlled left the battlefield this turn, you gain 5 life. // <i>Revolt</i> &mdash; When Airdrop Aeronauts enters the battlefield, if a permanent you controlled left the battlefield this turn, you gain 5 life.
Ability ability = new ConditionalInterveningIfTriggeredAbility( this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5))
new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5), false), .withInterveningIf(RevoltCondition.instance)
RevoltCondition.instance, "When {this} enters, " + .setAbilityWord(AbilityWord.REVOLT)
"if a permanent you controlled left the battlefield this turn, you gain 5 life." .addHint(RevoltCondition.getHint()), new RevoltWatcher());
);
ability.setAbilityWord(AbilityWord.REVOLT);
this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher());
} }
private AirdropAeronauts(final AirdropAeronauts card) { private AirdropAeronauts(final AirdropAeronauts card) {

View file

@ -0,0 +1,49 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesThisOrAnotherTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.common.TargetOpponent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AlBhedSalvagers extends CardImpl {
public AlBhedSalvagers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ARTIFICER);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life.
Ability ability = new DiesThisOrAnotherTriggeredAbility(
new LoseLifeTargetEffect(1), false,
StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT
);
ability.addEffect(new GainLifeEffect(1).concatBy("and"));
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
}
private AlBhedSalvagers(final AlBhedSalvagers card) {
super(card);
}
@Override
public AlBhedSalvagers copy() {
return new AlBhedSalvagers(this);
}
}

View file

@ -27,7 +27,7 @@ public final class AlignedHeart extends CardImpl {
// Flurry -- Whenever you cast your second spell each turn, put a rally counter on this enchantment. Then create a 1/1 white Monk creature token with prowess for each rally counter on it. // Flurry -- Whenever you cast your second spell each turn, put a rally counter on this enchantment. Then create a 1/1 white Monk creature token with prowess for each rally counter on it.
Ability ability = new FlurryAbility(new AddCountersSourceEffect(CounterType.RALLY.createInstance())); Ability ability = new FlurryAbility(new AddCountersSourceEffect(CounterType.RALLY.createInstance()));
ability.addEffect(new CreateTokenEffect(new MonasteryMentorToken(), xValue) ability.addEffect(new CreateTokenEffect(new MonasteryMentorToken(), xValue)
.setText("then create a 1/1 white Monk creature token with prowess for each rally counter on it")); .setText("Then create a 1/1 white Monk creature token with prowess for each rally counter on it"));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -0,0 +1,54 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition;
import mage.abilities.decorator.ConditionalCostModificationEffect;
import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.PartnerWithAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AlisaieLeveilleur extends CardImpl {
public AlisaieLeveilleur(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.ELF);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// Partner with Alphinaud Leveilleur
this.addAbility(new PartnerWithAbility("Alphinaud Leveilleur"));
// First strike
this.addAbility(FirstStrikeAbility.getInstance());
// Dualcast -- The second spell you cast each turn costs {2} less to cast.
this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect(
new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 2),
YouCastExactOneSpellThisTurnCondition.instance, "the second spell you cast each turn costs {2} less to cast"
)).withFlavorWord("Dualcast"));
}
private AlisaieLeveilleur(final AlisaieLeveilleur card) {
super(card);
}
@Override
public AlisaieLeveilleur copy() {
return new AlisaieLeveilleur(this);
}
}

View file

@ -4,7 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.IndestructibleAbility; import mage.abilities.keyword.IndestructibleAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -35,7 +35,7 @@ public final class AlmostPerfect extends CardImpl {
this.addAbility(new EnchantAbility(auraTarget)); this.addAbility(new EnchantAbility(auraTarget));
// Enchanted creature has base power and toughness 9/10 and has indestructible. // Enchanted creature has base power and toughness 9/10 and has indestructible.
Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(9, 10)); Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(9, 10, AttachmentType.AURA));
ability.addEffect(new GainAbilityAttachedEffect( ability.addEffect(new GainAbilityAttachedEffect(
IndestructibleAbility.getInstance(), AttachmentType.AURA IndestructibleAbility.getInstance(), AttachmentType.AURA
).setText("and has indestructible")); ).setText("and has indestructible"));

View file

@ -0,0 +1,48 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.CastSecondSpellTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.PartnerWithAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AlphinaudLeveilleur extends CardImpl {
public AlphinaudLeveilleur(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.ELF);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(4);
// Partner with Alisaie Leveilleur
this.addAbility(new PartnerWithAbility("Alisaie Leveilleur"));
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Eukrasia -- Whenever you cast your second spell each turn, draw a card.
this.addAbility(new CastSecondSpellTriggeredAbility(new DrawCardSourceControllerEffect(1)).withFlavorWord("Eukrasia"));
}
private AlphinaudLeveilleur(final AlphinaudLeveilleur card) {
super(card);
}
@Override
public AlphinaudLeveilleur copy() {
return new AlphinaudLeveilleur(this);
}
}

View file

@ -0,0 +1,144 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTargets;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class AltairIbnLaAhad extends CardImpl {
private static final FilterCard filter = new FilterCreatureCard("Assassin creature card from your graveyard");
static {
filter.add(SubType.ASSASSIN.getPredicate());
}
public AltairIbnLaAhad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ASSASSIN);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// First strike
this.addAbility(FirstStrikeAbility.getInstance());
// Whenever Altair Ibn-La'Ahad attacks, exile up to one target Assassin creature card from your graveyard with a memory counter on it. Then for each creature card you own in exile with a memory counter on it, create a tapped and attacking token that's a copy of it. Exile those tokens at end of combat.
Ability ability = new AttacksTriggeredAbility(new AltairIbnLaAhadExileEffect());
ability.addEffect(new AltairIbnLaAhadTokenEffect());
ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter));
this.addAbility(ability);
}
private AltairIbnLaAhad(final AltairIbnLaAhad card) {
super(card);
}
@Override
public AltairIbnLaAhad copy() {
return new AltairIbnLaAhad(this);
}
}
class AltairIbnLaAhadExileEffect extends OneShotEffect {
AltairIbnLaAhadExileEffect() {
super(Outcome.Benefit);
staticText = "exile up to one target Assassin creature card from your graveyard with a memory counter on it";
}
private AltairIbnLaAhadExileEffect(final AltairIbnLaAhadExileEffect effect) {
super(effect);
}
@Override
public AltairIbnLaAhadExileEffect copy() {
return new AltairIbnLaAhadExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (player == null || card == null) {
return false;
}
player.moveCards(card, Zone.EXILED, source, game);
card.addCounters(CounterType.MEMORY.createInstance(), source, game);
return true;
}
}
class AltairIbnLaAhadTokenEffect extends OneShotEffect {
AltairIbnLaAhadTokenEffect() {
super(Outcome.Benefit);
staticText = "Then for each creature card you own in exile with a memory counter on it, " +
"create a tapped and attacking token that's a copy of it. Exile those tokens at end of combat";
}
private AltairIbnLaAhadTokenEffect(final AltairIbnLaAhadTokenEffect effect) {
super(effect);
}
@Override
public AltairIbnLaAhadTokenEffect copy() {
return new AltairIbnLaAhadTokenEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Set<Card> cards = game
.getExile()
.getAllCards(game, source.getControllerId())
.stream()
.filter(card -> card.getCounters(game).containsKey(CounterType.MEMORY))
.filter(card -> card.isCreature(game))
.collect(Collectors.toSet());
if (cards.isEmpty()) {
return false;
}
Set<Permanent> permanents = new HashSet<>();
for (Card card : cards) {
CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(
source.getControllerId(), null,
false, 1, true, true
);
effect.setSavedPermanent(new PermanentCard(card, source.getControllerId(), game));
effect.apply(game, source);
permanents.addAll(effect.getAddedPermanents());
}
game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(
new ExileTargetEffect("exile those tokens")
.setTargetPointer(new FixedTargets(permanents, game))
), source);
return true;
}
}

View file

@ -47,7 +47,7 @@ public final class AnafenzaUnyieldingLineage extends CardImpl {
// Whenever another nontoken creature you control dies, Anafenza endures 2. // Whenever another nontoken creature you control dies, Anafenza endures 2.
this.addAbility(new DiesCreatureTriggeredAbility( this.addAbility(new DiesCreatureTriggeredAbility(
new EndureSourceEffect(2, "{this}"), true, filter new EndureSourceEffect(2, "{this}"), false, filter
)); ));
} }

View file

@ -0,0 +1,113 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.FilterSpell;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.target.common.TargetOpponent;
import mage.watchers.common.ManaPaidSourceWatcher;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AncientCellarspawn extends CardImpl {
private static final FilterCard filter
= new FilterCard("each spell you cast that's a Demon, Horror, or Nightmare");
private static final FilterSpell filter2
= new FilterSpell("a spell, if the amount of mana spent to cast it was less than its mana value");
static {
filter.add(Predicates.or(
SubType.DEMON.getPredicate(),
SubType.HORROR.getPredicate(),
SubType.NIGHTMARE.getPredicate()
));
filter2.add(AncientCellarspawnPredicate.instance);
}
public AncientCellarspawn(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{B}{B}");
this.subtype.add(SubType.HORROR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.
this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1)));
// Whenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.
Ability ability = new SpellCastControllerTriggeredAbility(
new LoseLifeTargetEffect(AncientCellarspawnValue.instance)
.setText("target opponent loses life equal to the difference"),
filter2, false
);
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
}
private AncientCellarspawn(final AncientCellarspawn card) {
super(card);
}
@Override
public AncientCellarspawn copy() {
return new AncientCellarspawn(this);
}
}
enum AncientCellarspawnPredicate implements Predicate<StackObject> {
instance;
@Override
public boolean apply(StackObject input, Game game) {
return ManaPaidSourceWatcher.getTotalPaid(input.getSourceId(), game) < input.getManaValue();
}
}
enum AncientCellarspawnValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return Optional
.ofNullable(effect.getValue("spellCast"))
.filter(Spell.class::isInstance)
.map(Spell.class::cast)
.map(spell -> Math.abs(spell.getManaValue() - ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game)))
.orElse(0);
}
@Override
public AncientCellarspawnValue copy() {
return this;
}
@Override
public String getMessage() {
return "";
}
@Override
public String toString() {
return "1";
}
}

View file

@ -2,9 +2,8 @@ package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint; import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
@ -13,7 +12,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import java.util.UUID; import java.util.UUID;
@ -23,10 +21,8 @@ import java.util.UUID;
*/ */
public final class AngelicObserver extends CardImpl { public final class AngelicObserver extends CardImpl {
private static final FilterPermanent filter private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CITIZEN, "Citizens");
= new FilterControlledPermanent(SubType.CITIZEN, "Citizen you control"); private static final Hint hint = new ValueHint("Citizens you control", new PermanentsOnBattlefieldCount(filter));
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
private static final Hint hint = new ValueHint("Citizens you control", xValue);
public AngelicObserver(UUID ownerId, CardSetInfo setInfo) { public AngelicObserver(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}");
@ -37,10 +33,7 @@ public final class AngelicObserver extends CardImpl {
this.toughness = new MageInt(3); this.toughness = new MageInt(3);
// This spell costs {1} less to cast for each Citizen you control. // This spell costs {1} less to cast for each Citizen you control.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint));
Zone.ALL,
new SpellCostReductionForEachSourceEffect(1, xValue).setCanWorksOnStackOnly(true)
).setRuleAtTheTop(true).addHint(hint));
// Flying // Flying
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());

View file

@ -33,7 +33,7 @@ public final class ArashinSunshield extends CardImpl {
// When this creature enters, exile up to two target cards from a single graveyard. // When this creature enters, exile up to two target cards from a single graveyard.
Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect());
ability.addTarget(new TargetCardInASingleGraveyard(0, 2, StaticFilters.FILTER_CARDS_NON_LAND)); ability.addTarget(new TargetCardInASingleGraveyard(0, 2, StaticFilters.FILTER_CARD_CARDS));
this.addAbility(ability); this.addAbility(ability);
// {W}, {T}: Tap target creature. // {W}, {T}: Tap target creature.

View file

@ -137,7 +137,7 @@ class ArchdruidsCharmMode1Effect extends SearchEffect {
private void setText() { private void setText() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Search your library for "); sb.append("Search your library for ");
if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { if (target.getMinNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' ');
sb.append(target.getTargetName()).append(revealCards ? " and reveal them." : "."); sb.append(target.getTargetName()).append(revealCards ? " and reveal them." : ".");
} else { } else {

View file

@ -0,0 +1,75 @@
package mage.cards.a;
import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.CounterType;
import mage.filter.FilterSpell;
import mage.filter.StaticFilters;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ArdbertWarriorOfDarkness extends CardImpl {
private static final FilterSpell filter = new FilterSpell("a white spell");
private static final FilterSpell filter2 = new FilterSpell("a black spell");
static {
filter.add(new ColorPredicate(ObjectColor.WHITE));
filter2.add(new ColorPredicate(ObjectColor.BLACK));
}
public ArdbertWarriorOfDarkness(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Whenever you cast a white spell, put a +1/+1 counter on each legendary creature you control. They gain vigilance until end of turn.
Ability ability = new SpellCastControllerTriggeredAbility(new AddCountersAllEffect(
CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY
), filter, false);
ability.addEffect(new GainAbilityAllEffect(
VigilanceAbility.getInstance(), Duration.EndOfTurn,
StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY
).setText("They gain vigilance until end of turn"));
this.addAbility(ability);
// Whenever you cast a black spell, put a +1/+1 counter on each legendary creature you control. They gain menace until end of turn.
ability = new SpellCastControllerTriggeredAbility(new AddCountersAllEffect(
CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY
), filter2, false);
ability.addEffect(new GainAbilityAllEffect(
new MenaceAbility(false), Duration.EndOfTurn,
StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY
).setText("They gain menace until end of turn"));
this.addAbility(ability);
}
private ArdbertWarriorOfDarkness(final ArdbertWarriorOfDarkness card) {
super(card);
}
@Override
public ArdbertWarriorOfDarkness copy() {
return new ArdbertWarriorOfDarkness(this);
}
}

View file

@ -0,0 +1,107 @@
package mage.cards.a;
import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInGraveyard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ArdynTheUsurper extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent(SubType.DEMON, "Demons");
public ArdynTheUsurper(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.ELDER);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.NOBLE);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Demons you control have menace, lifelink, and haste.
Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect(
new MenaceAbility(false), Duration.WhileOnBattlefield, filter
));
ability.addEffect(new GainAbilityControlledEffect(
LifelinkAbility.getInstance(), Duration.WhileOnBattlefield, filter
).setText(", lifelink"));
ability.addEffect(new GainAbilityControlledEffect(
HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter
).setText(", and haste"));
this.addAbility(ability);
// Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.
ability = new BeginningOfCombatTriggeredAbility(new ArdynTheUsurperEffect());
ability.addTarget(new TargetCardInGraveyard(
0, 1, StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD
));
this.addAbility(ability.withFlavorWord("Starscourge"));
}
private ArdynTheUsurper(final ArdynTheUsurper card) {
super(card);
}
@Override
public ArdynTheUsurper copy() {
return new ArdynTheUsurper(this);
}
}
class ArdynTheUsurperEffect extends OneShotEffect {
ArdynTheUsurperEffect() {
super(Outcome.Benefit);
staticText = "at the beginning of combat on your turn, exile up to one target creature card from a graveyard. " +
"If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon";
}
private ArdynTheUsurperEffect(final ArdynTheUsurperEffect effect) {
super(effect);
}
@Override
public ArdynTheUsurperEffect copy() {
return new ArdynTheUsurperEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (player == null || card == null) {
return false;
}
player.moveCards(card, Zone.EXILED, source, game);
return new CreateTokenCopyTargetEffect(
null, null, false, 1, false,
false, null, 5, 5, false
).setOnlyColor(ObjectColor.BLACK)
.setOnlySubType(SubType.DEMON)
.setTargetPointer(new FixedTarget(card, game))
.apply(game, source);
}
}

View file

@ -2,8 +2,7 @@ package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.abilities.hint.common.CreaturesYouControlHint; import mage.abilities.hint.common.CreaturesYouControlHint;
import mage.abilities.keyword.VigilanceAbility; import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -11,6 +10,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterControlledPermanent;
import java.util.UUID; import java.util.UUID;
@ -19,6 +20,8 @@ import java.util.UUID;
*/ */
public final class ArgivianPhalanx extends CardImpl { public final class ArgivianPhalanx extends CardImpl {
static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("creatures");
public ArgivianPhalanx(UUID ownerId, CardSetInfo setInfo) { public ArgivianPhalanx(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}");
@ -29,11 +32,7 @@ public final class ArgivianPhalanx extends CardImpl {
this.toughness = new MageInt(4); this.toughness = new MageInt(4);
// This spell costs {1} less to cast for each creature you control. // This spell costs {1} less to cast for each creature you control.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(CreaturesYouControlHint.instance));
Zone.ALL,
new SpellCostReductionSourceEffect(CreaturesYouControlCount.instance)
.setText("This spell costs {1} less to cast for each creature you control.")
).addHint(CreaturesYouControlHint.instance));
// Vigilance // Vigilance
this.addAbility(VigilanceAbility.getInstance()); this.addAbility(VigilanceAbility.getInstance());

View file

@ -10,7 +10,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanentAmount; import mage.filter.StaticFilters;
import mage.target.common.TargetPermanentAmount;
import java.util.UUID; import java.util.UUID;
@ -31,7 +32,7 @@ public final class ArmamentDragon extends CardImpl {
// When this creature enters, distribute three +1/+1 counters among one, two, or three target creatures you control. // When this creature enters, distribute three +1/+1 counters among one, two, or three target creatures you control.
Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1)); Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1));
ability.addTarget(new TargetCreaturePermanentAmount(3)); ability.addTarget(new TargetPermanentAmount(3, 1, StaticFilters.FILTER_CONTROLLED_CREATURES));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -0,0 +1,86 @@
package mage.cards.a;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.common.PutCountersSourceCost;
import mage.abilities.effects.common.DoWhenCostPaid;
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AuronVeneratedGuardian extends CardImpl {
private static final FilterPermanent filter
= new FilterCreaturePermanent("creature defending player controls with power less than {this}'s power");
static {
filter.add(DefendingPlayerControlsSourceAttackingPredicate.instance);
filter.add(AuronVeneratedGuardianPredicate.instance);
}
public AuronVeneratedGuardian(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.SAMURAI);
this.power = new MageInt(2);
this.toughness = new MageInt(5);
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Shooting Star -- Whenever Auron attacks, put a +1/+1 counter on it. When you do, exile target creature defending player controls with power less than Auron's power until Auron leaves the battlefield.
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new ExileUntilSourceLeavesEffect(), false);
ability.addTarget(new TargetPermanent(filter));
this.addAbility(new AttacksTriggeredAbility(new DoWhenCostPaid(
ability, new PutCountersSourceCost(CounterType.P1P1.createInstance())
.setText("put a +1/+1 counter on it"), "", false
)).withFlavorWord("Shooting Star"));
}
private AuronVeneratedGuardian(final AuronVeneratedGuardian card) {
super(card);
}
@Override
public AuronVeneratedGuardian copy() {
return new AuronVeneratedGuardian(this);
}
}
enum AuronVeneratedGuardianPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return Optional
.ofNullable(input.getSource().getSourcePermanentOrLKI(game))
.map(MageObject::getPower)
.map(MageInt::getValue)
.map(x -> input.getObject().getPower().getValue() < x)
.orElse(false);
}
}

View file

@ -0,0 +1,40 @@
package mage.cards.a;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.abilities.keyword.FlashbackAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AuronsInspiration extends CardImpl {
public AuronsInspiration(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}");
// Attacking creatures get +2/+0 until end of turn.
this.getSpellAbility().addEffect(new BoostAllEffect(
2, 0, Duration.EndOfTurn,
StaticFilters.FILTER_ATTACKING_CREATURES, false
));
// Flashback {2}{W}{W}
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{W}{W}")));
}
private AuronsInspiration(final AuronsInspiration card) {
super(card);
}
@Override
public AuronsInspiration copy() {
return new AuronsInspiration(this);
}
}

View file

@ -47,7 +47,7 @@ public final class AutonSoldier extends CardImpl {
Zone.ALL, Zone.ALL,
new EntersBattlefieldEffect(new CopyPermanentEffect( new EntersBattlefieldEffect(new CopyPermanentEffect(
StaticFilters.FILTER_PERMANENT_CREATURE, applier StaticFilters.FILTER_PERMANENT_CREATURE, applier
).setText("You may have {this} enter the battlefield as a copy of any creature on the battlefield, " + ).setText("You may have {this} enter as a copy of any creature on the battlefield, " +
"except it isn't legendary, is an artifact in addition to its other types, and has myriad"), "", true)) "except it isn't legendary, is an artifact in addition to its other types, and has myriad"), "", true))
); );
} }

View file

@ -0,0 +1,98 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterArtifactPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AvalancheOfSector7 extends CardImpl {
private static final FilterPermanent filter = new FilterArtifactPermanent("artifacts your opponents control");
static {
filter.add(TargetController.OPPONENT.getControllerPredicate());
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
public AvalancheOfSector7(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.REBEL);
this.power = new MageInt(0);
this.toughness = new MageInt(3);
// Menace
this.addAbility(new MenaceAbility());
// Avalanche of Sector 7's power is equal to the number of artifacts your opponents control.
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(xValue)));
// Whenever an opponent activates an ability of an artifact they control, Avalanche of Sector 7 deals 1 damage to that player.
this.addAbility(new AvalancheOfSector7TriggeredAbility());
}
private AvalancheOfSector7(final AvalancheOfSector7 card) {
super(card);
}
@Override
public AvalancheOfSector7 copy() {
return new AvalancheOfSector7(this);
}
}
class AvalancheOfSector7TriggeredAbility extends TriggeredAbilityImpl {
AvalancheOfSector7TriggeredAbility() {
super(Zone.BATTLEFIELD, new DamageTargetEffect(1, true, "that player", true));
setTriggerPhrase("Whenever an opponent activates an ability of an artifact they control, ");
}
private AvalancheOfSector7TriggeredAbility(final AvalancheOfSector7TriggeredAbility ability) {
super(ability);
}
@Override
public AvalancheOfSector7TriggeredAbility copy() {
return new AvalancheOfSector7TriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) {
return false;
}
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent == null || !permanent.isArtifact(game) || !permanent.isControlledBy(event.getPlayerId())) {
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
return true;
}
}

View file

@ -1,10 +1,11 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.Mana; import mage.Mana;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.DefenderAbility;
import mage.abilities.mana.DynamicManaAbility; import mage.abilities.mana.DynamicManaAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -15,18 +16,22 @@ import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.AbilityPredicate;
import java.util.UUID;
/** /**
*
* @author Plopman * @author Plopman
*/ */
public final class AxebaneGuardian extends CardImpl { public final class AxebaneGuardian extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creatures with defender you control"); private static final FilterPermanent filter = new FilterControlledCreaturePermanent();
static { static {
filter.add(new AbilityPredicate(DefenderAbility.class)); filter.add(new AbilityPredicate(DefenderAbility.class));
} }
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
private static final Hint hint = new ValueHint("Creatures you control with defender", xValue);
public AxebaneGuardian(UUID ownerId, CardSetInfo setInfo) { public AxebaneGuardian(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUMAN);
@ -39,8 +44,10 @@ public final class AxebaneGuardian extends CardImpl {
this.addAbility(DefenderAbility.getInstance()); this.addAbility(DefenderAbility.getInstance());
// {tap}: Add X mana in any combination of colors, where X is the number of creatures with defender you control. // {tap}: Add X mana in any combination of colors, where X is the number of creatures with defender you control.
this.addAbility(new DynamicManaAbility(new Mana(0, 0, 0, 0,0, 0,1, 0), new PermanentsOnBattlefieldCount(filter), this.addAbility(new DynamicManaAbility(
"Add X mana in any combination of colors, where X is the number of creatures with defender you control.")); Mana.AnyMana(1), xValue, "Add X mana in any combination of colors, " +
"where X is the number of creatures you control with defender."
).addHint(hint));
} }
private AxebaneGuardian(final AxebaneGuardian card) { private AxebaneGuardian(final AxebaneGuardian card) {

View file

@ -163,7 +163,7 @@ class AzorTheLawbringerAttacksEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{U}"); ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{U}");
if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, you gain X life and draw X cards.", source, game)) { if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, you gain X life and draw X cards.", source, game)) {
int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (gain life and draw cards)", game, source,true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
controller.resetStoredBookmark(game); // otherwise you can undo the payment controller.resetStoredBookmark(game); // otherwise you can undo the payment

View file

@ -114,11 +114,7 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect {
filter.add(new PermanentReferenceInCollectionPredicate(list, game)); filter.add(new PermanentReferenceInCollectionPredicate(list, game));
TargetPermanent target = new TargetPermanent(filter); TargetPermanent target = new TargetPermanent(filter);
target.withNotTarget(true); target.withNotTarget(true);
if (target.canChoose(controller.getId(), source, game)) { if (!controller.chooseTarget(outcome, target, source, game)) {
while (!target.isChosen(game) && target.canChoose(controller.getId(), source, game) && controller.canRespond()) {
controller.chooseTarget(outcome, target, source, game);
}
} else {
return true; return true;
} }
Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); Permanent chosenPermanent = game.getPermanent(target.getFirstTarget());

View file

@ -11,6 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -30,6 +31,7 @@ public final class BanesInvoker extends CardImpl {
// Wind Walk {8}: Up to two target creatures each get +2/+2 and gain flying until end of turn. // Wind Walk {8}: Up to two target creatures each get +2/+2 and gain flying until end of turn.
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(2, 2).setText("up to two target creatures each get +2/+2"), new GenericManaCost(8)); Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(2, 2).setText("up to two target creatures each get +2/+2"), new GenericManaCost(8));
ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()).setText("and gain flying until end of turn")); ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()).setText("and gain flying until end of turn"));
ability.addTarget(new TargetCreaturePermanent(0, 2));
this.addAbility(ability.withFlavorWord("Wind Walk")); this.addAbility(ability.withFlavorWord("Wind Walk"));
} }

View file

@ -0,0 +1,43 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.SagaAbility;
import mage.abilities.keyword.DoctorsCompanionAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BarbaraWright extends CardImpl {
public BarbaraWright(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ADVISOR);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// History Teacher -- Sagas you control have read ahead.
this.addAbility(SagaAbility.makeGainReadAheadAbility().withFlavorWord("History Teacher"));
// Doctor's companion
this.addAbility(DoctorsCompanionAbility.getInstance());
}
private BarbaraWright(final BarbaraWright card) {
super(card);
}
@Override
public BarbaraWright copy() {
return new BarbaraWright(this);
}
}

View file

@ -0,0 +1,88 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.TapForManaAllTriggeredManaAbility;
import mage.abilities.condition.common.SourceTappedCondition;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.mana.AddManaOfAnyTypeProducedEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterLandPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BarbflareGremlin extends CardImpl {
private static final FilterPermanent filter = new FilterLandPermanent("a player taps a land");
public BarbflareGremlin(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}");
this.subtype.add(SubType.GREMLIN);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// First strike
this.addAbility(FirstStrikeAbility.getInstance());
// Haste
this.addAbility(HasteAbility.getInstance());
// Whenever a player taps a land for mana, if Barbflare Gremlin is tapped, that player adds one mana of any type that land produced. Then that land deals 1 damage to that player.
Ability ability = new TapForManaAllTriggeredManaAbility(
new AddManaOfAnyTypeProducedEffect(), filter, SetTargetPointer.PERMANENT
).withInterveningIf(SourceTappedCondition.TAPPED);
ability.addEffect(new BarbflareGremlinEffect());
this.addAbility(ability);
}
private BarbflareGremlin(final BarbflareGremlin card) {
super(card);
}
@Override
public BarbflareGremlin copy() {
return new BarbflareGremlin(this);
}
}
class BarbflareGremlinEffect extends OneShotEffect {
BarbflareGremlinEffect() {
super(Outcome.Benefit);
staticText = "Then that land deals 1 damage to that player";
}
private BarbflareGremlinEffect(final BarbflareGremlinEffect effect) {
super(effect);
}
@Override
public BarbflareGremlinEffect copy() {
return new BarbflareGremlinEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = (Permanent) getValue("tappedPermanent");
if (permanent == null) {
return false;
}
Player player = game.getPlayer(permanent.getControllerId());
return player != null && player.damage(1, permanent.getId(), source, game) > 0;
}
}

View file

@ -0,0 +1,99 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.ReachAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.RebelRedToken;
import mage.target.TargetPermanent;
import mage.target.targetpointer.EachTargetPointer;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class BarretAvalancheLeader extends CardImpl {
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT);
private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.REBEL);
public BarretAvalancheLeader(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.REBEL);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Reach
this.addAbility(ReachAbility.getInstance());
// Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token.
this.addAbility(new EntersBattlefieldAllTriggeredAbility(
new CreateTokenEffect(new RebelRedToken()), filter
).withFlavorWord("Avalanche!"));
// At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control.
Ability ability = new BeginningOfCombatTriggeredAbility(new BarretAvalancheLeaderEffect());
ability.addTarget(new TargetPermanent(0, 1, filter));
ability.addTarget(new TargetPermanent(filter2));
this.addAbility(ability);
}
private BarretAvalancheLeader(final BarretAvalancheLeader card) {
super(card);
}
@Override
public BarretAvalancheLeader copy() {
return new BarretAvalancheLeader(this);
}
}
class BarretAvalancheLeaderEffect extends OneShotEffect {
BarretAvalancheLeaderEffect() {
super(Outcome.Benefit);
staticText = "attach up to one target Equipment you control to target Rebel you control";
this.setTargetPointer(new EachTargetPointer());
}
private BarretAvalancheLeaderEffect(final BarretAvalancheLeaderEffect effect) {
super(effect);
}
@Override
public BarretAvalancheLeaderEffect copy() {
return new BarretAvalancheLeaderEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
List<Permanent> permanents = this
.getTargetPointer()
.getTargets(game, source)
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return permanents.size() >= 2 && permanents.get(1).addAttachment(permanents.get(0).getId(), source, game);
}
}

View file

@ -0,0 +1,65 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.ReachAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.EquippedPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BarretWallace extends CardImpl {
private static final FilterPermanent filter
= new FilterControlledCreaturePermanent("equipped creatures you control");
static {
filter.add(EquippedPredicate.instance);
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
private static final Hint hint = new ValueHint("Equipped creatures you control", xValue);
public BarretWallace(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.REBEL);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Reach
this.addAbility(ReachAbility.getInstance());
// Whenever Barret Wallace attacks, it deals damage equal to the number of equipped creatures you control to defending player.
this.addAbility(new AttacksTriggeredAbility(
new DamageTargetEffect(xValue, true, "it", true),
false, null, SetTargetPointer.PLAYER
).withRuleTextReplacement(true).addHint(hint));
}
private BarretWallace(final BarretWallace card) {
super(card);
}
@Override
public BarretWallace copy() {
return new BarretWallace(this);
}
}

View file

@ -7,11 +7,12 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.common.TargetCreaturePermanentSameController; import mage.target.common.TargetPermanentSameController;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -29,7 +30,7 @@ public final class BarrinsSpite extends CardImpl {
// Choose two target creatures controlled by the same player. Their controller chooses and sacrifices one of them. Return the other to its owner's hand. // Choose two target creatures controlled by the same player. Their controller chooses and sacrifices one of them. Return the other to its owner's hand.
this.getSpellAbility().addEffect(new BarrinsSpiteEffect()); this.getSpellAbility().addEffect(new BarrinsSpiteEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2)); this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_PERMANENT_CREATURES));
} }
private BarrinsSpite(final BarrinsSpite card) { private BarrinsSpite(final BarrinsSpite card) {

View file

@ -0,0 +1,93 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetOpponentsCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BartzAndBoko extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.BIRD, "Birds");
private static final Hint hint = new ValueHint("Birds you control", new PermanentsOnBattlefieldCount(filter));
public BartzAndBoko(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.BIRD);
this.power = new MageInt(4);
this.toughness = new MageInt(3);
// Affinity for Birds
this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint));
// When Bartz and Boko enters, each other Bird you control deals damage equal to its power to target creature an opponent controls.
Ability ability = new EntersBattlefieldTriggeredAbility(new BartzAndBokoEffect());
ability.addTarget(new TargetOpponentsCreaturePermanent());
this.addAbility(ability);
}
private BartzAndBoko(final BartzAndBoko card) {
super(card);
}
@Override
public BartzAndBoko copy() {
return new BartzAndBoko(this);
}
}
class BartzAndBokoEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.BIRD);
static {
filter.add(AnotherPredicate.instance);
}
BartzAndBokoEffect() {
super(Outcome.Benefit);
staticText = "each other Bird you control deals damage equal to its power to target creature an opponent controls";
}
private BartzAndBokoEffect(final BartzAndBokoEffect effect) {
super(effect);
}
@Override
public BartzAndBokoEffect copy() {
return new BartzAndBokoEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
return false;
}
for (Permanent bird : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
permanent.damage(bird.getPower().getValue(), bird.getId(), source, game);
}
return true;
}
}

View file

@ -0,0 +1,89 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BeatrixLoyalGeneral extends CardImpl {
public BeatrixLoyalGeneral(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// At the beginning of combat on your turn, you may attach any number of Equipment you control to target creature you control.
Ability ability = new BeginningOfCombatTriggeredAbility(new BeatrixLoyalGeneralEffect());
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(ability);
}
private BeatrixLoyalGeneral(final BeatrixLoyalGeneral card) {
super(card);
}
@Override
public BeatrixLoyalGeneral copy() {
return new BeatrixLoyalGeneral(this);
}
}
class BeatrixLoyalGeneralEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT);
BeatrixLoyalGeneralEffect() {
super(Outcome.Benefit);
staticText = "you may attach any number of Equipment you control to target creature you control";
}
private BeatrixLoyalGeneralEffect(final BeatrixLoyalGeneralEffect effect) {
super(effect);
}
@Override
public BeatrixLoyalGeneralEffect copy() {
return new BeatrixLoyalGeneralEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (player == null || permanent == null) {
return false;
}
TargetPermanent target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true);
player.choose(outcome, target, source, game);
for (UUID targetId : target.getTargets()) {
permanent.addAttachment(targetId, source, game);
}
return true;
}
}

View file

@ -40,7 +40,7 @@ public final class BecomeTheAvalanche extends CardImpl {
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue)); this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue));
this.getSpellAbility().addEffect(new BoostControlledEffect( this.getSpellAbility().addEffect(new BoostControlledEffect(
CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY, Duration.EndOfTurn CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY, Duration.EndOfTurn
)); ).setText("Then creatures you control get +X/+X until end of turn, where X is the number of cards in your hand"));
this.getSpellAbility().addHint(hint); this.getSpellAbility().addHint(hint);
} }

View file

@ -4,10 +4,11 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect;
import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
@ -30,8 +31,9 @@ public final class BeltOfGiantStrength extends CardImpl {
this.subtype.add(SubType.EQUIPMENT); this.subtype.add(SubType.EQUIPMENT);
// Equipped creature has base power and toughness 10/10. // Equipped creature has base power and toughness 10/10.
this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(10, 10) this.addAbility(new SimpleStaticAbility(
.setText("equipped creature has base power and toughness 10/10"))); new SetBasePowerToughnessAttachedEffect(10, 10, AttachmentType.EQUIPMENT)
));
// Equip {10}. This ability costs {X} less to activate where X is the power of the creature it targets. // Equip {10}. This ability costs {X} less to activate where X is the power of the creature it targets.
EquipAbility ability = new EquipAbility(Outcome.BoostCreature, new GenericManaCost(10), new TargetControlledCreaturePermanent(), false); EquipAbility ability = new EquipAbility(Outcome.BoostCreature, new GenericManaCost(10), new TargetControlledCreaturePermanent(), false);

View file

@ -1,15 +1,15 @@
package mage.cards.b; package mage.cards.b;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.condition.Condition; import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.UntapAllEffect;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint; import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
@ -21,19 +21,19 @@ import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
*
* @author androosss * @author androosss
*/ */
public final class BetorKinToAll extends CardImpl { public final class BetorKinToAll extends CardImpl {
private static final Hint hint = new ValueHint( private static final Hint hint = new ValueHint(
"Total toughness of creatures you control", ControlledCreaturesToughnessValue.instance); "Total toughness of creatures you control", ControlledCreaturesToughnessValue.instance
);
public BetorKinToAll(UUID ownerId, CardSetInfo setInfo) { public BetorKinToAll(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}{G}");
@ -47,15 +47,18 @@ public final class BetorKinToAll extends CardImpl {
// Flying // Flying
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());
// At the beginning of your end step, if creatures you control have total // At the beginning of your end step, if creatures you control have total toughness 10 or greater, draw a card. Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up.
// toughness 10 or greater, draw a card. Then if creatures you control have Ability ability = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1))
// total toughness 20 or greater, untap each creature you control. Then if .withInterveningIf(BetorKinToAllCondition.TEN);
// creatures you control have total toughness 40 or greater, each opponent loses ability.addEffect(new ConditionalOneShotEffect(
// half their life, rounded up. new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE), BetorKinToAllCondition.TWENTY,
Ability betorAbility = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)) "Then if creatures you control have total toughness 20 or greater, untap each creature you control"
.withInterveningIf(BetorKinToAllCondition.instance).addHint(hint); ));
betorAbility.addEffect(new BetorKinToAllEffect()); ability.addEffect(new ConditionalOneShotEffect(
this.addAbility(betorAbility); new BetorKinToAllEffect(), BetorKinToAllCondition.FORTY, "Then if creatures you control " +
"have total toughness 40 or greater, each opponent loses half their life, rounded up"
));
this.addAbility(ability.addHint(hint));
} }
private BetorKinToAll(final BetorKinToAll card) { private BetorKinToAll(final BetorKinToAll card) {
@ -69,22 +72,30 @@ public final class BetorKinToAll extends CardImpl {
} }
enum BetorKinToAllCondition implements Condition { enum BetorKinToAllCondition implements Condition {
instance; TEN(10),
TWENTY(20),
FORTY(40);
private final int amount;
private BetorKinToAllCondition(int amount) {
this.amount = amount;
}
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
return ControlledCreaturesToughnessValue.instance.calculate(game, source, null) >= 10; return ControlledCreaturesToughnessValue.instance.calculate(game, source, null) >= amount;
} }
@Override
public String toString() {
return "creatures you control have total toughness " + amount + " or greater";
}
} }
class BetorKinToAllEffect extends OneShotEffect { class BetorKinToAllEffect extends OneShotEffect {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent();
BetorKinToAllEffect() { BetorKinToAllEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
this.staticText = "Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up.";
} }
private BetorKinToAllEffect(final BetorKinToAllEffect effect) { private BetorKinToAllEffect(final BetorKinToAllEffect effect) {
@ -98,36 +109,12 @@ class BetorKinToAllEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); for (UUID playerId : game.getOpponents(source.getControllerId())) {
if (controller == null) {
return false;
}
int sumToughness = ControlledCreaturesToughnessValue.instance.calculate(game, source, null);
if (sumToughness < 20) {
return true;
}
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(),
game)) {
permanent.untap(game);
}
if (sumToughness < 40) {
return true;
}
for (UUID playerId : game.getOpponents(controller.getId())) {
Player opponent = game.getPlayer(playerId); Player opponent = game.getPlayer(playerId);
if (opponent == null) { if (opponent != null) {
continue; opponent.loseLife(opponent.getLife() / 2 + opponent.getLife() % 2, game, source, false);
}
int amount = (int) Math.ceil(opponent.getLife() / 2f);
if (amount > 0) {
opponent.loseLife(amount, game, source, false);
} }
} }
return true; return true;
} }
} }

View file

@ -1,48 +1,38 @@
package mage.cards.b; package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.MageItem;
import mage.abilities.Ability;
import mage.abilities.Abilities; import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl; import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.common.ActivateAbilityTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.CopyStackObjectEffect; import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.meta.OrTriggeredAbility;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.abilities.keyword.DoctorsCompanionAbility; import mage.abilities.keyword.DoctorsCompanionAbility;
import mage.abilities.meta.OrTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.FilterSpell; import mage.filter.FilterSpell;
import mage.filter.FilterStackObject; import mage.filter.FilterStackObject;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.stack.StackObject;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.target.Target; import mage.target.Target;
import java.util.Arrays; import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.UUID;
/** /**
*
* @author padfoot * @author padfoot
*/ */
public final class BillPotts extends CardImpl { public final class BillPotts extends CardImpl {
private static final FilterSpell filterInstantOrSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery that targets only {this}"); private static final FilterSpell filterInstantOrSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery spell that targets only {this}");
private static final FilterStackObject filterAbility = new FilterStackObject("an ability that targets only {this}"); private static final FilterStackObject filterAbility = new FilterStackObject("an ability that targets only {this}");
static { static {

View file

@ -87,7 +87,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect {
} }
int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1); int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1);
if (amountCounters > 0) { if (amountCounters > 0) {
int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", game); int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", source, game);
if (amountToMove > 0) { if (amountToMove > 0) {
fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game); fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game);
toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game); toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game);

View file

@ -0,0 +1,62 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.constants.AttachmentType;
import mage.constants.SubType;
import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.JobSelectAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.TargetController;
import mage.filter.StaticFilters;
/**
* @author balazskristof
*/
public final class BlackMagesRod extends CardImpl {
public BlackMagesRod(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{B}");
this.subtype.add(SubType.EQUIPMENT);
// Job select
this.addAbility(new JobSelectAbility());
// Equipped creature gets +1/+0, has "Whenever you cast a noncreature spell, this creature deals 1 damage to each opponent," and is a Wizard in addition to its other types.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0));
ability.addEffect(new GainAbilityAttachedEffect(
new SpellCastControllerTriggeredAbility(
new DamagePlayersEffect(1, TargetController.OPPONENT, "this creature"),
StaticFilters.FILTER_SPELL_A_NON_CREATURE, false
),
AttachmentType.EQUIPMENT
).setText(", has \"Whenever you cast a noncreature spell, this creature deals 1 damage to each opponent,\""));
ability.addEffect(new AddCardSubtypeAttachedEffect(
SubType.WIZARD,
AttachmentType.EQUIPMENT
).setText("is a Wizard in addition to its other types").concatBy("and"));
this.addAbility(ability);
// Equip {3}
this.addAbility(new EquipAbility(3));
}
private BlackMagesRod(final BlackMagesRod card) {
super(card);
}
@Override
public BlackMagesRod copy() {
return new BlackMagesRod(this);
}
}

View file

@ -1,7 +1,6 @@
package mage.cards.b; package mage.cards.b;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.condition.common.MyTurnCondition;
@ -14,6 +13,7 @@ import mage.abilities.keyword.ClassLevelAbility;
import mage.abilities.keyword.ClassReminderAbility; import mage.abilities.keyword.ClassReminderAbility;
import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.DoubleStrikeAbility;
import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.HasteAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
@ -74,7 +74,7 @@ public final class BlacksmithsTalent extends CardImpl {
DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter2 DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter2
), MyTurnCondition.instance, "during your turn, equipped creatures you control have double strike")); ), MyTurnCondition.instance, "during your turn, equipped creatures you control have double strike"));
ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect( ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect(
HasteAbility.getInstance(), Duration.WhileOnBattlefield HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter2
), MyTurnCondition.instance, "and haste")); ), MyTurnCondition.instance, "and haste"));
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3))); this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3)));
} }

View file

@ -33,7 +33,7 @@ public final class BlightPile extends CardImpl {
filter.add(new AbilityPredicate(DefenderAbility.class)); filter.add(new AbilityPredicate(DefenderAbility.class));
} }
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null);
private static final Hint hint = new ValueHint("Creatures with defender you control", xValue); private static final Hint hint = new ValueHint("Creatures with defender you control", xValue);
public BlightPile(UUID ownerId, CardSetInfo setInfo) { public BlightPile(UUID ownerId, CardSetInfo setInfo) {
@ -47,7 +47,11 @@ public final class BlightPile extends CardImpl {
this.addAbility(DefenderAbility.getInstance()); this.addAbility(DefenderAbility.getInstance());
// {2}{B}, {T}: Each opponent loses X life, where X is the number of creatures with defender you control. // {2}{B}, {T}: Each opponent loses X life, where X is the number of creatures with defender you control.
Ability ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(xValue), new ManaCostsImpl<>("{2}{B}")); Ability ability = new SimpleActivatedAbility(
new LoseLifeOpponentsEffect(xValue)
.setText("each opponent loses X life, where X is the number of creatures with defender you control"),
new ManaCostsImpl<>("{2}{B}")
);
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
this.addAbility(ability.addHint(hint)); this.addAbility(ability.addHint(hint));
} }

View file

@ -0,0 +1,97 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.keyword.InvestigateEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SagaChapter;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.AlienAngelToken;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class Blink extends CardImpl {
public Blink(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{B}");
this.subtype.add(SubType.SAGA);
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)
SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV);
// I, III -- Choose target creature. Its owner shuffles it into their library, then investigates.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I,
new BlinkEffect(), new TargetCreaturePermanent()
);
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_III,
new BlinkEffect(), new TargetCreaturePermanent()
);
// II, IV -- Create a 2/2 black Alien Angel artifact creature token with first strike, vigilance, and "Whenever an opponent casts a creature spell, this permanent isn't a creature until end of turn."
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_II,
new CreateTokenEffect(new AlienAngelToken())
);
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_IV,
new CreateTokenEffect(new AlienAngelToken())
);
this.addAbility(sagaAbility);
}
private Blink(final Blink card) {
super(card);
}
@Override
public Blink copy() {
return new Blink(this);
}
}
class BlinkEffect extends OneShotEffect {
BlinkEffect() {
super(Outcome.Benefit);
staticText = "choose target creature. Its owner shuffles it into their library, then investigates";
}
private BlinkEffect(final BlinkEffect effect) {
super(effect);
}
@Override
public BlinkEffect copy() {
return new BlinkEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
return false;
}
Player player = game.getPlayer(permanent.getOwnerId());
if (player == null) {
return false;
}
player.shuffleCardsToLibrary(permanent, game, source);
InvestigateEffect.doInvestigate(player.getId(), 1, game, source);
return true;
}
}

View file

@ -0,0 +1,108 @@
package mage.cards.b;
import java.util.UUID;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.cards.Card;
import mage.constants.*;
import mage.abilities.keyword.JobSelectAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.card.DefendingPlayerOwnsCardPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInGraveyard;
import mage.util.CardUtil;
/**
* @author balazskristof
*/
public final class BlueMagesCane extends CardImpl {
private static final FilterInstantOrSorceryCard filter = new FilterInstantOrSorceryCard(
"instant or sorcery card from defending player's graveyard"
);
static {
filter.add(DefendingPlayerOwnsCardPredicate.instance);
}
public BlueMagesCane(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}");
this.subtype.add(SubType.EQUIPMENT);
// Job select
this.addAbility(new JobSelectAbility());
// Equipped creature gets +0/+2, is a Wizard in addition to its other types, and has "Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost."
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(0, 2));
ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.WIZARD, AttachmentType.EQUIPMENT)
.setText(", is a Wizard in addition to its other types")
);
Ability attackAbility = new AttacksTriggeredAbility(new BlueMagesCaneEffect());
attackAbility.addTarget(new TargetCardInGraveyard(0, 1, filter));
ability.addEffect(new GainAbilityAttachedEffect(attackAbility, AttachmentType.EQUIPMENT)
.setText("and has \"Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. "
+ "If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost.\"")
);
this.addAbility(ability);
// Spirit of the Whalaqee -- Equip {2}
this.addAbility(new EquipAbility(2).withFlavorWord("Spirit of the Whalaqee"));
}
private BlueMagesCane(final BlueMagesCane card) {
super(card);
}
@Override
public BlueMagesCane copy() {
return new BlueMagesCane(this);
}
}
class BlueMagesCaneEffect extends OneShotEffect {
BlueMagesCaneEffect() {
super(Outcome.Benefit);
staticText = "exile up to one target instant or sorcery card from defending player's graveyard. "
+ "If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost.";
}
private BlueMagesCaneEffect(final BlueMagesCaneEffect effect) {
super(effect);
}
@Override
public BlueMagesCaneEffect copy() {
return new BlueMagesCaneEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (controller == null || card == null) {
return false;
}
controller.moveCards(card, Zone.EXILED, source, game);
Card copiedCard = game.copyCard(card, source, source.getControllerId());
if (!controller.chooseUse(Outcome.Benefit, "Cast " + copiedCard.getName() + " by paying {3}?", source, game)) {
return false;
}
CardUtil.castSingle(controller, source, game, copiedCard, new ManaCostsImpl<>("{3}"));
return true;
}
}

View file

@ -48,7 +48,7 @@ public final class BoneDevourer extends CardImpl {
// When this creature dies, you draw X cards and you lose X life, where X is the number of +1/+1 counters on it. // When this creature dies, you draw X cards and you lose X life, where X is the number of +1/+1 counters on it.
Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(xValue).setText("you draw X cards")); Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(xValue).setText("you draw X cards"));
ability.addEffect(new LoseLifeSourceControllerEffect(xValue)); ability.addEffect(new LoseLifeSourceControllerEffect(xValue).setText("and you lose X life, where X is the number of +1/+1 counters on it"));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -3,7 +3,7 @@ package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SpellCastOpponentNoManaSpentTriggeredAbility; import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.common.CounterTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
@ -12,16 +12,12 @@ import mage.abilities.keyword.IndestructibleAbility;
import mage.abilities.keyword.VigilanceAbility; import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import java.util.UUID; import java.util.UUID;
/** /**
*
* @author Susucr * @author Susucr
*/ */
public final class BoromirWardenOfTheTower extends CardImpl { public final class BoromirWardenOfTheTower extends CardImpl {
@ -39,7 +35,10 @@ public final class BoromirWardenOfTheTower extends CardImpl {
this.addAbility(VigilanceAbility.getInstance()); this.addAbility(VigilanceAbility.getInstance());
// Whenever an opponent casts a spell, if no mana was spent to cast it, counter that spell. // Whenever an opponent casts a spell, if no mana was spent to cast it, counter that spell.
this.addAbility(new SpellCastOpponentNoManaSpentTriggeredAbility(new CounterTargetEffect().setText("counter that spell"))); this.addAbility(new SpellCastOpponentTriggeredAbility(
Zone.BATTLEFIELD, new CounterTargetEffect(),
StaticFilters.FILTER_SPELL_NO_MANA_SPENT, false, true
));
// Sacrifice Boromir, Warden of the Tower: Creatures you control gain indestructible until end of turn. The Ring tempts you. // Sacrifice Boromir, Warden of the Tower: Creatures you control gain indestructible until end of turn. The Ring tempts you.
Ability ability = new SimpleActivatedAbility(new GainAbilityAllEffect( Ability ability = new SimpleActivatedAbility(new GainAbilityAllEffect(

View file

@ -2,7 +2,6 @@ package mage.cards.b;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.DoubleStrikeAbility;
@ -11,6 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.StaticFilters;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.common.TargetPlayerOrPlaneswalker;
@ -27,10 +27,12 @@ public final class BorosCharm extends CardImpl {
//Choose one - Boros Charm deals 4 damage to target player //Choose one - Boros Charm deals 4 damage to target player
this.getSpellAbility().addEffect(new DamageTargetEffect(4)); this.getSpellAbility().addEffect(new DamageTargetEffect(4));
this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker());
//or permanents you control are indestructible this turn //or permanents you control are indestructible this turn
this.getSpellAbility().addMode(new Mode(new GainAbilityControlledEffect( this.getSpellAbility().addMode(new Mode(new GainAbilityControlledEffect(
IndestructibleAbility.getInstance(), Duration.EndOfTurn IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS
))); )));
//or target creature gains double strike until end of turn. //or target creature gains double strike until end of turn.
Mode mode2 = new Mode(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn)); Mode mode2 = new Mode(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn));
mode2.addTarget(new TargetCreaturePermanent()); mode2.addTarget(new TargetCreaturePermanent());

View file

@ -0,0 +1,61 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.SacrificeOpponentsEffect;
import mage.abilities.effects.common.discard.DiscardEachPlayerEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BraskasFinalAeon extends CardImpl {
public BraskasFinalAeon(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SAGA);
this.subtype.add(SubType.NIGHTMARE);
this.power = new MageInt(7);
this.toughness = new MageInt(7);
this.nightCard = true;
this.color.setBlack(true);
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
SagaAbility sagaAbility = new SagaAbility(this);
// I, II -- Jecht Beam -- Each opponent discards a card and you draw a card.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, ability -> {
ability.addEffect(new DiscardEachPlayerEffect(TargetController.OPPONENT));
ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and you"));
ability.withFlavorWord("Jecht Beam");
});
// III -- Ultimate Jecht Shot -- Each opponent sacrifices two creatures of their choice.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> {
ability.addEffect(new SacrificeOpponentsEffect(2, StaticFilters.FILTER_PERMANENT_CREATURES));
ability.withFlavorWord("Ultimate Jecht Shot");
});
this.addAbility(sagaAbility);
// Menace
this.addAbility(new MenaceAbility());
}
private BraskasFinalAeon(final BraskasFinalAeon card) {
super(card);
}
@Override
public BraskasFinalAeon copy() {
return new BraskasFinalAeon(this);
}
}

View file

@ -108,7 +108,7 @@ class BrenardGingerSculptorEffect extends OneShotEffect {
effect.setBecomesArtifact(true); effect.setBecomesArtifact(true);
effect.withAdditionalSubType(SubType.FOOD); effect.withAdditionalSubType(SubType.FOOD);
effect.withAdditionalSubType(SubType.GOLEM); effect.withAdditionalSubType(SubType.GOLEM);
effect.addAdditionalAbilities(new FoodAbility(false)); effect.addAdditionalAbilities(new FoodAbility());
player.moveCards(card, Zone.EXILED, source, game); player.moveCards(card, Zone.EXILED, source, game);
effect.apply(game, source); effect.apply(game, source);

View file

@ -2,15 +2,16 @@ package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.effects.common.AffinityEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint; import mage.abilities.hint.ValueHint;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterControlledEnchantmentPermanent;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import java.util.UUID; import java.util.UUID;
@ -20,13 +21,8 @@ import java.util.UUID;
*/ */
public final class BrineGiant extends CardImpl { public final class BrineGiant extends CardImpl {
static final FilterControlledPermanent filter = new FilterControlledPermanent("enchantment you control"); static final FilterControlledPermanent filter = new FilterControlledEnchantmentPermanent("enchantments");
private static final Hint hint = new ValueHint("Enchantments you control", new PermanentsOnBattlefieldCount(filter));
static {
filter.add(CardType.ENCHANTMENT.getPredicate());
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
public BrineGiant(UUID ownerId, CardSetInfo setInfo) { public BrineGiant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}");
@ -36,9 +32,7 @@ public final class BrineGiant extends CardImpl {
this.toughness = new MageInt(6); this.toughness = new MageInt(6);
// This spell costs {1} less to cast for each enchantment you control. // This spell costs {1} less to cast for each enchantment you control.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint));
Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)
).addHint(new ValueHint("Enchantments you control", xValue)));
} }
private BrineGiant(final BrineGiant card) { private BrineGiant(final BrineGiant card) {

View file

@ -69,8 +69,7 @@ enum BroodcallerScourgePredicate implements ObjectSourcePlayerPredicate<Card> {
.getManaValue() .getManaValue()
<= CardUtil <= CardUtil
.getEffectValueFromAbility( .getEffectValueFromAbility(
input.getSource(), "damage", input.getSource(), "damage", Integer.class
Integer.class, 0 ).orElse(0);
);
} }
} }

View file

@ -135,17 +135,17 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect {
&& controller.chooseUse(Outcome.Benefit, "Attach an Aura from your hand?", source, game)) { && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your hand?", source, game)) {
TargetCard targetAura = new TargetCard(Zone.HAND, filterAuraCard); TargetCard targetAura = new TargetCard(Zone.HAND, filterAuraCard);
if (!controller.choose(Outcome.Benefit, controller.getHand(), targetAura, source, game)) { if (!controller.choose(Outcome.Benefit, controller.getHand(), targetAura, source, game)) {
continue; break;
} }
Card aura = game.getCard(targetAura.getFirstTarget()); Card aura = game.getCard(targetAura.getFirstTarget());
if (aura == null) { if (aura == null) {
continue; break;
} }
Target target = aura.getSpellAbility().getTargets().get(0); Target target = aura.getSpellAbility().getTargets().get(0);
if (target == null) { if (target == null) {
continue; break;
} }
fromHandGraveyard.add(aura); fromHandGraveyard.add(aura);
filterAuraCard.add(Predicates.not(new CardIdPredicate(aura.getId()))); filterAuraCard.add(Predicates.not(new CardIdPredicate(aura.getId())));
@ -159,17 +159,17 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect {
&& controller.chooseUse(Outcome.Benefit, "Attach an Aura from your graveyard?", source, game)) { && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your graveyard?", source, game)) {
TargetCard targetAura = new TargetCard(Zone.GRAVEYARD, filterAuraCard); TargetCard targetAura = new TargetCard(Zone.GRAVEYARD, filterAuraCard);
if (!controller.choose(Outcome.Benefit, controller.getGraveyard(), targetAura, source, game)) { if (!controller.choose(Outcome.Benefit, controller.getGraveyard(), targetAura, source, game)) {
continue; break;
} }
Card aura = game.getCard(targetAura.getFirstTarget()); Card aura = game.getCard(targetAura.getFirstTarget());
if (aura == null) { if (aura == null) {
continue; break;
} }
Target target = aura.getSpellAbility().getTargets().get(0); Target target = aura.getSpellAbility().getTargets().get(0);
if (target == null) { if (target == null) {
continue; break;
} }
fromHandGraveyard.add(aura); fromHandGraveyard.add(aura);

View file

@ -0,0 +1,66 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.abilities.keyword.ReachAbility;
import mage.abilities.mana.AnyColorManaAbility;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BugenhagenWiseElder extends CardImpl {
private static final FilterPermanent filter
= new FilterControlledCreaturePermanent("you control a creature with power 7 or greater");
static {
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 7));
}
private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter);
private static final Hint hint = new ConditionHint(condition, "You control a creature with power 7 or greater");
public BugenhagenWiseElder(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SHAMAN);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// Reach
this.addAbility(ReachAbility.getInstance());
// At the beginning of your upkeep, if you control a creature with power 7 or greater, draw a card.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DrawCardSourceControllerEffect(1)).withInterveningIf(condition).addHint(hint));
// {T}: Add one mana of any color.
this.addAbility(new AnyColorManaAbility());
}
private BugenhagenWiseElder(final BugenhagenWiseElder card) {
super(card);
}
@Override
public BugenhagenWiseElder copy() {
return new BugenhagenWiseElder(this);
}
}

View file

@ -8,6 +8,7 @@ import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FirstStrikeAbility;
import mage.constants.Duration;
import mage.constants.SubType; import mage.constants.SubType;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
@ -37,7 +38,7 @@ public final class BullRushBruiser extends CardImpl {
// Whenever Bull-Rush Bruiser attacks, if your team controls another Warrior, Bull-Rush Bruiser gains first strike until end of turn. // Whenever Bull-Rush Bruiser attacks, if your team controls another Warrior, Bull-Rush Bruiser gains first strike until end of turn.
this.addAbility(new ConditionalInterveningIfTriggeredAbility( this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new AttacksTriggeredAbility(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), false), new AttacksTriggeredAbility(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), false),
new PermanentsOnTheBattlefieldCondition(filter), new PermanentsOnTheBattlefieldCondition(filter),
"Whenever {this} attacks, if your team controls another Warrior, " "Whenever {this} attacks, if your team controls another Warrior, "
+ "{this} gains first strike until end of turn." + "{this} gains first strike until end of turn."

View file

@ -16,6 +16,9 @@ import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SubType; import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.CounterAnyPredicate;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -25,6 +28,12 @@ import java.util.UUID;
*/ */
public final class BulwarkOx extends CardImpl { public final class BulwarkOx extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent();
static {
filter.add(CounterAnyPredicate.instance);
}
public BulwarkOx(UUID ownerId, CardSetInfo setInfo) { public BulwarkOx(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
@ -40,10 +49,10 @@ public final class BulwarkOx extends CardImpl {
// Sacrifice this creature: Creatures you control with counters on them gain hexproof and indestructible until end of turn. // Sacrifice this creature: Creatures you control with counters on them gain hexproof and indestructible until end of turn.
ability = new SimpleActivatedAbility(new GainAbilityControlledEffect( ability = new SimpleActivatedAbility(new GainAbilityControlledEffect(
HexproofAbility.getInstance(), Duration.EndOfTurn HexproofAbility.getInstance(), Duration.EndOfTurn, filter
).setText("creatures you control with counters on them gain hexproof"), new SacrificeSourceCost()); ).setText("creatures you control with counters on them gain hexproof"), new SacrificeSourceCost());
ability.addEffect(new GainAbilityControlledEffect( ability.addEffect(new GainAbilityControlledEffect(
IndestructibleAbility.getInstance(), Duration.EndOfTurn IndestructibleAbility.getInstance(), Duration.EndOfTurn, filter
).setText("and indestructible until end of turn")); ).setText("and indestructible until end of turn"));
this.addAbility(ability); this.addAbility(ability);

View file

@ -82,18 +82,15 @@ class BurningOfXinyeEffect extends OneShotEffect {
Target target = new TargetControlledPermanent(amount, amount, filter, true); Target target = new TargetControlledPermanent(amount, amount, filter, true);
if (amount > 0 && target.canChoose(player.getId(), source, game)) { if (amount > 0 && target.canChoose(player.getId(), source, game)) {
while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { if (player.choose(Outcome.DestroyPermanent, target, source, game)) {
player.choose(Outcome.DestroyPermanent, target, source, game);
}
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId); Permanent permanent = game.getPermanent(targetId);
if (permanent != null) { if (permanent != null) {
abilityApplied |= permanent.destroy(source, game, false); abilityApplied |= permanent.destroy(source, game, false);
} }
} }
} }
}
return abilityApplied; return abilityApplied;
} }

View file

@ -0,0 +1,85 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageEquippedTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardTargetEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BusterSword extends CardImpl {
public BusterSword(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
this.subtype.add(SubType.EQUIPMENT);
// Equipped creature gets +3/+2.
this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(3, 2)));
// Whenever equipped creature deals combat damage to a player, draw a card, then you may cast a spell from your hand with mana value less than or equal to that damage without paying its mana cost.
Ability ability = new DealsCombatDamageEquippedTriggeredAbility(new DrawCardTargetEffect(1));
ability.addEffect(new BusterSwordEffect());
this.addAbility(ability);
// Equip {2}
this.addAbility(new EquipAbility(2));
}
private BusterSword(final BusterSword card) {
super(card);
}
@Override
public BusterSword copy() {
return new BusterSword(this);
}
}
class BusterSwordEffect extends OneShotEffect {
BusterSwordEffect() {
super(Outcome.Benefit);
staticText = ", then you may cast a spell from your hand with mana value " +
"less than or equal to that damage without paying its mana cost";
}
private BusterSwordEffect(final BusterSwordEffect effect) {
super(effect);
}
@Override
public BusterSwordEffect copy() {
return new BusterSwordEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null || player.getHand().isEmpty()) {
return false;
}
int damage = (Integer) getValue("damage");
FilterCard filter = new FilterCard();
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, damage + 1));
return CardUtil.castSpellWithAttributesForFree(player, source, game, new CardsImpl(player.getHand()), filter);
}
}

View file

@ -58,7 +58,7 @@ class ByInvitationOnlyEffect extends OneShotEffect {
return false; return false;
} }
int number = player.getAmount( int number = player.getAmount(
0, 13, "Choose a number between 0 and 13", game 0, 13, "Choose a number between 0 and 13", source, game
); );
return new SacrificeAllEffect( return new SacrificeAllEffect(
number, StaticFilters.FILTER_PERMANENT_CREATURE number, StaticFilters.FILTER_PERMANENT_CREATURE

View file

@ -0,0 +1,86 @@
package mage.cards.c;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class CaitSithFortuneTeller extends CardImpl {
public CaitSithFortuneTeller(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.CAT);
this.subtype.add(SubType.MOOGLE);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Lucky Slots -- At the beginning of combat on your turn, scry 1, then exile the top card of your library. You may play that card this turn. When you exile a card this way, target creature you control gets +X/+0 until end of turn, where X is that card's mana value.
this.addAbility(new BeginningOfCombatTriggeredAbility(new CaitSithFortuneTellerEffect()).withFlavorWord("Lucky Slots"));
}
private CaitSithFortuneTeller(final CaitSithFortuneTeller card) {
super(card);
}
@Override
public CaitSithFortuneTeller copy() {
return new CaitSithFortuneTeller(this);
}
}
class CaitSithFortuneTellerEffect extends OneShotEffect {
CaitSithFortuneTellerEffect() {
super(Outcome.Benefit);
staticText = "scry 1, then exile the top card of your library. You may play that card this turn. " +
"When you exile a card this way, target creature you control gets +X/+0 until end of turn, " +
"where X is that card's mana value";
}
private CaitSithFortuneTellerEffect(final CaitSithFortuneTellerEffect effect) {
super(effect);
}
@Override
public CaitSithFortuneTellerEffect copy() {
return new CaitSithFortuneTellerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
player.scry(1, source, game);
Card card = player.getLibrary().getFromTop(game);
if (card == null) {
return true;
}
player.moveCards(card, Zone.EXILED, source, game);
CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false);
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
new BoostTargetEffect(card.getManaValue(), 0), false
);
ability.addTarget(new TargetControlledCreaturePermanent());
game.fireReflexiveTriggeredAbility(ability, source);
return true;
}
}

View file

@ -13,6 +13,7 @@ import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledEnchantmentPermanent; import mage.filter.common.FilterControlledEnchantmentPermanent;
import mage.filter.common.FilterEnchantmentCard; import mage.filter.common.FilterEnchantmentCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
@ -45,7 +46,6 @@ public final class CalixDestinysHand extends CardImpl {
} }
private static final FilterPermanent filter3 = new FilterControlledEnchantmentPermanent(); private static final FilterPermanent filter3 = new FilterControlledEnchantmentPermanent();
private static final FilterCard filter4 = new FilterEnchantmentCard("enchantment cards");
public CalixDestinysHand(UUID ownerId, CardSetInfo setInfo) { public CalixDestinysHand(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{W}"); super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{W}");
@ -70,7 +70,7 @@ public final class CalixDestinysHand extends CardImpl {
this.addAbility(ability); this.addAbility(ability);
// 7: Return all enchantment cards from your graveyard to the battlefield. // 7: Return all enchantment cards from your graveyard to the battlefield.
this.addAbility(new LoyaltyAbility(new ReturnFromYourGraveyardToBattlefieldAllEffect(filter4), -7)); this.addAbility(new LoyaltyAbility(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_ENCHANTMENTS), -7));
} }
private CalixDestinysHand(final CalixDestinysHand card) { private CalixDestinysHand(final CalixDestinysHand card) {

View file

@ -1,46 +1,44 @@
package mage.cards.c; package mage.cards.c;
import java.util.UUID;
import mage.abilities.TriggeredAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.RevoltCondition; import mage.abilities.condition.common.RevoltCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AbilityWord;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.watchers.common.RevoltWatcher; import mage.watchers.common.RevoltWatcher;
import java.util.UUID;
/** /**
*
* @author fireshoes * @author fireshoes
*/ */
public final class CallForUnity extends CardImpl { public final class CallForUnity extends CardImpl {
private static final String ruleText = "<i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn, " private static final DynamicValue xValue = new CountersSourceCount(CounterType.UNITY);
+ "put a unity counter on {this}.";
public CallForUnity(UUID ownerId, CardSetInfo setInfo) { public CallForUnity(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}");
// <i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn, put a unity counter on Call for Unity. // <i>Revolt</i> &mdash; At the beginning of your end step, if a permanent you controlled left the battlefield this turn, put a unity counter on Call for Unity.
TriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.UNITY.createInstance(), true)); this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.UNITY.createInstance()))
this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, RevoltCondition.instance, ruleText).addHint(RevoltCondition.getHint()), new RevoltWatcher()); .withInterveningIf(RevoltCondition.instance)
.setAbilityWord(AbilityWord.REVOLT)
.addHint(RevoltCondition.getHint()), new RevoltWatcher());
// Creatures you control get +1/+1 for each unity counter on Call for Unity. // Creatures you control get +1/+1 for each unity counter on Call for Unity.
Effect effect = new BoostControlledEffect(new CountersSourceCount(CounterType.UNITY), new CountersSourceCount(CounterType.UNITY), Duration.WhileOnBattlefield, this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(
StaticFilters.FILTER_PERMANENT_CREATURE, false); xValue, xValue, Duration.WhileOnBattlefield,
effect.setText("Creatures you control get +1/+1 for each unity counter on {this}"); StaticFilters.FILTER_PERMANENT_CREATURE, false
this.addAbility(new SimpleStaticAbility(effect)); ).setText("creatures you control get +1/+1 for each unity counter on {this}")));
} }
private CallForUnity(final CallForUnity card) { private CallForUnity(final CallForUnity card) {

View file

@ -114,7 +114,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl {
// (This temporarily manipulates Blocking values to "test" how many blockers the creature has still left to assign) // (This temporarily manipulates Blocking values to "test" how many blockers the creature has still left to assign)
List<Permanent> spentBlockers = new ArrayList<>(); List<Permanent> spentBlockers = new ArrayList<>();
for (Permanent possibleBlocker : list) { for (Permanent possibleBlocker : list) {
if (possibleBlocker.getMaxBlocks() != 0 && possibleBlocker.getBlocking() >= possibleBlocker.getMaxBlocks()) { if (possibleBlocker.getMaxBlocks() > 0 && possibleBlocker.getBlocking() >= possibleBlocker.getMaxBlocks()) {
spentBlockers.add(possibleBlocker); spentBlockers.add(possibleBlocker);
} }
} }

View file

@ -8,10 +8,11 @@ import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCreaturePermanentSameController; import mage.target.common.TargetPermanentSameController;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -28,7 +29,7 @@ public final class Cannibalize extends CardImpl {
// Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other. // Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other.
this.getSpellAbility().addEffect(new CannibalizeEffect()); this.getSpellAbility().addEffect(new CannibalizeEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2)); this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_PERMANENT_CREATURES));
} }
private Cannibalize(final Cannibalize card) { private Cannibalize(final Cannibalize card) {

Some files were not shown because too many files have changed in this diff Show more