* GUI: face down cards improved:

* now it show face up cards in game logs on game end;
 * now it show face up cards in battlefield on game end (#4635);
 * fixed that real card face was visible in network data;
This commit is contained in:
Oleg Agafonov 2023-04-01 18:39:38 +04:00
parent 6017f35517
commit 4bf3e43da6
4 changed files with 131 additions and 22 deletions

View file

@ -26,7 +26,7 @@ public class PermanentView extends CardView {
private final boolean summoningSickness;
private final int damage;
private List<UUID> attachments;
private final CardView original; // original card before transforms and modifications
private final CardView original; // original card before transforms and modifications (null for opponents face down cards)
private final boolean copy;
private final String nameOwner; // only filled if != controller
private final boolean controlled;
@ -51,13 +51,17 @@ public class PermanentView extends CardView {
attachments.addAll(permanent.getAttachments());
}
this.attachedTo = permanent.getAttachedTo();
// show face down cards to all players at the game end
boolean showFaceDownInfo = controlled || game.hasEnded();
if (isToken()) {
original = new CardView(((PermanentToken) permanent).getToken().copy(), (Game) null);
original.expansionSetCode = permanent.getExpansionSetCode();
expansionSetCode = permanent.getExpansionSetCode();
} else {
if (card != null) {
// original may not be face down
if (card != null && showFaceDownInfo) {
// face down card must be hidden from opponent, but shown on game end for all
original = new CardView(card.copy(), (Game) null);
} else {
original = null;
@ -71,10 +75,7 @@ public class PermanentView extends CardView {
if (permanent.isCopy() && permanent.isFlipCard()) {
this.alternateName = permanent.getFlipCardName();
} else {
if (controlled // controller may always know
|| (!morphed && !manifested)) { // others don't know for morph or transformed cards
this.alternateName = original.getName();
}
this.alternateName = original.getName();
}
}
if (permanent.getOwnerId() != null && !permanent.getOwnerId().equals(permanent.getControllerId())) {
@ -88,8 +89,9 @@ public class PermanentView extends CardView {
this.nameOwner = "";
}
// add info for face down permanents
if (permanent.isFaceDown(game) && card != null) {
if (controlled) {
if (showFaceDownInfo) {
// must be a morphed or manifested card
for (Ability permanentAbility : permanent.getAbilities(game)) {
if (permanentAbility.getWorksFaceDown()) {

View file

@ -4,13 +4,19 @@ import mage.cards.Card;
import mage.constants.EmptyNames;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.view.CardView;
import mage.view.GameView;
import mage.view.PermanentView;
import mage.view.PlayerView;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author LevelX2
* @author LevelX2, JayDi85
*/
public class ManifestTest extends CardTestPlayerBase {
@ -391,7 +397,7 @@ public class ManifestTest extends CardTestPlayerBase {
@Test
public void testWhisperwoodElemental() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Seismic Rupture deals 2 damage to each creature without flying.
addCard(Zone.HAND, playerA, "Seismic Rupture", 1);
@ -474,7 +480,7 @@ public class ManifestTest extends CardTestPlayerBase {
}
@Test
@Test
public void test_ManifestSorceryAndBlinkIt() {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1);
@ -483,11 +489,11 @@ public class ManifestTest extends CardTestPlayerBase {
// {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library.
addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
// Exile target creature you control, then return that card to the battlefield under your control.
addCard(Zone.HAND, playerB, "Cloudshift", 1); //Instant {W}
// Devoid
// Flying
// At the beginning of your upkeep, sacrifice a creature
@ -502,14 +508,14 @@ public class ManifestTest extends CardTestPlayerBase {
setChoice(playerB, "Silvercoat Lion");
waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, playerB);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString());
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
// no life gain
// no life gain
assertLife(playerA, 20);
assertLife(playerB, 20);
@ -517,11 +523,91 @@ public class ManifestTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
assertGraveyardCount(playerB, "Cloudshift", 1);
assertPermanentCount(playerB, "Lightning Bolt", 0);
assertExileCount(playerB, "Lightning Bolt", 1);
assertPermanentCount(playerB, "Lightning Bolt", 0);
assertExileCount(playerB, "Lightning Bolt", 1);
assertHandCount(playerB, "Mountain", 1);
}
}
private PermanentView findFaceDownPermanent(Game game, TestPlayer viewFromPlayer, TestPlayer searchInPlayer) {
Permanent perm = game.getBattlefield().getAllPermanents()
.stream()
.filter(permanent -> permanent.isFaceDown(game))
.findFirst()
.orElse(null);
Assert.assertNotNull(perm);
GameView gameView = new GameView(game.getState(), game, viewFromPlayer.getId(), null);
PlayerView playerView = gameView.getPlayers()
.stream()
.filter(view -> view.getPlayerId().equals(searchInPlayer.getId()))
.findFirst()
.orElse(null);
Assert.assertNotNull(playerView);
PermanentView permanentView = playerView.getBattlefield().values()
.stream()
.filter(CardView::isFaceDown)
.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());
}
}
@Test
public void test_FaceDownCardsMustBeVisibleOnGameEnd() {
// Exile target creature. Its controller manifests the top card of their library {1}{U}
addCard(Zone.HAND, playerA, "Reality Shift");
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
//
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reality Shift", "Silvercoat Lion");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
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);
// show for yourself
permanent = findFaceDownPermanent(game, playerB, playerB);
assertFaceDown("in game: must show for yourself", permanent, "Mountain", 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);
//
permanent = findFaceDownPermanent(currentGame, playerB, playerB);
assertFaceDown("end game: must show for yourself", permanent, "Mountain", true);
}
}

View file

@ -30,7 +30,7 @@ public class AuratouchedMageTest extends CardTestPlayerBase {
* made the Mage an artifact, for example, you could search for an Aura with
* enchant artifact. (2005-10-01)
*/
@Test
@Test // TODO: fix very rare random fails (16 of 1000)
public void testAuratouchedMageEffectHasMadeIntoTypeArtifact() {
//Expected result: An effect has made Auratouched Mage into an artifact upon entering the battlefield. An aura that only works on artifacts should work.
setStrictChooseMode(true);

View file

@ -675,8 +675,8 @@ public abstract class GameImpl implements Game {
spell = (Spell) obj;
} else if (obj != null) {
logger.error(String.format(
"getSpellOrLKIStack got non-spell id %s correlating to non-spell object %s.",
obj.getClass().getName(), obj.getName()),
"getSpellOrLKIStack got non-spell id %s correlating to non-spell object %s.",
obj.getClass().getName(), obj.getName()),
new Throwable()
);
}
@ -1398,6 +1398,27 @@ public abstract class GameImpl implements Game {
logger.debug("END of gameId: " + this.getId());
endTime = new Date();
state.endGame();
// inform players about face down cards
state.getBattlefield().getAllPermanents()
.stream()
.filter(permanent -> permanent.isFaceDown(this))
.map(permanent -> {
Player player = this.getPlayer(permanent.getControllerId());
Card card = permanent.getMainCard();
if (card != null) {
return String.format("Face down card reveal: %s had %s",
(player == null ? "Unknown" : player.getLogName()),
permanent.getLogName());
} else {
return null;
}
})
.filter(Objects::nonNull)
.sorted()
.forEach(this::informPlayers);
// cancel all player dialogs/feedbacks
for (Player player : state.getPlayers().values()) {
player.abort();
}