mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 11:02:00 -08:00
* GUI: added card icon for announced X value (card cast);
This commit is contained in:
parent
fc0ff6c22d
commit
76082e1d7a
9 changed files with 353 additions and 12 deletions
|
|
@ -7,10 +7,12 @@ import mage.abilities.Abilities;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.Mode;
|
import mage.abilities.Mode;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
|
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.Effects;
|
import mage.abilities.effects.Effects;
|
||||||
import mage.abilities.icon.CardIcon;
|
import mage.abilities.icon.CardIcon;
|
||||||
import mage.abilities.icon.other.FaceDownStatusIcon;
|
import mage.abilities.icon.other.FaceDownCardIcon;
|
||||||
|
import mage.abilities.icon.other.VariableCostCardIcon;
|
||||||
import mage.abilities.keyword.AftermathAbility;
|
import mage.abilities.keyword.AftermathAbility;
|
||||||
import mage.cards.*;
|
import mage.cards.*;
|
||||||
import mage.cards.mock.MockCard;
|
import mage.cards.mock.MockCard;
|
||||||
|
|
@ -369,7 +371,7 @@ public class CardView extends SimpleCardView {
|
||||||
this.manaCostRightStr = String.join("", mainCard.getRightHalfCard().getManaCostSymbols());
|
this.manaCostRightStr = String.join("", mainCard.getRightHalfCard().getManaCostSymbols());
|
||||||
} else if (card instanceof AdventureCard) {
|
} else if (card instanceof AdventureCard) {
|
||||||
AdventureCard adventureCard = ((AdventureCard) card);
|
AdventureCard adventureCard = ((AdventureCard) card);
|
||||||
AdventureCardSpell adventureCardSpell = ((AdventureCardSpell) adventureCard.getSpellCard());
|
AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard();
|
||||||
fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName();
|
fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName();
|
||||||
this.manaCostLeftStr = String.join("", adventureCardSpell.getManaCostSymbols());
|
this.manaCostLeftStr = String.join("", adventureCardSpell.getManaCostSymbols());
|
||||||
this.manaCostRightStr = String.join("", adventureCard.getManaCostSymbols());
|
this.manaCostRightStr = String.join("", adventureCard.getManaCostSymbols());
|
||||||
|
|
@ -420,11 +422,10 @@ public class CardView extends SimpleCardView {
|
||||||
permanent.getAbilities(game).forEach(ability -> {
|
permanent.getAbilities(game).forEach(ability -> {
|
||||||
this.cardIcons.addAll(ability.getIcons(game));
|
this.cardIcons.addAll(ability.getIcons(game));
|
||||||
});
|
});
|
||||||
// other
|
// face down
|
||||||
if (permanent.isFaceDown(game)) {
|
if (permanent.isFaceDown(game)) {
|
||||||
this.cardIcons.add(FaceDownStatusIcon.instance);
|
this.cardIcons.add(FaceDownCardIcon.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (card.isCopy()) {
|
if (card.isCopy()) {
|
||||||
this.mageObjectType = MageObjectType.COPY_CARD;
|
this.mageObjectType = MageObjectType.COPY_CARD;
|
||||||
|
|
@ -439,6 +440,25 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// card icons for any permanents and cards
|
||||||
|
if (game != null) {
|
||||||
|
// x cost
|
||||||
|
Zone cardZone = game.getState().getZone(card.getId());
|
||||||
|
if (card.getManaCost().containsX()
|
||||||
|
&& (cardZone.match(Zone.BATTLEFIELD) || cardZone.match(Zone.STACK))) {
|
||||||
|
int costX;
|
||||||
|
if (card instanceof Permanent) {
|
||||||
|
// permanent on battlefield
|
||||||
|
costX = ManacostVariableValue.ETB.calculate(game, card.getSpellAbility(), null);
|
||||||
|
} else {
|
||||||
|
// other like Stack
|
||||||
|
costX = ManacostVariableValue.REGULAR.calculate(game, card.getSpellAbility(), null);
|
||||||
|
}
|
||||||
|
this.cardIcons.add(new VariableCostCardIcon(costX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.power = Integer.toString(card.getPower().getValue());
|
this.power = Integer.toString(card.getPower().getValue());
|
||||||
this.toughness = Integer.toString(card.getToughness().getValue());
|
this.toughness = Integer.toString(card.getToughness().getValue());
|
||||||
this.cardTypes = card.getCardType(game);
|
this.cardTypes = card.getCardType(game);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import mage.cards.SplitCard;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
import mage.counters.CounterType;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -522,6 +523,138 @@ public class CopySpellTest extends CardTestPlayerBase {
|
||||||
assertAllCommandsUsed();
|
assertAllCommandsUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CopiedSpellsAndX_1() {
|
||||||
|
// testing:
|
||||||
|
// 1. x in copied instant spell (copy X)
|
||||||
|
// 2. x in copied creature (X=0)
|
||||||
|
|
||||||
|
// test use case with rules:
|
||||||
|
// https://tappedout.net/mtg-questions/copying-a-creature-with-x-in-its-mana-cost/#c3561513
|
||||||
|
// 107.3f If a card in any zone other than the stack has an {X} in its mana cost, the value of {X} is
|
||||||
|
// treated as 0, even if the value of X is defined somewhere within its text.
|
||||||
|
|
||||||
|
// Whenever you cast an instant or sorcery spell, you may pay {U}{R}. If you do, copy that spell. You may choose new targets for the copy.
|
||||||
|
// Whenever another nontoken creature enters the battlefield under your control, you may pay {G}{U}. If you do, create a token that’s a copy of that creature.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Riku of Two Reflections", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// Banefire deals X damage to any target.
|
||||||
|
addCard(Zone.HAND, playerA, "Banefire", 1); // {X}{R}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
|
//
|
||||||
|
// 0/0
|
||||||
|
// Capricopian enters the battlefield with X +1/+1 counters on it.
|
||||||
|
addCard(Zone.HAND, playerA, "Capricopian", 1); // {X}{G}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
|
||||||
|
// 1
|
||||||
|
// cast banefire and make copy
|
||||||
|
// announced X=2 must be copied
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banefire", playerB);
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
setChoice(playerA, "Yes"); // make copy
|
||||||
|
setChoice(playerA, "No"); // keep target same
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkLife("after spell copy", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 2 * 2);
|
||||||
|
|
||||||
|
// 2
|
||||||
|
// cast creature and copy it as token
|
||||||
|
// token must have x=0 (dies)
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian");
|
||||||
|
setChoice(playerA, "X=1");
|
||||||
|
setChoice(playerA, "Yes"); // make copy
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("after creature copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian", 1);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CopiedSpellsHasntETB() {
|
||||||
|
// testing:
|
||||||
|
// - x in copied creature spell (copy x)
|
||||||
|
// - copied spells enters as tokens and it hasn't ETB, see rules below
|
||||||
|
|
||||||
|
// 0/0
|
||||||
|
// Capricopian enters the battlefield with X +1/+1 counters on it.
|
||||||
|
addCard(Zone.HAND, playerA, "Capricopian", 1); // {X}{G}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
|
||||||
|
//
|
||||||
|
// Grenzo, Dungeon Warden enters the battlefield with X +1/+1 counters on it.
|
||||||
|
addCard(Zone.HAND, playerA, "Grenzo, Dungeon Warden", 1);// {X}{B}{R}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// Copy target creature spell you control, except it isn't legendary if the spell is legendary.
|
||||||
|
// (A copy of a creature spell becomes a token.)
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major", 2); // {G}{U}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||||
|
|
||||||
|
// 1. Capricopian
|
||||||
|
// cast and put on stack
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 3);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
// copy of spell
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Capricopian", "Capricopian");
|
||||||
|
|
||||||
|
// 2. Grenzo, Dungeon Warden
|
||||||
|
// cast and put on stack
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 1);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 1);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
// copy of spell
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Grenzo, Dungeon Warden", "Grenzo, Dungeon Warden");
|
||||||
|
|
||||||
|
// ETB triggers will not trigger here due not normal cast. From rules:
|
||||||
|
// - The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2021-04-16)
|
||||||
|
// - A nontoken permanent “enters the battlefield” when it’s moved onto the battlefield from another zone.
|
||||||
|
// A token “enters the battlefield” when it’s created. See rules 403.3, 603.6a, 603.6d, and 614.12.
|
||||||
|
//
|
||||||
|
// So both copies enters without counters:
|
||||||
|
// - Capricopian copy must die
|
||||||
|
// - Grenzo, Dungeon Warden must have default PT
|
||||||
|
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Capricopian", 1); // copy dies
|
||||||
|
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden", 2);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
// counters checks
|
||||||
|
int originalCounters = currentGame.getBattlefield().getAllActivePermanents().stream()
|
||||||
|
.filter(p -> p.getName().equals("Grenzo, Dungeon Warden"))
|
||||||
|
.filter(p -> !p.isCopy())
|
||||||
|
.mapToInt(p -> p.getCounters(currentGame).getCount(CounterType.P1P1))
|
||||||
|
.sum();
|
||||||
|
int copyCounters = currentGame.getBattlefield().getAllActivePermanents().stream()
|
||||||
|
.filter(p -> p.getName().equals("Grenzo, Dungeon Warden"))
|
||||||
|
.filter(p -> p.isCopy())
|
||||||
|
.mapToInt(p -> p.getCounters(currentGame).getCount(CounterType.P1P1))
|
||||||
|
.sum();
|
||||||
|
Assert.assertEquals("original grenzo must have 2x counters", 2, originalCounters);
|
||||||
|
Assert.assertEquals("copied grenzo must have 0x counters", 0, copyCounters);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_SimpleCopy_Card() {
|
public void test_SimpleCopy_Card() {
|
||||||
Card sourceCard = CardRepository.instance.findCard("Grizzly Bears").getCard();
|
Card sourceCard = CardRepository.instance.findCard("Grizzly Bears").getCard();
|
||||||
|
|
@ -628,7 +761,7 @@ public class CopySpellTest extends CardTestPlayerBase {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.fail(infoPrefix + " - " + "ability source must be same: " + ability.toString());
|
Assert.fail(infoPrefix + " - " + "ability source must be same: " + ability);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
package org.mage.test.serverside;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.view.CardView;
|
||||||
|
import mage.view.GameView;
|
||||||
|
import mage.view.PlayerView;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI tests: card icons for cards
|
||||||
|
*
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class CardIconsTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CostX_Spells() {
|
||||||
|
// Chalice of the Void enters the battlefield with X charge counters on it.
|
||||||
|
// Whenever a player casts a spell with converted mana cost equal to the number of charge counters on Chalice of the Void, counter that spell.
|
||||||
|
addCard(Zone.HAND, playerA, "Chalice of the Void", 1); // {X}{X}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
||||||
|
|
||||||
|
// hand (not visible)
|
||||||
|
runCode("card icons in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
GameView gameView = getGameView(player);
|
||||||
|
Assert.assertEquals("must have 1 card in hand", 1, gameView.getHand().values().size());
|
||||||
|
CardView cardView = gameView.getHand().values().stream().findFirst().get();
|
||||||
|
Assert.assertEquals("must have non x cost card icons in hand", 0, cardView.getCardIcons().size());
|
||||||
|
});
|
||||||
|
|
||||||
|
// cast and put on stack
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chalice of the Void");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
|
||||||
|
// stack (visible)
|
||||||
|
runCode("card icons on stack (spell)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
GameView gameView = getGameView(player);
|
||||||
|
Assert.assertEquals("must have 1 card in stack", 1, gameView.getStack().values().size());
|
||||||
|
CardView cardView = gameView.getStack().values().stream().findFirst().get();
|
||||||
|
Assert.assertEquals("must have x cost card icons in stack", 1, cardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("x cost text", "x=2", cardView.getCardIcons().get(0).getText());
|
||||||
|
});
|
||||||
|
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chalice of the Void", 1);
|
||||||
|
|
||||||
|
// battlefield (card, not visible)
|
||||||
|
runCode("card icons in battlefield (card)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
GameView gameView = getGameView(player);
|
||||||
|
PlayerView playerView = gameView.getPlayers().get(0);
|
||||||
|
Assert.assertEquals("player", player.getName(), playerView.getName());
|
||||||
|
CardView cardView = playerView.getBattlefield().values().stream().filter(p -> p.getName().equals("Chalice of the Void")).findFirst().orElse(null);
|
||||||
|
Assert.assertNotNull("must have 1 chalice in battlefield", cardView);
|
||||||
|
Assert.assertEquals("must have x cost card icons in battlefield (card)", 1, cardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("x cost text", "x=2", cardView.getCardIcons().get(0).getText());
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CostX_Copies() {
|
||||||
|
// Grenzo, Dungeon Warden enters the battlefield with X +1/+1 counters on it.
|
||||||
|
addCard(Zone.HAND, playerA, "Grenzo, Dungeon Warden", 1);// {X}{B}{R}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// Copy target creature spell you control, except it isn't legendary if the spell is legendary.
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major", 1); // {G}{U}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||||
|
|
||||||
|
// cast and put on stack
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 1);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 1);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
|
||||||
|
// prepare copy of spell
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1);
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Grenzo, Dungeon Warden", "Grenzo, Dungeon Warden");
|
||||||
|
checkStackSize("before copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true);
|
||||||
|
checkStackSize("after copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
|
||||||
|
|
||||||
|
// stack (copied spell)
|
||||||
|
runCode("card icons on stack (copied spell)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
GameView gameView = getGameView(player);
|
||||||
|
Assert.assertEquals("must have 2 cards in stack", 2, gameView.getStack().values().size());
|
||||||
|
CardView originalCardView = gameView.getStack().values()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> !c.getOriginalCard().isCopy())
|
||||||
|
.findFirst()
|
||||||
|
.get();
|
||||||
|
CardView copiedCardView = gameView.getStack().values()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> c.getOriginalCard().isCopy())
|
||||||
|
.findFirst()
|
||||||
|
.get();
|
||||||
|
Assert.assertNotNull("stack must have original spell", originalCardView);
|
||||||
|
Assert.assertNotNull("stack must have copied spell", copiedCardView);
|
||||||
|
Assert.assertNotEquals("must find two spells on stack", originalCardView.getId(), copiedCardView.getId());
|
||||||
|
Assert.assertEquals("original spell must have x cost card icons", 1, originalCardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("copied spell must have x cost card icons", 1, copiedCardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("original x cost text", "x=2", originalCardView.getCardIcons().get(0).getText());
|
||||||
|
Assert.assertEquals("copied x cost text", "x=2", copiedCardView.getCardIcons().get(0).getText());
|
||||||
|
});
|
||||||
|
|
||||||
|
// must resolve copied creature spell as a token
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grenzo, Dungeon Warden", 2);
|
||||||
|
|
||||||
|
// battlefield (card and copied card as token)
|
||||||
|
runCode("card icons in battlefield (copied)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
GameView gameView = getGameView(player);
|
||||||
|
PlayerView playerView = gameView.getPlayers().get(0);
|
||||||
|
Assert.assertEquals("player", player.getName(), playerView.getName());
|
||||||
|
// copied spell goes as token to battlefield, not copied card - so must check isToken
|
||||||
|
// original
|
||||||
|
CardView originalCardView = playerView.getBattlefield().values()
|
||||||
|
.stream()
|
||||||
|
.filter(p -> p.getName().equals("Grenzo, Dungeon Warden"))
|
||||||
|
.filter(p -> !p.isToken())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
Assert.assertNotNull("original card must be in battlefield", originalCardView);
|
||||||
|
Assert.assertEquals("original must have x cost card icons", 1, originalCardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("original x cost text", "x=2", originalCardView.getCardIcons().get(0).getText());
|
||||||
|
//
|
||||||
|
CardView copiedCardView = playerView.getBattlefield().values()
|
||||||
|
.stream()
|
||||||
|
.filter(p -> p.getName().equals("Grenzo, Dungeon Warden"))
|
||||||
|
.filter(p -> p.isToken())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
Assert.assertNotNull("copied card must be in battlefield", copiedCardView);
|
||||||
|
Assert.assertEquals("copied must have x cost card icons", 1, copiedCardView.getCardIcons().size());
|
||||||
|
Assert.assertEquals("copied x cost text", "x=0", copiedCardView.getCardIcons().get(0).getText());
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,9 @@ import mage.game.Game;
|
||||||
import mage.watchers.common.ManaSpentToCastWatcher;
|
import mage.watchers.common.ManaSpentToCastWatcher;
|
||||||
|
|
||||||
public enum ManacostVariableValue implements DynamicValue {
|
public enum ManacostVariableValue implements DynamicValue {
|
||||||
REGULAR, ETB;
|
|
||||||
|
REGULAR, // if you need X on cast/activate (in stack)
|
||||||
|
ETB; // if you need X after ETB (in battlefield)
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
|
|
@ -15,7 +17,10 @@ public enum ManacostVariableValue implements DynamicValue {
|
||||||
return sourceAbility.getManaCostsToPay().getX();
|
return sourceAbility.getManaCostsToPay().getX();
|
||||||
}
|
}
|
||||||
ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class);
|
ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class);
|
||||||
return watcher != null ? watcher.getAndResetLastXValue(sourceAbility.getSourceId()) : sourceAbility.getManaCostsToPay().getX();
|
if (watcher != null) {
|
||||||
|
return watcher.getAndResetLastXValue(sourceAbility);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ public enum CardIconType {
|
||||||
ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100),
|
ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100),
|
||||||
//
|
//
|
||||||
OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100),
|
OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100),
|
||||||
|
OTHER_COST_X("prepared/square-fill.svg", CardIconCategory.ABILITY, 100),
|
||||||
//
|
//
|
||||||
SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order
|
SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order
|
||||||
SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog
|
SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import mage.abilities.icon.CardIconType;
|
||||||
/**
|
/**
|
||||||
* @author JayDi85
|
* @author JayDi85
|
||||||
*/
|
*/
|
||||||
public enum FaceDownStatusIcon implements CardIcon {
|
public enum FaceDownCardIcon implements CardIcon {
|
||||||
instance;
|
instance;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package mage.abilities.icon.other;
|
||||||
|
|
||||||
|
import mage.abilities.icon.CardIconImpl;
|
||||||
|
import mage.abilities.icon.CardIconType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Showing x cost value
|
||||||
|
*
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class VariableCostCardIcon extends CardIconImpl {
|
||||||
|
|
||||||
|
public VariableCostCardIcon(int costX) {
|
||||||
|
super(CardIconType.OTHER_COST_X, "Announced X = " + costX, "x=" + costX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,7 +81,11 @@ public class CopyTokenFunction implements Function<Token, Card> {
|
||||||
|
|
||||||
for (Ability ability0 : sourceObj.getAbilities()) {
|
for (Ability ability0 : sourceObj.getAbilities()) {
|
||||||
Ability ability = ability0.copy();
|
Ability ability = ability0.copy();
|
||||||
ability.newOriginalId(); // The token is independant from the copy from object so it need a new original Id, otherwise there are problems to check for created continuous effects to check if the source (the Token) has still this ability
|
|
||||||
|
// The token is independant from the copy from object so it need a new original Id,
|
||||||
|
// otherwise there are problems to check for created continuous effects to check if
|
||||||
|
// the source (the Token) has still this ability
|
||||||
|
ability.newOriginalId();
|
||||||
|
|
||||||
target.addAbility(ability);
|
target.addAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package mage.watchers.common;
|
package mage.watchers.common;
|
||||||
|
|
||||||
import mage.Mana;
|
import mage.Mana;
|
||||||
|
import mage.abilities.Ability;
|
||||||
import mage.constants.WatcherScope;
|
import mage.constants.WatcherScope;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
@ -51,8 +52,14 @@ public class ManaSpentToCastWatcher extends Watcher {
|
||||||
return manaMap.getOrDefault(sourceId, null);
|
return manaMap.getOrDefault(sourceId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAndResetLastXValue(UUID sourceId) {
|
public int getAndResetLastXValue(Ability source) {
|
||||||
return xValueMap.getOrDefault(sourceId, 0);
|
if (xValueMap.containsKey(source.getSourceId())) {
|
||||||
|
// cast normal way
|
||||||
|
return xValueMap.get(source.getSourceId());
|
||||||
|
} else {
|
||||||
|
// put to battlefield without cast (example: copied spell must keep announced X)
|
||||||
|
return source.getManaCostsToPay().getX();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue