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
@ -43,7 +42,7 @@ public final class GiantOyster extends CardImpl {
public GiantOyster(UUID ownerId, CardSetInfo setInfo) { public GiantOyster(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}");
this.subtype.add(SubType.OYSTER); this.subtype.add(SubType.OYSTER);
this.power = new MageInt(0); this.power = new MageInt(0);
this.toughness = new MageInt(3); this.toughness = new MageInt(3);
@ -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);
@ -161,4 +159,4 @@ class GiantOysterLeaveUntapDelayedTriggeredAbility extends DelayedTriggeredAbili
public String getRule() { public String getRule() {
return "When {this} leaves the battlefield or becomes untapped, remove all -1/-1 counters from the creature."; return "When {this} leaves the battlefield or becomes untapped, remove all -1/-1 counters from the creature.";
} }
} }

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,179 +1,180 @@
package org.mage.test.cards.abilities.other;
package org.mage.test.cards.abilities.other;
import mage.constants.PhaseStep;
import mage.constants.PhaseStep; import mage.constants.Zone;
import mage.constants.Zone; import org.junit.Test;
import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
/** *
* * @author LevelX2
* @author LevelX2 */
*/ public class LimitedCountedActivationsTest extends CardTestPlayerBase {
public class LimitedCountedActivationsTest extends CardTestPlayerBase {
/**
/** * Tests usage of ActivationInfo class
* Tests usage of ActivationInfo class */
*/ @Test
@Test public void testDragonWhelpActivatedThreeTimes() {
public void testDragonWhelpActivatedThreeTimes() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); // Flying
// Flying // {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step.
// {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
attack(1, playerA, "Dragon Whelp");
attack(1, playerA, "Dragon Whelp");
setStopAt(2, PhaseStep.UPKEEP);
setStopAt(2, PhaseStep.UPKEEP); execute();
execute();
assertPermanentCount(playerA, "Dragon Whelp", 1);
assertPermanentCount(playerA, "Dragon Whelp", 1);
assertLife(playerA, 20);
assertLife(playerA, 20); assertLife(playerB, 15);
assertLife(playerB, 15); }
}
@Test
@Test public void testDragonWhelpActivatedFourTimes() {
public void testDragonWhelpActivatedFourTimes() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); // Flying
// Flying // {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step.
// {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
attack(1, playerA, "Dragon Whelp");
attack(1, playerA, "Dragon Whelp");
setStopAt(2, PhaseStep.UPKEEP);
setStopAt(2, PhaseStep.UPKEEP); execute();
execute();
assertPermanentCount(playerA, "Dragon Whelp", 0);
assertPermanentCount(playerA, "Dragon Whelp", 0); assertGraveyardCount(playerA, "Dragon Whelp", 1);
assertGraveyardCount(playerA, "Dragon Whelp", 1);
assertLife(playerA, 20);
assertLife(playerA, 20); assertLife(playerB, 14);
assertLife(playerB, 14); }
}
@Test
@Test public void testDragonWhelpActivatedFiveTimes() {
public void testDragonWhelpActivatedFiveTimes() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); // Flying
// Flying // {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step.
// {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
attack(1, playerA, "Dragon Whelp");
attack(1, playerA, "Dragon Whelp");
setStopAt(2, PhaseStep.UPKEEP);
setStopAt(2, PhaseStep.UPKEEP); execute();
execute();
assertPermanentCount(playerA, "Dragon Whelp", 0);
assertPermanentCount(playerA, "Dragon Whelp", 0); assertGraveyardCount(playerA, "Dragon Whelp", 1);
assertGraveyardCount(playerA, "Dragon Whelp", 1);
assertLife(playerA, 20);
assertLife(playerA, 20); assertLife(playerB, 13);
assertLife(playerB, 13); }
}
@Test
@Test public void testDragonWhelpTwoObjects() {
public void testDragonWhelpTwoObjects() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); // Flying
// Flying // {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step.
// {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3 // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost.
// Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.HAND, playerA, "Reanimate", 1);
addCard(Zone.HAND, playerA, "Reanimate", 1); // Target creature gains haste until end of turn.
// Target creature gains haste until end of turn. addCard(Zone.HAND, playerA, "Unnatural Speed", 1);
addCard(Zone.HAND, playerA, "Unnatural Speed", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); // Destroy target nonartifact, nonblack creature. It can't be regenerated.
// Destroy target nonartifact, nonblack creature. It can't be regenerated. addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B}
addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B}
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Dragon Whelp");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Dragon Whelp"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dragon Whelp");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dragon Whelp"); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Unnatural Speed", "Dragon Whelp");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Unnatural Speed", "Dragon Whelp");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: "); activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: "); activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
attack(1, playerA, "Dragon Whelp");
attack(1, playerA, "Dragon Whelp");
setStopAt(2, PhaseStep.UPKEEP);
setStopAt(2, PhaseStep.UPKEEP); execute();
execute();
assertGraveyardCount(playerA, "Unnatural Speed", 1);
assertGraveyardCount(playerA, "Unnatural Speed", 1); assertGraveyardCount(playerA, "Reanimate", 1);
assertGraveyardCount(playerA, "Reanimate", 1);
assertGraveyardCount(playerB, "Terror", 1);
assertGraveyardCount(playerB, "Terror", 1);
assertPermanentCount(playerA, "Dragon Whelp", 1);
assertPermanentCount(playerA, "Dragon Whelp", 1); assertPowerToughness(playerA, "Dragon Whelp", 2, 3);
assertPowerToughness(playerA, "Dragon Whelp", 2, 3); assertGraveyardCount(playerA, "Dragon Whelp", 0);
assertGraveyardCount(playerA, "Dragon Whelp", 0);
assertLife(playerA, 16);
assertLife(playerA, 16); assertLife(playerB, 15);
assertLife(playerB, 15); }
}
@Test
@Test public void testDragonWhelpDontSacrificeNewObject() {
public void testDragonWhelpDontSacrificeNewObject() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); // Flying
// Flying // {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step.
// {R}: Dragon Whelp gets +1/+0 until end of turn. If this ability has been activated four or more times this turn, sacrifice Dragon Whelp at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Dragon Whelp", 1); // 3/3 // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost.
// Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.HAND, playerA, "Reanimate", 1);
addCard(Zone.HAND, playerA, "Reanimate", 1); // Target creature gains haste until end of turn.
// Target creature gains haste until end of turn. addCard(Zone.HAND, playerA, "Unnatural Speed", 1);
addCard(Zone.HAND, playerA, "Unnatural Speed", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); // Destroy target nonartifact, nonblack creature. It can't be regenerated.
// Destroy target nonartifact, nonblack creature. It can't be regenerated. addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B}
addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B}
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: "); activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: "); activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: "); activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
activateAbility(1, PhaseStep.UPKEEP, playerA, "{R}: ");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Dragon Whelp");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Dragon Whelp"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dragon Whelp");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dragon Whelp"); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Unnatural Speed", "Dragon Whelp");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Unnatural Speed", "Dragon Whelp");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: "); activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: "); activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "{R}: ");
attack(1, playerA, "Dragon Whelp");
attack(1, playerA, "Dragon Whelp");
setStopAt(2, PhaseStep.UPKEEP);
setStopAt(2, PhaseStep.UPKEEP); execute();
execute();
assertGraveyardCount(playerA, "Unnatural Speed", 1);
assertGraveyardCount(playerA, "Unnatural Speed", 1); assertGraveyardCount(playerA, "Reanimate", 1);
assertGraveyardCount(playerA, "Reanimate", 1);
assertGraveyardCount(playerB, "Terror", 1);
assertGraveyardCount(playerB, "Terror", 1);
assertLife(playerA, 16);
assertPermanentCount(playerA, "Dragon Whelp", 1); assertLife(playerB, 15);
assertPowerToughness(playerA, "Dragon Whelp", 2, 3);
assertGraveyardCount(playerA, "Dragon Whelp", 0); assertGraveyardCount(playerA, "Dragon Whelp", 0);
assertLife(playerA, 16); assertPermanentCount(playerA, "Dragon Whelp", 1);
assertLife(playerB, 15); 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;
@ -289,7 +287,7 @@ public class Plane implements CommandObject {
if (plane instanceof Plane) { if (plane instanceof Plane) {
return (Plane) plane; return (Plane) plane;
} }
} catch (Exception ex) { } catch (Exception ex) {
} }
return null; return null;
} }

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();