* Rally the Ancestors - Fixed that creatures cards were moved to exile also if already in the graveyard. Problem was that the zoneChangeCounter was not raised as a permanent card left the battlefield. So some more fixes were neccessary for implementations that are based on this fixed zoneChangeCounter of permanents leaving the battlefield. I guess there will be some more bugs caused by this change but I guess this is the correct way to go.

This commit is contained in:
LevelX2 2015-01-31 19:17:22 +01:00
parent dbbbbc0279
commit faa2b0a0bf
18 changed files with 77 additions and 37 deletions

View file

@ -107,8 +107,10 @@ class GhastlyConscriptionEffect extends OneShotEffect {
}
Collections.shuffle(cardsToManifest);
game.informPlayers(controller.getName() + " shuffles the face-down pile");
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
for (Card card: cardsToManifest) {
if (card.moveToZone(Zone.BATTLEFIELD, source.getSourceId(), game, false)) {
if (card.moveToZone(Zone.BATTLEFIELD, newSource.getSourceId(), game, false)) {
game.informPlayers(new StringBuilder(controller.getName())
.append(" puts facedown card from exile onto the battlefield").toString());
ManaCosts<ManaCost> manaCosts = null;
@ -120,7 +122,7 @@ class GhastlyConscriptionEffect extends OneShotEffect {
}
ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED);
effect.setTargetPointer(new FixedTarget(card.getId()));
game.addEffect(effect, source);
game.addEffect(effect, newSource);
}
}
return true;

View file

@ -65,7 +65,7 @@ public class HungeringYeti extends CardImpl {
// As long as you control a green or blue permanent, you may cast Hungering Yeti as though it had flash.
AsThoughEffect effect = new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame);
effect.setText("As long as you control a green or blue permanent, you may cast Hungering Yeti as though it had flash");
effect.setText("As long as you control a green or blue permanent, you may cast {this} as though it had flash");
this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalAsThoughEffect(effect,
new PermanentsOnTheBattlefieldCondition(filter))));

View file

@ -119,7 +119,7 @@ class JeskaiInfiltratorEffect extends OneShotEffect {
sourceCard.setFaceDown(true);
cardsToManifest.add(sourceCard);
}
if (player.getLibrary().size() > 0) {
if (sourcePermanent!= null && player.getLibrary().size() > 0) {
Card cardFromLibrary = player.getLibrary().removeFromTop(game);
cardFromLibrary.setFaceDown(true);
player.moveCardToExileWithInfo(cardFromLibrary, sourcePermanent.getId(), sourcePermanent.getName(), source.getSourceId(), game, Zone.LIBRARY);
@ -127,8 +127,10 @@ class JeskaiInfiltratorEffect extends OneShotEffect {
}
Collections.shuffle(cardsToManifest);
game.fireUpdatePlayersEvent(); // removes Jeskai from Battlefield, so he returns as a fresh permanent to the battlefield with new position
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
for (Card card : cardsToManifest) {
if (card.moveToZone(Zone.BATTLEFIELD, source.getSourceId(), game, false)) {
if (card.moveToZone(Zone.BATTLEFIELD, newSource.getSourceId(), game, false)) {
game.informPlayers(new StringBuilder(player.getName())
.append(" puts facedown card from exile onto the battlefield").toString());
ManaCosts<ManaCost> manaCosts = null;
@ -145,7 +147,7 @@ class JeskaiInfiltratorEffect extends OneShotEffect {
FaceDownType.MANIFESTED
);
effect.setTargetPointer(new FixedTarget(card.getId()));
game.addEffect(effect, source);
game.addEffect(effect, newSource);
}
}

View file

@ -59,7 +59,8 @@ public class RallyTheAncestors extends CardImpl {
super(ownerId, 22, "Rally the Ancestors", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{X}{W}{W}");
this.expansionSetCode = "FRF";
// Return each creature card with converted mana cost X or less from your graveyard to the battlefield. Exile those creatures at the beginning of your next upkeep. Exile Rally the Ancestors.
// Return each creature card with converted mana cost X or less from your graveyard to the battlefield.
// Exile those creatures at the beginning of your next upkeep. Exile Rally the Ancestors.
this.getSpellAbility().addEffect(new RallyTheAncestorsEffect());
this.getSpellAbility().addEffect(ExileSpellEffect.getInstance());
}
@ -101,7 +102,7 @@ class RallyTheAncestorsEffect extends OneShotEffect {
for (Card card : cards) {
if (card != null) {
player.putOntoBattlefieldWithInfo(card, game, Zone.GRAVEYARD, source.getSourceId());
Effect exileEffect = new ExileTargetEffect();
Effect exileEffect = new ExileTargetEffect("Exile those creatures at the beginning of your next upkeep");
exileEffect.setTargetPointer(new FixedTarget(card.getId()));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(exileEffect);
delayedAbility.setSourceId(source.getSourceId());

View file

@ -28,6 +28,7 @@
package mage.sets.theros;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.EntersBattlefieldAbility;
@ -121,11 +122,12 @@ class AshiokNightmareWeaverExileEffect extends OneShotEffect {
UUID exileId = CardUtil.getCardExileZoneId(game, source);
Player opponent = game.getPlayer(this.getTargetPointer().getFirst(game, source));
Player controller = game.getPlayer(source.getControllerId());
if (opponent != null && controller != null) {
MageObject sourceObject = game.getObject(source.getSourceId());
if (sourceObject != null && opponent != null && controller != null) {
for (int i = 0; i < 3; i++) {
Card card = opponent.getLibrary().getFromTop(game);
if (card != null) {
controller.moveCardToExileWithInfo(card, exileId, "Ashiok, Nightmare Weaver", source.getSourceId(), game, Zone.LIBRARY);
controller.moveCardToExileWithInfo(card, exileId, sourceObject.getLogName(), source.getSourceId(), game, Zone.LIBRARY);
}
}
return true;
@ -166,7 +168,8 @@ class AshiokNightmareWeaverPutIntoPlayEffect extends OneShotEffect {
FilterCard filter = new FilterCreatureCard(new StringBuilder("creature card with converted mana cost {").append(cmc).append("} exiled with Ashiok, Nightmare Weaver").toString());
filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.Equal, cmc));
Target target = new TargetCardInExile(filter, CardUtil.getCardExileZoneId(game, source));
Target target = new TargetCardInExile(filter, CardUtil.getCardExileZoneId(game, source.getSourceId(), game.getPermanent(source.getSourceId()) == null));
if (target.canChoose(source.getSourceId(), player.getId(), game)) {
if (player.chooseTarget(Outcome.PutCreatureInPlay, target, source, game)) {

View file

@ -83,7 +83,7 @@ public class BanisherPriestTest extends CardTestPlayerBase {
* Rockslide Elemental
* Creature Elemental 1/1, 2R (3)
* First strike.
* Whenever another creature dies, you may put a +1/+1 counter on Rockslide Elemental..
* Whenever another creature dies, you may put a +1/+1 counter on Rockslide Elemental.
*/
addCard(Zone.BATTLEFIELD, playerB, "Rockslide Elemental");

View file

@ -76,7 +76,11 @@ public abstract class DelayedTriggeredAbility extends TriggeredAbilityImpl {
return triggerOnlyOnce;
}
public void init(Game game) {};
public void init(Game game) {
for (Effect effect: this.getEffects()) {
effect.getTargetPointer().init(game, this);
}
};
public boolean isInactive(Game game) {
return false;

View file

@ -61,14 +61,6 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg
this.targetController = ability.targetController;
}
@Override
public void init(Game game) {
super.init(game);
for (Effect effect: this.getEffects()) {
effect.getTargetPointer().init(game, this);
}
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == EventType.END_TURN_STEP_PRE) {

View file

@ -100,7 +100,8 @@ class ReturnExiledPermanentsEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
ExileZone exile = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source));
// because source already changed zone we have to get previous related exile zone
ExileZone exile = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId(), true));
if (exile != null) {
LinkedList<UUID> cards = new LinkedList<>(exile);
for (UUID cardId : cards) {

View file

@ -74,10 +74,12 @@ public class ManifestEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
List<Card> cards = controller.getLibrary().getTopCards(game, amount);
for (Card card: cards) {
card.setFaceDown(true);
controller.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, source.getSourceId());
controller.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, newSource.getSourceId());
Permanent permanent = game.getPermanent(card.getId());
if (permanent != null) {
permanent.setManifested(true);
@ -90,7 +92,7 @@ public class ManifestEffect extends OneShotEffect {
}
ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED);
effect.setTargetPointer(new FixedTarget(card.getId()));
game.addEffect(effect, source);
game.addEffect(effect, newSource);
}
}
game.applyEffects(); // to apply before ETB triggered or replace Effects are executed

View file

@ -78,10 +78,12 @@ public class ManifestTargetPlayerEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source));
if (targetPlayer != null) {
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
List<Card> cards = targetPlayer.getLibrary().getTopCards(game, amount);
for (Card card: cards) {
card.setFaceDown(true);
targetPlayer.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, source.getSourceId());
targetPlayer.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, newSource.getSourceId());
Permanent permanent = game.getPermanent(card.getId());
if (permanent != null) {
permanent.setManifested(true);
@ -94,7 +96,7 @@ public class ManifestTargetPlayerEffect extends OneShotEffect {
}
ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED);
effect.setTargetPointer(new FixedTarget(card.getId()));
game.addEffect(effect, source);
game.addEffect(effect, newSource);
} }
return true;
}

View file

@ -126,6 +126,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
ruleText = sb.toString();
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(morphCosts, FaceDownType.MORPHED));
ability.setWorksFaceDown(true);
ability.setRuleVisible(false);
card.addAbility(ability);

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.abilities.Ability;
@ -36,9 +37,9 @@ public class UndyingAbility extends DiesTriggeredAbility {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (super.checkTrigger(event, game)) {
Permanent p = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD);
if (!p.getCounters().containsKey(CounterType.P1P1) || p.getCounters().getCount(CounterType.P1P1) == 0) {
game.getState().setValue(new StringBuilder("undying").append(getSourceId()).toString(), new FixedTarget(p.getId()));
Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD);
if (!permanent.getCounters().containsKey(CounterType.P1P1) || permanent.getCounters().getCount(CounterType.P1P1) == 0) {
game.getState().setValue("undying" + getSourceId(),permanent.getId());
return true;
}
}
@ -109,8 +110,10 @@ class UndyingReplacementEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getTargetId().equals(source.getSourceId())) {
Object fixedTarget = game.getState().getValue(new StringBuilder("undying").append(source.getSourceId()).toString());
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getFirst(game, source).equals(source.getSourceId())) {
// Check if undying condition is true
UUID target = (UUID) game.getState().getValue("undying" + source.getSourceId());
if (target.equals(source.getSourceId())) {
game.getState().setValue("undying" + source.getSourceId(), null);
return true;
}
}

View file

@ -77,6 +77,7 @@ public interface Card extends MageObject {
void assignNewId();
int getZoneChangeCounter();
void updateZoneChangeCounter();
void addInfo(String key, String value);

View file

@ -665,7 +665,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return zoneChangeCounter;
}
private void updateZoneChangeCounter() {
@Override
public void updateZoneChangeCounter() {
zoneChangeCounter++;
}

View file

@ -135,9 +135,10 @@ public class PermanentCard extends PermanentImpl {
Zone fromZone = game.getState().getZone(objectId);
Player controller = game.getPlayer(controllerId);
if (controller != null && controller.removeFromBattlefield(this, game)) {
Card originalCard = game.getCard(this.getId());
if (isFaceDown()) {
setFaceDown(false);
game.getCard(this.getId()).setFaceDown(false); //TODO: Do this in a better way
originalCard.setFaceDown(false); //TODO: Do this in a better way
}
ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone, appliedEffects);
if (!game.replaceEvent(event)) {
@ -145,6 +146,7 @@ public class PermanentCard extends PermanentImpl {
game.rememberLKI(objectId, Zone.BATTLEFIELD, this);
if (owner != null) {
this.setControllerId(ownerId); // neccessary for e.g. abilities in graveyard or hand to not have a controller != owner
originalCard.updateZoneChangeCounter();
switch (event.getToZone()) {
case GRAVEYARD:
owner.putInGraveyard(card, game, !flag);
@ -195,6 +197,8 @@ public class PermanentCard extends PermanentImpl {
ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects);
if (!game.replaceEvent(event)) {
game.rememberLKI(objectId, Zone.BATTLEFIELD, this);
// update zone change counter of original card
game.getCard(this.getId()).updateZoneChangeCounter();
if (exileId == null) {
game.getExile().getPermanentExile().add(card);
} else {

View file

@ -7,7 +7,9 @@ import java.util.Map;
import java.util.UUID;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
public class FirstTargetPointer implements TargetPointer {
@ -47,7 +49,12 @@ public class FirstTargetPointer implements TargetPointer {
Card card = game.getCard(targetId);
if (card != null && zoneChangeCounter.containsKey(targetId)
&& card.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) {
continue;
// because if dies trigger has to trigger as permanent has already moved zone, we have to check if target was on the battlefield immed. before
// but no longer if new permanent is already on the battlefield
Permanent permanent = game.getPermanentOrLKIBattlefield(targetId);
if (permanent == null || permanent.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) {
continue;
}
}
target.add(targetId);
}
@ -62,7 +69,12 @@ public class FirstTargetPointer implements TargetPointer {
Card card = game.getCard(targetId);
if (card != null && zoneChangeCounter.containsKey(targetId)
&& card.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) {
return null;
// because if dies trigger has to trigger as permanent has already moved zone, we have to check if target was on the battlefield immed. before
// but no longer if new permanent is already on the battlefield
Permanent permanent = game.getPermanentOrLKIBattlefield(targetId);
if (permanent == null || permanent.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) {
return null;
}
}
}
return targetId;

View file

@ -479,7 +479,11 @@ public class CardUtil {
}
public static UUID getCardExileZoneId(Game game, UUID sourceId) {
String key = getCardZoneString("SourceExileZone", sourceId, game);
return getCardExileZoneId(game, sourceId, false);
}
public static UUID getCardExileZoneId(Game game, UUID sourceId, boolean previous) {
String key = getCardZoneString("SourceExileZone", sourceId, game, previous);
UUID exileId = (UUID) game.getState().getValue(key);
if (exileId == null) {
exileId = UUID.randomUUID();
@ -499,6 +503,11 @@ public class CardUtil {
* @return
*/
public static String getCardZoneString(String text, UUID cardId, Game game) {
return getCardZoneString(text, cardId, game, false);
}
public static String getCardZoneString(String text, UUID cardId, Game game, boolean previous) {
StringBuilder uniqueString = new StringBuilder();
if (text != null) {
uniqueString.append(text);
@ -506,7 +515,7 @@ public class CardUtil {
uniqueString.append(cardId);
Card card = game.getCard(cardId); // if called for a token, the id is enough
if (card != null) {
uniqueString.append(card.getZoneChangeCounter());
uniqueString.append(previous ? card.getZoneChangeCounter() - 1: card.getZoneChangeCounter());
}
return uniqueString.toString();
}