Face down images and cards rework (#11873)

Face down changes:
* GUI: added visible face down type and real card name for controller/owner (opponent can see it after game ends);
* GUI: added day/night button to view real card for controller/owner (opponent can see it after game ends);
* game: fixed that faced-down card can render symbols, abilities and other hidden data from a real card;
* images: added image support for normal faced-down cards;
* images: added image support for morph and megamorph faced-down cards;
* images: added image support for foretell faced-down cards;

Other changes:
* images: fixed missing tokens from DDD set;
* images: no more client restart to apply newly downloaded images or render settings;
* images: improved backface image quality (use main menu -> symbols to download it);
This commit is contained in:
Oleg Agafonov 2024-02-29 01:14:54 +04:00 committed by GitHub
parent 4901de12c1
commit e38a79f231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 2178 additions and 1495 deletions

View file

@ -22,8 +22,8 @@ public class BloodMoonTest extends CardTestPlayerBase {
// which replacement effects apply and how they apply, check the characteristics of the permanent as it
// would exist on the battlefield, taking into account replacement effects that have already modified how
// it enters the battlefield (see rule 616.1), continuous effects generated by the resolution of spells
// or abilities that changed the permanent’s characteristics on the stack (see rule 400.7a), and continuous
// effects from the permanent’s own static abilities, but ignoring continuous effects from any other source
// or abilities that changed the permanent's characteristics on the stack (see rule 400.7a), and continuous
// effects from the permanent's own static abilities, but ignoring continuous effects from any other source
// that would affect it.
// Grassland has to enter the battlefield tapped, because
// the Blood Moon does not prevent ETB Replacement Effects

View file

@ -1,11 +1,14 @@
package org.mage.test.cards.abilities.keywords;
import mage.cards.Card;
import mage.cards.repository.TokenRepository;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.util.CardUtil;
import mage.view.CardView;
import mage.view.GameView;
import mage.view.PermanentView;
@ -535,9 +538,14 @@ public class ManifestTest extends CardTestPlayerBase {
Permanent perm = game.getBattlefield().getAllPermanents()
.stream()
.filter(permanent -> permanent.isFaceDown(game))
.filter(permanent -> {
Assert.assertEquals("face down permanent must have not name", "", permanent.getName());
return true;
})
.findFirst()
.orElse(null);
Assert.assertNotNull(perm);
Assert.assertEquals("server side face down permanent must have empty name", EmptyNames.FACE_DOWN_CREATURE.toString(), perm.getName());
GameView gameView = new GameView(game.getState(), game, viewFromPlayer.getId(), null);
PlayerView playerView = gameView.getPlayers()
.stream()
@ -548,29 +556,34 @@ public class ManifestTest extends CardTestPlayerBase {
PermanentView permanentView = playerView.getBattlefield().values()
.stream()
.filter(CardView::isFaceDown)
.filter(p -> {
CardView debugView = new CardView((PermanentCard) currentGame.getPermanent(p.getId()), currentGame, false, false);
Assert.assertNotEquals("face down view must have name", "", p.getName());
return true;
})
.findFirst()
.orElse(null);
Assert.assertNotNull(permanentView);
return permanentView;
}
private void assertFaceDown(String info, PermanentView faceDownPermanent, String realPermanentName, boolean realInfoMustBeVisible) {
if (realInfoMustBeVisible) {
// show all info
Assert.assertEquals(realPermanentName, faceDownPermanent.getName()); // show real name
Assert.assertEquals("2", faceDownPermanent.getPower());
Assert.assertEquals("2", faceDownPermanent.getToughness());
//
Assert.assertNotNull(faceDownPermanent.getOriginal());
Assert.assertEquals(realPermanentName, faceDownPermanent.getOriginal().getName());
} else {
// hide original info
Assert.assertEquals(info, "", faceDownPermanent.getName());
Assert.assertEquals(info, "2", faceDownPermanent.getPower());
Assert.assertEquals(info, "2", faceDownPermanent.getToughness());
Assert.assertNull(info, faceDownPermanent.getOriginal());
}
private void assertFaceDown(String checkInfo, PermanentView faceDownPermanentView, String needRealName, String needFaceDownStatus, boolean needShowRealInfo) {
String info = checkInfo + " - " + faceDownPermanentView;
String needName = CardUtil.getCardNameForGUI(needShowRealInfo ? needRealName : "", needFaceDownStatus);
// check view
Assert.assertTrue(info + " - wrong face down status", faceDownPermanentView.isFaceDown());
Assert.assertEquals(info + " - wrong name", needName, faceDownPermanentView.getName()); // show real name
Assert.assertEquals(info + " - wrong power", "2", faceDownPermanentView.getPower());
Assert.assertEquals(info + " - wrong toughness", "2", faceDownPermanentView.getToughness());
// check original info
if (needShowRealInfo) {
Assert.assertNotNull(info + " - miss original card data", faceDownPermanentView.getOriginal());
Assert.assertEquals(info + " - wrong original card name", needRealName, faceDownPermanentView.getOriginal().getName());
} else {
Assert.assertNull(info + " - original data must be hidden", faceDownPermanentView.getOriginal());
}
}
@Test
@ -587,27 +600,26 @@ public class ManifestTest extends CardTestPlayerBase {
runCode("on active game", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
// hide from opponent
PermanentView permanent = findFaceDownPermanent(game, playerA, playerB);
assertFaceDown("in game: must hide from opponent", permanent, "Mountain", false);
assertFaceDown("in game: must hide from opponent", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, false);
// show for yourself
permanent = findFaceDownPermanent(game, playerB, playerB);
assertFaceDown("in game: must show for yourself", permanent, "Mountain", true);
assertFaceDown("in game: must show for yourself", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
// workaround to force end game (can't use other test commands after that)
playerA.won(currentGame);
Assert.assertTrue(currentGame.hasEnded());
// show all after game end
PermanentView permanent = findFaceDownPermanent(currentGame, playerA, playerB);
assertFaceDown("end game: must show for opponent", permanent, "Mountain", true);
assertFaceDown("end game: must show for opponent", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
//
permanent = findFaceDownPermanent(currentGame, playerB, playerB);
assertFaceDown("end game: must show for yourself", permanent, "Mountain", true);
assertFaceDown("end game: must show for yourself", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
}
}

View file

@ -21,7 +21,7 @@ public class MegamorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Morph");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Megamorph");
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn");

View file

@ -521,14 +521,10 @@ public class MorphTest extends CardTestPlayerBase {
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury using Morph");
// showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
// showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form");
addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString());
// showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA);
// showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB);
setStopAt(1, PhaseStep.END_TURN);
execute();
@ -1103,10 +1099,17 @@ public class MorphTest extends CardTestPlayerBase {
@Test
public void test_MorphIsColorlessFlash() {
// creature
// Morph {4}{G}
addCard(Zone.HAND, playerA, "Pine Walker", 1);
// land
// Morph {2}
addCard(Zone.HAND, playerA, "Zoetic Cavern", 1);
addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1);
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
//
// You may cast colorless spells and artifact spells as though they had flash.
addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1);
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Pine Walker using Morph");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Zoetic Cavern using Morph");

View file

@ -23,9 +23,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/
public class PrototypeTest extends CardTestPlayerBase {
// Prototype {2}{R} - 3/2
private static final String automaton = "Blitz Automaton";
private static final String withPrototype = " using Prototype";
private static final String automatonWithPrototype = automaton+withPrototype;
private static final String bolt = "Lightning Bolt";
private static final String cloudshift = "Cloudshift";
private static final String clone = "Clone";
@ -89,6 +91,7 @@ public class PrototypeTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.HAND, playerA, automaton);
showAvailableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);

View file

@ -2,6 +2,7 @@ package org.mage.test.cards.cost.sacrifice;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.util.RandomUtil;
import org.junit.Test;
import org.mage.test.sba.PlaneswalkerRuleTest;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -22,16 +23,15 @@ public class SacrificeLandTest extends CardTestPlayerBase {
*/
@Test
public void testRollback() {
// If Soldevi Excavations would enter the battlefield, sacrifice an untapped Island instead.
// If Soldevi Excavations entered the battlefield, sacrifice an untapped Island instead.
// If you do, put Soldevi Excavations onto the battlefield. If you don't, put it into its owner's graveyard.
String soldeviExcavations = "Soldevi Excavations";
addCard(Zone.HAND, playerA, soldeviExcavations);
addCard(Zone.BATTLEFIELD, playerA, "Island");
Random random = new Random();
boolean sacFirstLand = random.nextBoolean();
boolean sacSecondLand = random.nextBoolean();
boolean sacFirstLand = RandomUtil.nextBoolean();
boolean sacSecondLand = RandomUtil.nextBoolean();
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations);
setChoice(playerA, sacFirstLand);

View file

@ -2,21 +2,26 @@ package org.mage.test.serverside;
import mage.MageObject;
import mage.MageObjectImpl;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.Card;
import mage.cards.repository.TokenRepository;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.game.permanent.token.HumanToken;
import mage.game.permanent.token.SoldierToken;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.TokenImpl;
import mage.game.permanent.token.custom.CreatureToken;
import mage.game.stack.Spell;
import mage.util.CardUtil;
import mage.view.CardView;
import mage.view.GameView;
@ -261,13 +266,13 @@ public class TokenImagesTest extends CardTestPlayerBase {
}
}
private void assert_TokenImageNumber(String tokenName, List<Integer> needUniqueImages) {
private void assert_TokenOrCardImageNumber(String tokenOrCardName, List<Integer> needUniqueImages) {
Set<Integer> serverStats = currentGame.getBattlefield().getAllPermanents()
.stream()
.filter(card -> card.getName().equals(tokenName))
.filter(card -> card instanceof PermanentToken)
.filter(card -> card.getName().equals(tokenOrCardName))
.filter(card -> card instanceof MageObjectImpl)
.sorted(Comparator.comparing(Card::getExpansionSetCode))
.map(card -> (PermanentToken) card)
.map(card -> (MageObjectImpl) card)
.map(MageObjectImpl::getImageNumber)
.collect(Collectors.toSet());
@ -280,7 +285,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
Assert.assertNotNull(playerView);
Set<Integer> clientStats = playerView.getBattlefield().values()
.stream()
.filter(card -> card.getName().equals(tokenName))
.filter(card -> card.getName().equals(tokenOrCardName))
.sorted(Comparator.comparing(CardView::getExpansionSetCode))
.map(CardView::getImageNumber)
.collect(Collectors.toSet());
@ -289,8 +294,79 @@ public class TokenImagesTest extends CardTestPlayerBase {
String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
Assert.assertEquals(imagesNeed, imagesServer);
Assert.assertEquals(imagesNeed, imagesClient);
Assert.assertEquals("server side", imagesNeed, imagesServer);
Assert.assertEquals("client side", imagesNeed, imagesClient);
}
private void assertFaceDownCharacteristics(String info, MageObject object, String faceDownTypeName) {
String prefix = info + " - " + object;
// image info
Assert.assertEquals(prefix + " - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, object.getExpansionSetCode());
Assert.assertEquals(prefix + " - wrong card number", "0", object.getCardNumber());
Assert.assertEquals(prefix + " - wrong image file name", faceDownTypeName, object.getImageFileName());
Assert.assertNotEquals(prefix + " - wrong image number", Integer.valueOf(0), object.getImageNumber());
// characteristic checks instead new test
Assert.assertEquals(prefix + " - wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), object.getName());
Assert.assertEquals(prefix + " - wrong power", 2, object.getPower().getValue());
Assert.assertEquals(prefix + " - wrong toughness", 2, object.getToughness().getValue());
Assert.assertEquals(prefix + " - wrong color", "", object.getColor(currentGame).toString());
Assert.assertEquals(prefix + " - wrong supertypes", "[]", object.getSuperType(currentGame).toString());
Assert.assertEquals(prefix + " - wrong types", "[Creature]", object.getCardType(currentGame).toString());
Assert.assertEquals(prefix + " - wrong subtypes", "[]", object.getSubtype(currentGame).toString());
Assert.assertEquals(prefix + " - wrong abilities", 2, object.getAbilities().size()); // become face down + face up abilities only
}
private void assertOriginalData(String info, CardView cardView, int needPower, int needToughness, String needColor) {
String prefix = info + " - " + cardView;
int currentPower = cardView.getOriginalPower() == null ? 0 : cardView.getOriginalPower().getValue();
int currentToughness = cardView.getOriginalToughness() == null ? 0 : cardView.getOriginalToughness().getValue();
Assert.assertEquals(prefix + " - wrong power", needPower, currentPower);
Assert.assertEquals(prefix + " - wrong toughness", needToughness, currentToughness);
if (needColor != null) {
Assert.assertEquals(prefix + " - wrong color", needColor, cardView.getOriginalColorIdentity());
}
}
private void assert_FaceDownMorphImageNumber(List<Integer> needUniqueImages) {
Set<Integer> serverStats = currentGame.getBattlefield().getAllPermanents()
.stream()
.filter(card -> card.isFaceDown(currentGame))
.filter(card -> {
Assert.assertEquals("server side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode());
return true;
})
.sorted(Comparator.comparing(Card::getExpansionSetCode))
.map(card -> (MageObjectImpl) card)
.map(MageObjectImpl::getImageNumber)
.collect(Collectors.toSet());
// use another player to hide card view names in face down
GameView gameView = new GameView(currentGame.getState(), currentGame, playerB.getId(), null);
PlayerView playerView = gameView.getPlayers()
.stream()
.filter(p -> p.getName().equals(playerA.getName()))
.findFirst()
.orElse(null);
Assert.assertNotNull(playerView);
Set<Integer> clientStats = playerView.getBattlefield().values()
.stream()
.filter(CardView::isFaceDown)
.filter(card -> {
Assert.assertEquals("client side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode());
return true;
})
.sorted(Comparator.comparing(CardView::getExpansionSetCode))
.map(CardView::getImageNumber)
.collect(Collectors.toSet());
// server and client sides must have same data
String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
Assert.assertEquals("server side", imagesNeed, imagesServer);
Assert.assertEquals("client side", imagesNeed, imagesClient);
}
@Test
@ -317,7 +393,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
// x2 tokens
assert_MemorialToGlory(20, "40K=40");
assert_TokenImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers
assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers
}
@Test
@ -381,7 +457,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
execute();
assertPermanentCount(playerA, 1 + 10); // 1 test card + 10 tokens
assert_TokenImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image
assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image
assert_Inner("test", 0, 0, 1,
"Soldier Token", 10, false, "40K=10");
}
@ -479,7 +555,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
setStopAt(1, PhaseStep.END_TURN);
execute();
assert_TokenImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image
assert_TokenOrCardImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image
assert_Inner("test", 0, 0, 1,
"Human Token", 10, false, "MOC=10");
}
@ -614,9 +690,9 @@ public class TokenImagesTest extends CardTestPlayerBase {
@Test // it's ok for fail in 1 of 50
// TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo
// (see setFlipCoinResult and setDieRollResult), so no needs in big amout
// (see setFlipCoinResult and setDieRollResult), so no needs in big amount
public void test_Abilities_Incubator_MustTransformWithSameSettings() {
// bug with miss image data in tranformed incubator token: https://github.com/magefree/mage/issues/11535
// bug with miss image data in transformed incubator token: https://github.com/magefree/mage/issues/11535
// make sure random images take all 3 diff images
int needIncubatorTokens = 30;
@ -656,8 +732,261 @@ public class TokenImagesTest extends CardTestPlayerBase {
"Phyrexian Token", needPhyrexianTokens, false, "MOM=" + needPhyrexianTokens);
// MOM-Incubator has 1 image (number is 0)
assert_TokenImageNumber("Incubator Token", Arrays.asList(0));
assert_TokenOrCardImageNumber("Incubator Token", Arrays.asList(0));
// MOM-Phyrexian has 3 images
assert_TokenImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3));
assert_TokenOrCardImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3));
}
@Test // it's ok for fail in very rare random
// TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo
// (see setFlipCoinResult and setDieRollResult), so no needs in big amount
public void test_FaceDown_CardWithMorph_MustGetDefaultImage() {
int faceDownAmount = 15;
addCard(Zone.HAND, playerA, "Ainok Tracker", faceDownAmount); // {5}{R}, Morph {4}{R}, face up {3}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5 * faceDownAmount);
IntStream.range(0, faceDownAmount).forEach(i -> {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ainok Tracker using Morph");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount);
assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3));
}
@Test // it's ok for fail in very rare random
public void test_FaceDown_LandWithMorph_MustGetDefaultImage() {
int faceDownAmount = 15;
addCard(Zone.HAND, playerA, "Zoetic Cavern", faceDownAmount);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 * faceDownAmount);
IntStream.range(0, faceDownAmount).forEach(i -> {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount);
assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3));
}
@Test
public void test_FaceDown_Spell() {
addCard(Zone.HAND, playerA, "Zoetic Cavern", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph");
runCode("stack check", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Assert.assertEquals("stack must be active", 1, game.getState().getStack().size());
// server side spell before resolve contains full info, not empty
// so real data will be full, but view data will be hidden by face down status
String cardName = "Zoetic Cavern";
String needClientControllerName = CardUtil.getCardNameForGUI(cardName, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH);
String needClientOpponentName = CardUtil.getCardNameForGUI("", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH);
Spell spell = (Spell) game.getState().getStack().stream().findFirst().orElse(null);
Assert.assertNotNull("server - spell must exists", spell);
// make sure image from object's id works fine
IntStream.of(5).forEach(i -> {
UUID objectId = UUID.randomUUID();
int objectImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber();
Assert.assertNotEquals("wrong image number", 0, objectImageNumber);
IntStream.of(5).forEach(j -> {
int newImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber();
Assert.assertEquals("generated image numbers must be same for same id", objectImageNumber, newImageNumber);
});
});
// debug
//CardView debugViewOpponent = new CardView(spell, currentGame, false, false);
//CardView debugViewController = new CardView(spell, currentGame, true, false);
// server side (full data)
Assert.assertTrue("server - wrong face down status", spell.isFaceDown(game));
Assert.assertEquals("server - wrong color", spell.getColor(game), new ObjectColor());
Assert.assertEquals("server - wrong name", cardName, spell.getName());
//
// workaround to find image number (from id) - it must be same on each generate
int serverImageNumber = spell.getSpellAbility().getCharacteristics(game).getImageNumber();
Assert.assertNotEquals("server - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spell.getExpansionSetCode());
Assert.assertNotEquals("server - wrong image number", 0, serverImageNumber);
// client side - controller (hidden + card name)
GameView gameView = getGameView(playerA);
CardView spellView = gameView.getStack().values().stream().findFirst().orElse(null);
Assert.assertNotNull("client, controller - spell must exists", spellView);
Assert.assertTrue("client, controller - wrong face down status", spellView.isFaceDown());
Assert.assertEquals("client, controller - wrong color", spellView.getColor(), new ObjectColor());
Assert.assertEquals("client, controller - wrong spell name", needClientControllerName, spellView.getName());
//
Assert.assertEquals("client, controller - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode());
Assert.assertEquals("client, controller - wrong card number", "0", spellView.getCardNumber());
Assert.assertEquals("client, controller - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName());
Assert.assertEquals("client, controller - wrong image number", serverImageNumber, spellView.getImageNumber());
// client side - opponent (hidden)
gameView = getGameView(playerB);
spellView = gameView.getStack().values().stream().findFirst().orElse(null);
Assert.assertNotNull("client, opponent - spell must exists", spellView);
Assert.assertTrue("client, opponent - wrong face down status", spellView.isFaceDown());
Assert.assertEquals("client, opponent - wrong color", spellView.getColor(), new ObjectColor());
Assert.assertEquals("client, opponent - wrong spell name", needClientOpponentName, spellView.getName());
//
Assert.assertEquals("client, opponent - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode());
Assert.assertEquals("client, opponent - wrong card number", "0", spellView.getCardNumber());
Assert.assertEquals("client, opponent - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName());
Assert.assertEquals("client, opponent - wrong image number", serverImageNumber, spellView.getImageNumber());
});
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
}
@Test
public void test_FaceDown_Megamorph_MustGetDefaultImage() {
addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6 + 3);
// prepare face down permanent
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Megamorph");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
assertPermanentCount(playerA, "Aerie Bowmasters", 0);
Permanent permanent = getPermanent(EmptyNames.FACE_DOWN_CREATURE.toString(), playerA);
assertFaceDownCharacteristics("permanent", permanent, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH);
});
// face up it and find counter
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn this");
runCode("on face up", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0);
assertPermanentCount(playerA, "Aerie Bowmasters", 1);
assertCounterCount(playerA, "Aerie Bowmasters", CounterType.P1P1, 1);
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
@Test
public void test_FaceDown_ExileZone_MustGetDefaultImage() {
// {T}: Draw a card, then exile a card from your hand face down.
addCard(Zone.BATTLEFIELD, playerA, "Bane Alley Broker", 1);
addCard(Zone.HAND, playerA, "Forest", 1);
// exile face down
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw a card");
addTarget(playerA, "Forest");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// check face down card in exile
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0);
GameView gameView = getGameView(playerA);
CardView controllerCardView = gameView.getExile()
.stream()
.flatMap(e -> e.values().stream())
.findFirst()
.orElse(null);
gameView = getGameView(playerB);
CardView opponentCardView = gameView.getExile()
.stream()
.flatMap(e -> e.values().stream())
.findFirst()
.orElse(null);
// server side (full data)
// TODO: possible bugged?! Other abilities must not see faced-down card as real on server side!
String needName = "Forest";
Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame));
Assert.assertEquals("server side - wrong name", needName, card.getName());
Assert.assertEquals("server side - wrong abilities", 2, card.getAbilities(currentGame).size()); // play + add mana
// client side - controller (hidden data + original name)
needName = "Face Down: Forest";
Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName());
Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown());
Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size());
assertOriginalData("controller, original data", controllerCardView, 0, 0, "");
// client side - opponent (hidden data)
needName = "Face Down";
Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown());
Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName());
Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size());
assertOriginalData("opponent, original data", opponentCardView, 0, 0, "");
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
@Test
public void test_FaceDown_ForetellInExile_MustGetDefaultImage() {
// Foretell {1}{U}
addCard(Zone.HAND, playerA, "Behold the Multiverse", 1);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
// exile face down as foretell
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}{U}");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// check face down card
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0);
GameView gameView = getGameView(playerA);
CardView controllerCardView = gameView.getExile()
.stream()
.flatMap(e -> e.values().stream())
.findFirst()
.orElse(null);
gameView = getGameView(playerB);
CardView opponentCardView = gameView.getExile()
.stream()
.flatMap(e -> e.values().stream())
.findFirst()
.orElse(null);
// server side (full data)
// TODO: possible bugged?! Other abilities must not see faced-down card as real on server side!
String needName = "Behold the Multiverse";
Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame));
Assert.assertEquals("server side - wrong name", needName, card.getName());
Assert.assertTrue("server side - wrong abilities", card.getAbilities(currentGame).size() > 0);
// client side - controller (hidden data + original name)
needName = "Foretell: Behold the Multiverse";
Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName());
Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown());
Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size());
assertOriginalData("controller, original data", controllerCardView, 0, 0, "");
// client side - opponent (hidden data)
needName = "Foretell";
Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown());
Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName());
Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size());
assertOriginalData("opponent, original data", opponentCardView, 0, 0, "");
});
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
}

View file

@ -3,10 +3,12 @@ package org.mage.test.utils;
import mage.MageObject;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.CommanderCardType;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.util.GameLog;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.junit.Assert;
import org.junit.Ignore;
@ -29,6 +31,11 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
// * client side: inject additional elements for popup support (e.g. "a" with "href")
// * client side: process mouse move over a href and show object data like a card popup
private Document parseHtmlLog(String originalLog) {
// replace log's face down info by real empty name (need for compatibility)
return Jsoup.parse(originalLog.replace(EmptyNames.EMPTY_NAME_IN_LOGS, ""));
}
private void assertObjectHtmlLog(String originalLog, String needVisibleColorPart, String needVisibleNormalPart, String needId) {
String needVisibleFull = needVisibleColorPart;
if (!needVisibleNormalPart.isEmpty()) {
@ -44,7 +51,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
Assert.assertTrue(mesPrefix + "can't find id" + mesPostfix, originalLog.contains(needId));
// html check
Element html = Jsoup.parse(originalLog);
Element html = parseHtmlLog(originalLog);
Assert.assertEquals(mesPrefix + "can't find full text" + mesPostfix, needVisibleFull, html.text());
Element htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null);
Assert.assertNotNull(mesPrefix + "can't find tag [font]" + mesPostfix, htmlFont);
@ -53,7 +60,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
// improved log from client (with href and popup support)
String popupLog = GameLog.injectPopupSupport(originalLog);
html = Jsoup.parse(popupLog);
html = parseHtmlLog(popupLog);
Assert.assertEquals(mesPrefix + "injected, can't find full text" + mesPostfix, needVisibleFull, html.text());
// href
Element htmlA = html.getElementsByTag("a").stream().findFirst().orElse(null);
@ -99,7 +106,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
}
@Test
@Ignore // TODO: Fix test failure related to e264457
@Ignore
public void test_ObjectNamesInHtml() {
skipInitShuffling();
@ -127,7 +134,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
.stream()
.map(c -> currentGame.getObject(c))
.collect(Collectors.toList()));
Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // defaul commander game already contains +1 commander
Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // default commander game already contains +1 commander
sampleObjects.forEach(this::assertObjectSupport);
}