Reworked ability source object handling.

This commit is contained in:
LevelX2 2018-10-21 21:37:23 +02:00
parent e6b78d7a2e
commit 26a93d4427
19 changed files with 292 additions and 288 deletions

View file

@ -1,6 +1,9 @@
package mage.cards.c; package mage.cards.c;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
@ -31,11 +34,6 @@ import mage.target.common.TargetLandPermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/** /**
* *
* @author MTGfan * @author MTGfan
@ -123,7 +121,6 @@ class CyclopeanTombCreateTriggeredEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
Permanent tomb = game.getPermanentOrLKIBattlefield(source.getSourceId()); // we need to set the correct source object Permanent tomb = game.getPermanentOrLKIBattlefield(source.getSourceId()); // we need to set the correct source object
DelayedTriggeredAbility ability = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(new CyclopeanTombEffect(), Duration.EndOfGame, false); DelayedTriggeredAbility ability = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(new CyclopeanTombEffect(), Duration.EndOfGame, false);
ability.setSourceObject(tomb, game);
ability.setControllerId(source.getControllerId()); ability.setControllerId(source.getControllerId());
ability.setSourceId(source.getSourceId()); ability.setSourceId(source.getSourceId());
game.addDelayedTriggeredAbility(ability); game.addDelayedTriggeredAbility(ability);

View file

@ -1,5 +1,6 @@
package mage.cards.g; package mage.cards.g;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
@ -27,8 +28,6 @@ import mage.players.Player;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/** /**
* *
* @author noahg * @author noahg
@ -111,7 +110,6 @@ class GiantOysterCreateDelayedTriggerEffects extends OneShotEffect {
Effect addCountersEffect = new AddCountersTargetEffect(CounterType.M1M1.createInstance(1)); Effect addCountersEffect = new AddCountersTargetEffect(CounterType.M1M1.createInstance(1));
addCountersEffect.setTargetPointer(getTargetPointer().getFixedTarget(game, source)); addCountersEffect.setTargetPointer(getTargetPointer().getFixedTarget(game, source));
DelayedTriggeredAbility drawStepAbility = new AtTheBeginOfYourNextDrawStepDelayedTriggeredAbility(addCountersEffect, Duration.Custom, false); DelayedTriggeredAbility drawStepAbility = new AtTheBeginOfYourNextDrawStepDelayedTriggeredAbility(addCountersEffect, Duration.Custom, false);
drawStepAbility.setSourceObject(oyster, game);
drawStepAbility.setControllerId(source.getControllerId()); drawStepAbility.setControllerId(source.getControllerId());
UUID drawStepAbilityUUID = game.addDelayedTriggeredAbility(drawStepAbility, source); UUID drawStepAbilityUUID = game.addDelayedTriggeredAbility(drawStepAbility, source);

View file

@ -1,4 +1,3 @@
package mage.cards.p; package mage.cards.p;
import java.util.UUID; import java.util.UUID;
@ -9,9 +8,9 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.util.functions.EmptyApplyToPermanent; import mage.util.functions.EmptyApplyToPermanent;
@ -23,7 +22,7 @@ import mage.util.functions.EmptyApplyToPermanent;
public final class PermeatingMass extends CardImpl { public final class PermeatingMass extends CardImpl {
public PermeatingMass(UUID ownerId, CardSetInfo setInfo) { public PermeatingMass(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}");
this.subtype.add(SubType.SPIRIT); this.subtype.add(SubType.SPIRIT);
this.power = new MageInt(1); this.power = new MageInt(1);
this.toughness = new MageInt(3); this.toughness = new MageInt(3);
@ -62,7 +61,7 @@ class PermeatingMassEffect extends OneShotEffect {
public boolean apply(Game game, Ability ability) { public boolean apply(Game game, Ability ability) {
Permanent copyTo = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, ability)); Permanent copyTo = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, ability));
if (copyTo != null) { if (copyTo != null) {
Permanent copyFrom = (Permanent) ability.getSourceObject(game); Permanent copyFrom = ability.getSourcePermanentOrLKI(game);
if (copyFrom != null) { if (copyFrom != null) {
game.copyPermanent(Duration.Custom, copyFrom, copyTo.getId(), ability, new EmptyApplyToPermanent()); game.copyPermanent(Duration.Custom, copyFrom, copyTo.getId(), ability, new EmptyApplyToPermanent());
} }

View file

@ -39,9 +39,11 @@ public class SpitefulShadowsTest extends CardTestPlayerBase {
@Test @Test
public void testCard1() { public void testCard1() {
addCard(Zone.BATTLEFIELD, playerA, "Craw Wurm"); addCard(Zone.BATTLEFIELD, playerA, "Craw Wurm"); // Creature 6/4
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
// Enchant creature
// Whenever enchanted creature is dealt damage, it deals that much damage to its controller.
addCard(Zone.HAND, playerA, "Spiteful Shadows"); addCard(Zone.HAND, playerA, "Spiteful Shadows");
addCard(Zone.HAND, playerA, "Lightning Bolt"); addCard(Zone.HAND, playerA, "Lightning Bolt");

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.oneshot.exile; package org.mage.test.cards.abilities.oneshot.exile;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -70,10 +69,9 @@ public class FiendHunterTest extends CardTestPlayerBase {
execute(); execute();
assertExileCount("Silvercoat Lion", 1); assertExileCount("Silvercoat Lion", 1);
assertPermanentCount(playerB, "Primeval Titan", 1);
assertPermanentCount(playerA, "Fiend Hunter", 1); assertPermanentCount(playerA, "Fiend Hunter", 1);
assertPermanentCount(playerA, "Restoration Angel", 1); assertPermanentCount(playerA, "Restoration Angel", 1);
assertPermanentCount(playerB, "Primeval Titan", 1);
} }

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.other; package org.mage.test.cards.abilities.other;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -169,11 +168,13 @@ public class LimitedCountedActivationsTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Terror", 1); assertGraveyardCount(playerB, "Terror", 1);
assertPermanentCount(playerA, "Dragon Whelp", 1);
assertPowerToughness(playerA, "Dragon Whelp", 2, 3);
assertGraveyardCount(playerA, "Dragon Whelp", 0);
assertLife(playerA, 16); assertLife(playerA, 16);
assertLife(playerB, 15); assertLife(playerB, 15);
assertGraveyardCount(playerA, "Dragon Whelp", 0);
assertPermanentCount(playerA, "Dragon Whelp", 1);
assertPowerToughness(playerA, "Dragon Whelp", 2, 3);
} }
} }

View file

@ -0,0 +1,35 @@
/*
* 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.mana;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class HarvestMageTest extends CardTestPlayerBase {
@Test
public void testOneInstance() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
// {G}, {T}, Discard a card: Until end of turn, if you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount.
addCard(Zone.HAND, playerA, "Harvest Mage", 1); // Creature 1/1 {G}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Harvest Mage");
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{G}, {T}, Discard a card: Until end of turn");
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Harvest Mage", 1);
}
}

View file

@ -52,6 +52,7 @@ public class BlatantThieveryTest extends CardTestMultiPlayerBase {
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertGraveyardCount(playerA, "Blatant Thievery", 1);
assertPermanentCount(playerA, "Silvercoat Lion", 1); assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertPermanentCount(playerA, "Walking Corpse", 1); assertPermanentCount(playerA, "Walking Corpse", 1);
assertPermanentCount(playerA, "Pillarfield Ox", 1); assertPermanentCount(playerA, "Pillarfield Ox", 1);

View file

@ -479,16 +479,7 @@ public interface Ability extends Controllable, Serializable {
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game); boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game);
/** /**
* Sets the object that actually existed while a ability triggerd or an * Returns the object that actually existed while a ability triggered or an
* ability was activated.
*
* @param mageObject
* @param game
*/
void setSourceObject(MageObject mageObject, Game game);
/**
* Returns the object that actually existed while a ability triggerd or an
* ability was activated. If not set yet, the current object will be * ability was activated. If not set yet, the current object will be
* retrieved from the game. * retrieved from the game.
* *
@ -497,6 +488,8 @@ public interface Ability extends Controllable, Serializable {
*/ */
MageObject getSourceObject(Game game); MageObject getSourceObject(Game game);
void setSourceObjectZoneChangeCounter(int zoneChangeCounter);
int getSourceObjectZoneChangeCounter(); int getSourceObjectZoneChangeCounter();
/** /**
@ -520,6 +513,8 @@ public interface Ability extends Controllable, Serializable {
*/ */
Permanent getSourcePermanentIfItStillExists(Game game); Permanent getSourcePermanentIfItStillExists(Game game);
Permanent getSourcePermanentOrLKI(Game game);
String getTargetDescription(Targets targets, Game game); String getTargetDescription(Targets targets, Game game);
void setCanFizzle(boolean canFizzle); void setCanFizzle(boolean canFizzle);

View file

@ -5,7 +5,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference;
import mage.Mana; import mage.Mana;
import mage.abilities.costs.*; import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.PayLifeCost;
@ -67,7 +66,6 @@ public abstract class AbilityImpl implements Ability {
protected boolean costModificationActive = true; protected boolean costModificationActive = true;
protected boolean activated = false; protected boolean activated = false;
protected boolean worksFaceDown = false; protected boolean worksFaceDown = false;
protected MageObject sourceObject;
protected int sourceObjectZoneChangeCounter; protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>(); protected List<Watcher> watchers = new ArrayList<>();
protected List<Ability> subAbilities = null; protected List<Ability> subAbilities = null;
@ -116,7 +114,6 @@ public abstract class AbilityImpl implements Ability {
this.costModificationActive = ability.costModificationActive; this.costModificationActive = ability.costModificationActive;
this.worksFaceDown = ability.worksFaceDown; this.worksFaceDown = ability.worksFaceDown;
this.abilityWord = ability.abilityWord; this.abilityWord = ability.abilityWord;
this.sourceObject = null; // you may not copy this because otherwise simulation may modify real game object
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter; this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
this.canFizzle = ability.canFizzle; this.canFizzle = ability.canFizzle;
this.targetAdjuster = ability.targetAdjuster; this.targetAdjuster = ability.targetAdjuster;
@ -131,8 +128,6 @@ public abstract class AbilityImpl implements Ability {
public void newId() { public void newId() {
if (!(this instanceof MageSingleton)) { if (!(this instanceof MageSingleton)) {
this.id = UUID.randomUUID(); this.id = UUID.randomUUID();
// this.sourceObject = null;
// this.sourceObjectZoneChangeCounter = -1;
} }
getEffects().newId(); getEffects().newId();
} }
@ -226,8 +221,10 @@ public abstract class AbilityImpl implements Ability {
return false; return false;
} }
getSourceObject(game); MageObject sourceObject = getSourceObject(game);
if (getSourceObjectZoneChangeCounter() == 0) {
setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(getSourceId()));
}
if (controller.isTestMode()) { if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) { if (!controller.addTargets(this, game)) {
return false; return false;
@ -1160,58 +1157,44 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public MageObject getSourceObject(Game game) { public MageObject getSourceObject(Game game) {
if (sourceObject == null) { return game.getObject(getSourceId());
setSourceObject(null, game);
if (sourceObject == null) {
logger.warn("Source object could not be retrieved: " + this.getRule());
}
}
return sourceObject;
} }
@Override @Override
public MageObject getSourceObjectIfItStillExists(Game game) { public MageObject getSourceObjectIfItStillExists(Game game) {
MageObject currentObject = game.getObject(getSourceId()); if (getSourceObjectZoneChangeCounter() == 0
if (currentObject != null) { || getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(getSourceId())) {
if (sourceObject == null) { return game.getObject(getSourceId());
setSourceObject(currentObject, game);
}
MageObjectReference mor = new MageObjectReference(currentObject, game);
if (mor.getZoneChangeCounter() == getSourceObjectZoneChangeCounter()) {
// source object has meanwhile not changed zone
return currentObject;
}
} }
return null; return null;
} }
@Override @Override
public Permanent getSourcePermanentIfItStillExists(Game game) { public Permanent getSourcePermanentIfItStillExists(Game game) {
if (sourceObject == null || !sourceObject.getId().equals(getSourceId())) { MageObject mageObject = getSourceObjectIfItStillExists(game);
setSourceObject(game.getObject(getSourceId()), game); if (mageObject instanceof Permanent) {
} return (Permanent) mageObject;
if (sourceObject instanceof Permanent) {
if (game.getState().getZoneChangeCounter(getSourceId()) == getSourceObjectZoneChangeCounter()) {
return (Permanent) sourceObject;
}
} }
return null; return null;
} }
@Override @Override
public int getSourceObjectZoneChangeCounter() { public Permanent getSourcePermanentOrLKI(Game game) {
return sourceObjectZoneChangeCounter; if (getSourceObjectZoneChangeCounter() == 0
|| getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(getSourceId())) {
return game.getPermanent(getSourceId());
}
return (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getSourceObjectZoneChangeCounter());
} }
@Override @Override
public void setSourceObject(MageObject sourceObject, Game game) { public void setSourceObjectZoneChangeCounter(int sourceObjectZoneChangeCounter) {
if (sourceObject == null) { this.sourceObjectZoneChangeCounter = sourceObjectZoneChangeCounter;
this.sourceObject = game.getObject(sourceId); }
this.sourceObjectZoneChangeCounter = game.getState().getZoneChangeCounter(sourceId);
} else { @Override
this.sourceObject = sourceObject; public int getSourceObjectZoneChangeCounter() {
this.sourceObjectZoneChangeCounter = this.sourceObject.getZoneChangeCounter(game); return sourceObjectZoneChangeCounter;
}
} }
@Override @Override

View file

@ -1,4 +1,3 @@
package mage.abilities; package mage.abilities;
import java.util.Locale; import java.util.Locale;
@ -45,9 +44,6 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
@Override @Override
public void trigger(Game game, UUID controllerId) { public void trigger(Game game, UUID controllerId) {
//20091005 - 603.4 //20091005 - 603.4
if (!(this instanceof DelayedTriggeredAbility)) {
setSourceObject(null, game);
}
if (checkInterveningIfClause(game)) { if (checkInterveningIfClause(game)) {
game.addTriggeredAbility(this); game.addTriggeredAbility(this);
} }

View file

@ -1,4 +1,3 @@
package mage.abilities.common.delayed; package mage.abilities.common.delayed;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;

View file

@ -1,13 +1,12 @@
package mage.abilities.effects.common; package mage.abilities.effects.common;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
/** /**
* *
@ -34,11 +33,9 @@ public class SacrificeSourceEffect extends OneShotEffect {
MageObject sourceObject = source.getSourceObjectIfItStillExists(game); MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
if (sourceObject == null) { if (sourceObject == null) {
// Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent // Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent
if (source.getSourceObject(game) instanceof Spell) { if (game.getState().getZone(source.getSourceId()).equals(Zone.BATTLEFIELD)
&& source.getSourceObjectZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
sourceObject = game.getPermanent(source.getSourceId()); sourceObject = game.getPermanent(source.getSourceId());
if (sourceObject != null && sourceObject.getZoneChangeCounter(game) > source.getSourceObjectZoneChangeCounter() + 1) {
return false;
}
} }
} }
if (sourceObject instanceof Permanent) { if (sourceObject instanceof Permanent) {

View file

@ -1,6 +1,5 @@
package mage.abilities.meta; package mage.abilities.meta;
import mage.MageObject;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -14,9 +13,12 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
* A triggered ability that combines several others and triggers whenever one or more of them would. The abilities * A triggered ability that combines several others and triggers whenever one or
* passed in should have null as their effect, and should have their own targets set if necessary. All other information * more of them would. The abilities passed in should have null as their effect,
* will be passed in from changes to this Ability. Note: this does NOT work with abilities that have intervening if clauses. * and should have their own targets set if necessary. All other information
* will be passed in from changes to this Ability. Note: this does NOT work with
* abilities that have intervening if clauses.
*
* @author noahg * @author noahg
*/ */
public class OrTriggeredAbility extends TriggeredAbilityImpl { public class OrTriggeredAbility extends TriggeredAbilityImpl {
@ -43,18 +45,17 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
public OrTriggeredAbility(OrTriggeredAbility ability) { public OrTriggeredAbility(OrTriggeredAbility ability) {
super(ability); super(ability);
this.triggeredAbilities = new TriggeredAbility[ability.triggeredAbilities.length]; this.triggeredAbilities = new TriggeredAbility[ability.triggeredAbilities.length];
for (int i = 0; i < this.triggeredAbilities.length; i++){ for (int i = 0; i < this.triggeredAbilities.length; i++) {
this.triggeredAbilities[i] = ability.triggeredAbilities[i].copy(); this.triggeredAbilities[i] = ability.triggeredAbilities[i].copy();
} }
this.triggeringAbilities = new ArrayList<>(ability.triggeringAbilities); this.triggeringAbilities = new ArrayList<>(ability.triggeringAbilities);
this.ruleTrigger = ability.ruleTrigger; this.ruleTrigger = ability.ruleTrigger;
} }
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
for (TriggeredAbility ability : triggeredAbilities) { for (TriggeredAbility ability : triggeredAbilities) {
if (ability.checkEventType(event, game)){ if (ability.checkEventType(event, game)) {
System.out.println("Correct event type (" + event.getType() + ")"); System.out.println("Correct event type (" + event.getType() + ")");
return true; return true;
} }
@ -101,7 +102,6 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
return sb.toString() + super.getRule(); return sb.toString() + super.getRule();
} }
@Override @Override
public void setControllerId(UUID controllerId) { public void setControllerId(UUID controllerId) {
super.setControllerId(controllerId); super.setControllerId(controllerId);
@ -126,11 +126,4 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl {
} }
} }
@Override
public void setSourceObject(MageObject sourceObject, Game game) {
super.setSourceObject(sourceObject, game);
for (TriggeredAbility ability : triggeredAbilities) {
ability.setSourceObject(sourceObject, game);
}
}
} }

View file

@ -1527,7 +1527,7 @@ public abstract class GameImpl implements Game, Serializable {
@Override @Override
public void addEffect(ContinuousEffect continuousEffect, Ability source) { public void addEffect(ContinuousEffect continuousEffect, Ability source) {
Ability newAbility = source.copy(); Ability newAbility = source.copy();
newAbility.setSourceObject(null, this); // Update the source object to the currently existing Object newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
ContinuousEffect newEffect = continuousEffect.copy(); ContinuousEffect newEffect = continuousEffect.copy();
newEffect.newId(); newEffect.newId();
@ -1698,11 +1698,17 @@ public abstract class GameImpl implements Game, Serializable {
if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) { if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) {
// 20110715 - 605.4 // 20110715 - 605.4
Ability manaAbiltiy = ability.copy(); Ability manaAbiltiy = ability.copy();
if (manaAbiltiy.getSourceObjectZoneChangeCounter() == 0) {
manaAbiltiy.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
}
manaAbiltiy.activate(this, false); manaAbiltiy.activate(this, false);
manaAbiltiy.resolve(this); manaAbiltiy.resolve(this);
} else { } else {
TriggeredAbility newAbility = ability.copy(); TriggeredAbility newAbility = ability.copy();
newAbility.newId(); newAbility.newId();
if (newAbility.getSourceObjectZoneChangeCounter() == 0) {
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
}
state.addTriggeredAbility(newAbility); state.addTriggeredAbility(newAbility);
} }
} }
@ -1711,10 +1717,10 @@ public abstract class GameImpl implements Game, Serializable {
public UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility, Ability source) { public UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility, Ability source) {
delayedAbility.setSourceId(source.getSourceId()); delayedAbility.setSourceId(source.getSourceId());
delayedAbility.setControllerId(source.getControllerId()); delayedAbility.setControllerId(source.getControllerId());
delayedAbility.setSourceObject(source.getSourceObject(this), this);
// return addDelayedTriggeredAbility(delayedAbility); // return addDelayedTriggeredAbility(delayedAbility);
DelayedTriggeredAbility newAbility = delayedAbility.copy(); DelayedTriggeredAbility newAbility = delayedAbility.copy();
newAbility.newId(); newAbility.newId();
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
newAbility.initOnAdding(this); newAbility.initOnAdding(this);
// ability.init is called as the ability triggeres not now. // ability.init is called as the ability triggeres not now.
// If a FixedTarget pointer is already set from the effect setting up this delayed ability // If a FixedTarget pointer is already set from the effect setting up this delayed ability

View file

@ -1,4 +1,3 @@
package mage.game.command; package mage.game.command;
import java.util.EnumSet; import java.util.EnumSet;

View file

@ -1,7 +1,5 @@
package mage.game.command; package mage.game.command;
import static java.lang.Math.log;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;

View file

@ -518,14 +518,19 @@ public class StackAbility extends StackObjImpl implements Ability {
return this.ability.getSourcePermanentIfItStillExists(game); return this.ability.getSourcePermanentIfItStillExists(game);
} }
@Override
public void setSourceObjectZoneChangeCounter(int zoneChangeCounter) {
ability.setSourceObjectZoneChangeCounter(zoneChangeCounter);
}
@Override @Override
public int getSourceObjectZoneChangeCounter() { public int getSourceObjectZoneChangeCounter() {
return ability.getSourceObjectZoneChangeCounter(); return ability.getSourceObjectZoneChangeCounter();
} }
@Override @Override
public void setSourceObject(MageObject sourceObject, Game game) { public Permanent getSourcePermanentOrLKI(Game game) {
throw new UnsupportedOperationException("Not supported."); return ability.getSourcePermanentOrLKI(game);
} }
@Override @Override

View file

@ -1041,15 +1041,15 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
@Override @Override
public boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference permittingObject) { public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) {
if (game == null || ability == null) { if (game == null || originalAbility == null) {
return false; return false;
} }
// Use ability copy to avoid problems with targets and costs on recast (issue https://github.com/magefree/mage/issues/5189). // Use ability copy to avoid problems with targets and costs on recast (issue https://github.com/magefree/mage/issues/5189).
ability = ability.copy(); SpellAbility ability = originalAbility.copy();
ability.setControllerId(getId()); ability.setControllerId(getId());
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
if (ability.getSpellAbilityType() != SpellAbilityType.BASE) { if (ability.getSpellAbilityType() != SpellAbilityType.BASE) {
ability = chooseSpellAbilityForCast(ability, game, noMana); ability = chooseSpellAbilityForCast(ability, game, noMana);
if (ability == null) { if (ability == null) {
@ -1073,6 +1073,8 @@ public abstract class PlayerImpl implements Player, Serializable {
logger.error("Got no spell from stack. ability: " + ability.getRule()); logger.error("Got no spell from stack. ability: " + ability.getRule());
return false; return false;
} }
// Update the zcc to the stack
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
// some effects set sourceId to cast without paying mana costs or other costs // some effects set sourceId to cast without paying mana costs or other costs
if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) { if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) {
Ability spellAbility = spell.getSpellAbility(); Ability spellAbility = spell.getSpellAbility();