* Transform abilities - fixed duplicated triggered abilities from transformed permanents;

* Accursed Witch // Infectious Curse - fixed wrong cost modification effect (#6684);
This commit is contained in:
Oleg Agafonov 2020-07-30 12:13:32 +04:00
parent 1045f352bc
commit 0824d2901a
6 changed files with 102 additions and 36 deletions

View file

@ -1,9 +1,6 @@
package mage.cards.i;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
@ -18,20 +15,23 @@ import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPlayer;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
*
* @author halljared
*/
public final class InfectiousCurse extends CardImpl {
public InfectiousCurse(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"");
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "");
this.subtype.add(SubType.AURA, SubType.CURSE);
this.color.setBlack(true);
@ -46,6 +46,7 @@ public final class InfectiousCurse extends CardImpl {
// Spells you cast that target enchanted player cost {1} less to cast.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfectiousCurseCostReductionEffect()));
// At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life.
InfectiousCurseAbility curseAbility = new InfectiousCurseAbility();
curseAbility.addEffect(new GainLifeEffect(1));
@ -121,23 +122,31 @@ class InfectiousCurseCostReductionEffect extends CostModificationEffectImpl {
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (abilityToModify instanceof SpellAbility) {
if (source.isControlledBy(abilityToModify.getControllerId())) {
for (UUID modeId : abilityToModify.getModes().getSelectedModes()) {
Mode mode = abilityToModify.getModes().get(modeId);
for (Target target : mode.getTargets()) {
for (UUID targetUUID : target.getTargets()) {
Permanent enchantment = game.getPermanent(source.getSourceId());
UUID attachedTo = enchantment.getAttachedTo();
if (targetUUID.equals(attachedTo)) {
return true;
}
}
}
}
}
if (!(abilityToModify instanceof SpellAbility)) {
return false;
}
return false;
if (!source.isControlledBy(abilityToModify.getControllerId())) {
return false;
}
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment == null || enchantment.getAttachedTo() == null) {
return false;
}
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
Set<UUID> allTargets;
if (spell != null) {
// real cast
allTargets = CardUtil.getAllSelectedTargets(abilityToModify, game);
} else {
// playable
allTargets = CardUtil.getAllPossibleTargets(abilityToModify, game);
}
// try to reduce all the time (if it possible to target that player)
return allTargets.stream().anyMatch(target -> Objects.equals(target, enchantment.getAttachedTo()));
}
@Override

View file

@ -1,9 +1,8 @@
package org.mage.test.cards.copy;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -14,7 +13,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* addition to its other types. It gains haste. Sacrifice it at the beginning of
* the next end step.
*
*
* @author LevelX2
*/
public class FeldonOfTheThirdPathTest extends CardTestPlayerBase {
@ -22,27 +20,41 @@ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase {
/**
* Checking that enters the battlefield abilities of the copied creature
* card works.
*
*/
@Test
public void testETBEffect() {
// When Highway Robber enters the battlefield, target opponent loses 2 life and you gain 2 life.
addCard(Zone.GRAVEYARD, playerA, "Highway Robber", 1);
// {2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact
// in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.
addCard(Zone.BATTLEFIELD, playerA, "Feldon of the Third Path", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
//
// When Highway Robber enters the battlefield, target opponent loses 2 life and you gain 2 life.
addCard(Zone.GRAVEYARD, playerA, "Highway Robber", 1);
//
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA,
"{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.",
"Highway Robber");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Create a token", "Highway Robber");
addTarget(playerA, playerB); // opponent to loses 2 life
waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("must have token", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Highway Robber", 1);
checkPermanentCount("must have card", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Feldon of the Third Path", 1);
// destroy token
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Highway Robber");
waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("must haven't token", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Highway Robber", 0);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Highway Robber", 1);
assertPermanentCount(playerA, "Feldon of the Third Path", 1);
assertAllCommandsUsed();
assertLife(playerA, 22); // +2 from Robber
assertLife(playerB, 18); // -2 from Robber
// possible bug: triggers from destroyed permanents keeps in game state (e.g. 2 triggers in game state)
Assert.assertEquals("game state must have only 1 trigger from original card", 1, currentGame.getState().getTriggers().size());
}
@Test
@ -59,8 +71,11 @@ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase {
"{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.",
"Sepulchral Primordial");
addTarget(playerA, "Silvercoat Lion"); // target for ETB Sepulchral Primordial
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Feldon of the Third Path", 1);
assertPermanentCount(playerA, "Sepulchral Primordial", 1);

View file

@ -343,4 +343,43 @@ public class CostModificationTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Engulfing Flames", 1);
}
@Test
public void test_ThatTargetEnchantedPlayer_InfectiousCurse() {
// When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent.
//
// transformed: Infectious Curse
// Enchant player
// Spells you cast that target enchanted player cost {1} less to cast.
addCard(Zone.BATTLEFIELD, playerA, "Accursed Witch", 1); // 4/2
//
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//
// {1}{R} SORCERY
// Grapeshot deals 1 damage to any target.
addCard(Zone.HAND, playerA, "Grapeshot");
checkPlayableAbility("no mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false);
// transform
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Accursed Witch");
addTarget(playerA, playerB); // attach curse to
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("transformed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Infectious Curse", 1);
checkPlayableAbility("reduced, but no mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false);
// cast
// possible bug: transform command can generate duplicated triggers (player get choose trigger dialog but must not)
checkPlayableAbility("reduced, with mana", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", true);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grapeshot", playerB);
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN);
checkLife("a", 3, PhaseStep.PRECOMBAT_MAIN, playerA, 20 + 1); // +1 from curse on turn 2
checkLife("b", 3, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 1 - 1); // -1 from grapeshot, -1 from curse on turn 2
setStrictChooseMode(true);
setStopAt(6, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
}

View file

@ -30,7 +30,7 @@ public class ApplyCountersEffect extends ContinuousEffectImpl {
if (layer == Layer.AbilityAddingRemovingEffects_6) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) {
for (AbilityCounter counter : permanent.getCounters(game).getAbilityCounters()) {
permanent.addAbility(counter.getAbility(), source == null ? null : source.getSourceId(), game);
permanent.addAbility(counter.getAbility(), source == null ? permanent.getId() : source.getSourceId(), game);
}
}
}

View file

@ -63,7 +63,9 @@ public class TransformAbility extends SimpleStaticAbility {
permanent.setExpansionSetCode(sourceCard.getExpansionSetCode());
permanent.getAbilities().clear();
for (Ability ability : sourceCard.getAbilities()) {
permanent.addAbility(ability, source == null ? null : source.getSourceId(), game);
// source == null -- call from init card (e.g. own abilities)
// source != null -- from apply effect
permanent.addAbility(ability, source == null ? permanent.getId() : source.getSourceId(), game);
}
permanent.getPower().modifyBaseValue(sourceCard.getPower().getValue());
permanent.getToughness().modifyBaseValue(sourceCard.getToughness().getValue());

View file

@ -58,6 +58,7 @@ public class PermanentToken extends PermanentImpl {
this.abilities.addAll(token.getAbilities());
} else {
// first time -> create ContinuousEffects only once
// so sourceId must be null (keep triggered abilities forever?)
for (Ability ability : token.getAbilities()) {
this.addAbility(ability, null, game);
}