* Genesis Ultimatum - fixed rollback error on usage with modal double faces cards (#7275);

This commit is contained in:
Oleg Agafonov 2020-12-23 09:15:04 +04:00
parent 796c8fb22e
commit 10cf9c4a4e
9 changed files with 94 additions and 46 deletions

View file

@ -1,6 +1,5 @@
package mage.cards.g;
import mage.MageItem;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileSpellEffect;
@ -18,7 +17,6 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import java.util.Objects;
import java.util.UUID;
/**
@ -49,7 +47,7 @@ class GenesisUltimatumEffect extends OneShotEffect {
private static final FilterCard filter = new FilterPermanentCard("any number of permanent cards");
GenesisUltimatumEffect() {
super(Outcome.Benefit);
super(Outcome.PutCardInPlay);
staticText = "Look at the top five cards of your library. Put any number of permanent cards " +
"from among them onto the battlefield and the rest into your hand";
}
@ -72,14 +70,13 @@ class GenesisUltimatumEffect extends OneShotEffect {
Cards toHand = new CardsImpl(player.getLibrary().getTopCards(game, 5));
player.lookAtCards(source, null, toHand, game);
TargetCard targetCard = new TargetCardInLibrary(0, 5, filter);
targetCard.withChooseHint("put to battlefield");
player.choose(outcome, toHand, targetCard, game);
Cards toBattlefield = new CardsImpl(targetCard.getTargets());
if (player.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game)) {
toBattlefield
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull) // to prevent exception https://github.com/magefree/mage/issues/7220
.map(MageItem::getId)
.filter(id -> Zone.BATTLEFIELD.equals(game.getState().getZone(id)))
.forEach(toHand::remove);
}
player.moveCards(toHand, Zone.HAND, source, game);

View file

@ -61,7 +61,7 @@ class SelectiveAdaptationEffect extends OneShotEffect {
private final String abilityName;
private final FilterCard filter;
private AbilitySelector(Class abilityClass, String abilityName) {
AbilitySelector(Class abilityClass, String abilityName) {
this.abilityClass = abilityClass;
this.abilityName = abilityName;
this.filter = new FilterCard("card with " + abilityName);
@ -121,7 +121,7 @@ class SelectiveAdaptationEffect extends OneShotEffect {
Card toBattlefield = game.getCard(target.getFirstTarget());
if (toBattlefield != null
&& player.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game)
&& game.getPermanent(toBattlefield.getId()) != null) {
&& Zone.BATTLEFIELD.equals(game.getState().getZone(toBattlefield.getId()))) {
toHand.remove(toBattlefield);
}
}

View file

@ -1,4 +0,0 @@
package org.mage.test.cards.single.iko;
public class BlitzLeechTest {
}

View file

@ -0,0 +1,47 @@
package org.mage.test.cards.single.iko;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class GenesisUltimatumTest extends CardTestPlayerBase {
@Test
public void test_Playable() {
removeAllCardsFromLibrary(playerA);
removeAllCardsFromHand(playerA);
// Look at the top five cards of your library. Put any number of permanent cards from among them onto
// the battlefield and the rest into your hand. Exile Genesis Ultimatum.
addCard(Zone.HAND, playerA, "Genesis Ultimatum"); // {G}{G}{U}{U}{U}{R}{R}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
//
addCard(Zone.LIBRARY, playerA, "Grizzly Bears", 1);
addCard(Zone.LIBRARY, playerA, "Alpha Tyrranax", 1);
addCard(Zone.LIBRARY, playerA, "Kitesail Corsair", 1);
addCard(Zone.LIBRARY, playerA, "Riverglide Pathway", 1); // mdf card
// cast spell and put 3 cards to battle
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Genesis Ultimatum");
setChoice(playerA, "Grizzly Bears^Kitesail Corsair^Riverglide Pathway");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertExileCount(playerA, "Genesis Ultimatum", 1);
assertPermanentCount(playerA, "Grizzly Bears", 1);
assertPermanentCount(playerA, "Kitesail Corsair", 1);
assertPermanentCount(playerA, "Riverglide Pathway", 1);
assertHandCount(playerA, "Alpha Tyrranax", 1);
assertLibraryCount(playerA, 0);
}
}

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.test.cards.single.iko;

View file

@ -90,6 +90,14 @@ public interface Game extends MageItem, Serializable {
Spell getSpellOrLKIStack(UUID spellId);
/**
* Find permanent on the battlefield by id. If you works with cards and want to check it on battlefield then
* use game.getState().getZone() instead. Card's id and permanent's id can be different (example: mdf card
* puts half card to battlefield, not the main card).
*
* @param permanentId
* @return
*/
Permanent getPermanent(UUID permanentId);
Permanent getPermanentOrLKIBattlefield(UUID permanentId);

View file

@ -342,7 +342,6 @@ public abstract class GameImpl implements Game, Serializable {
MageObject object;
if (state.getBattlefield().containsPermanent(objectId)) {
object = state.getBattlefield().getPermanent(objectId);
// state.setZone(objectId, Zone.BATTLEFIELD); // why is this neccessary?
return object;
}
if (getPermanentsEntering().containsKey(objectId)) {
@ -350,7 +349,6 @@ public abstract class GameImpl implements Game, Serializable {
}
for (StackObject item : state.getStack()) {
if (item.getId().equals(objectId)) {
// state.setZone(objectId, Zone.STACK); // why is this neccessary?
return item;
}
if (item.getSourceId().equals(objectId) && item instanceof Spell) {

View file

@ -167,8 +167,8 @@ public class GameState implements Serializable, Copyable<GameState> {
this.values.put(entry.getKey(), ((HashSet) entry.getValue()).clone());
} else if (entry.getValue() instanceof EnumSet) {
this.values.put(entry.getKey(), ((EnumSet) entry.getValue()).clone());
} else if (entry.getValue() instanceof HashMap){
this.values.put(entry.getKey(), ((HashMap) entry.getValue()).clone());
} else if (entry.getValue() instanceof HashMap) {
this.values.put(entry.getKey(), ((HashMap) entry.getValue()).clone());
} else {
this.values.put(entry.getKey(), entry.getValue());
}
@ -696,9 +696,7 @@ public class GameState implements Serializable, Copyable<GameState> {
public Permanent getPermanent(UUID permanentId) {
if (permanentId != null && battlefield.containsPermanent(permanentId)) {
Permanent permanent = battlefield.getPermanent(permanentId);
// setZone(permanent.getId(), Zone.BATTLEFIELD); // shouldn't this be set anyway? (LevelX2)
return permanent;
return battlefield.getPermanent(permanentId);
}
return null;
}

View file

@ -1,15 +1,16 @@
package mage.game.permanent;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.abilities.keyword.PhasingAbility;
import mage.constants.CardType;
import mage.constants.RangeOfInfluence;
import mage.filter.FilterPermanent;
import mage.game.Game;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -57,8 +58,8 @@ public class Battlefield implements Serializable {
return (int) field.values()
.stream()
.filter(permanent -> permanent.isControlledBy(controllerId)
&& filter.match(permanent, game)
&& permanent.isPhasedIn())
&& filter.match(permanent, game)
&& permanent.isPhasedIn())
.count();
}
@ -68,8 +69,8 @@ public class Battlefield implements Serializable {
* influence of the specified player id and that match the supplied filter.
*
* @param filter
* @param sourceId - sourceId of the MageObject the calling effect/ability
* belongs to
* @param sourceId - sourceId of the MageObject the calling effect/ability
* belongs to
* @param sourcePlayerId
* @param game
* @return count
@ -79,15 +80,15 @@ public class Battlefield implements Serializable {
return (int) field.values()
.stream()
.filter(permanent -> filter.match(permanent, sourceId, sourcePlayerId, game)
&& permanent.isPhasedIn())
&& permanent.isPhasedIn())
.count();
} else {
List<UUID> range = game.getState().getPlayersInRange(sourcePlayerId, game);
return (int) field.values()
.stream()
.filter(permanent -> range.contains(permanent.getControllerId())
&& filter.match(permanent, sourceId, sourcePlayerId, game)
&& permanent.isPhasedIn()).count();
&& filter.match(permanent, sourceId, sourcePlayerId, game)
&& permanent.isPhasedIn()).count();
}
}
@ -104,7 +105,7 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(permanent -> filter.match(permanent, game)
&& permanent.isPhasedIn()).count() >= num;
&& permanent.isPhasedIn()).count() >= num;
}
@ -123,8 +124,8 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(permanent -> permanent.isControlledBy(controllerId)
&& filter.match(permanent, game)
&& permanent.isPhasedIn())
&& filter.match(permanent, game)
&& permanent.isPhasedIn())
.count() >= num;
}
@ -144,14 +145,14 @@ public class Battlefield implements Serializable {
if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) {
return field.values().stream()
.filter(permanent -> filter.match(permanent, null, sourcePlayerId, game)
&& permanent.isPhasedIn()).count() >= num;
&& permanent.isPhasedIn()).count() >= num;
} else {
List<UUID> range = game.getState().getPlayersInRange(sourcePlayerId, game);
return field.values().stream()
.filter(permanent -> range.contains(permanent.getControllerId())
&& filter.match(permanent, null, sourcePlayerId, game)
&& permanent.isPhasedIn())
&& filter.match(permanent, null, sourcePlayerId, game)
&& permanent.isPhasedIn())
.count() >= num;
}
}
@ -168,6 +169,14 @@ public class Battlefield implements Serializable {
field.remove(key);
}
/**
* Find permanent on the battlefield by id. If you works with cards and want to check it on battlefield then
* use game.getState().getZone() instead. Card's id and permanent's id can be different (example: mdf card
* puts half card to battlefield, not the main card).
*
* @param key
* @return
*/
public boolean containsPermanent(UUID key) {
return field.containsKey(key);
}
@ -211,7 +220,7 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(perm -> perm.isPhasedIn()
&& perm.isControlledBy(controllerId))
&& perm.isControlledBy(controllerId))
.collect(Collectors.toList());
}
@ -300,7 +309,7 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(perm -> perm.isPhasedIn() && range.contains(perm.getControllerId())
&& filter.match(perm, sourceId, sourcePlayerId, game)).collect(Collectors.toList());
&& filter.match(perm, sourceId, sourcePlayerId, game)).collect(Collectors.toList());
}
}
@ -321,7 +330,7 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(perm -> perm.isPhasedIn()
&& range.contains(perm.getControllerId()))
&& range.contains(perm.getControllerId()))
.collect(Collectors.toList());
}
@ -331,8 +340,8 @@ public class Battlefield implements Serializable {
return field.values()
.stream()
.filter(perm -> perm.hasAbility(PhasingAbility.getInstance(), game)
&& perm.isPhasedIn()
&& perm.isControlledBy(controllerId))
&& perm.isPhasedIn()
&& perm.isControlledBy(controllerId))
.collect(Collectors.toList());
}