Fix copying fused spells; also fixes #9545 (#9546)

This commit is contained in:
Alex W. Jackson 2022-09-22 01:59:21 -04:00 committed by GitHub
parent fddec2ae9c
commit 3996127032
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 81 deletions

View file

@ -22,13 +22,12 @@ public final class ArmedDangerous extends SplitCard {
// Target creature gets +1/+1 and gains double strike until end of turn. // Target creature gets +1/+1 and gains double strike until end of turn.
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(1, 1, Duration.EndOfTurn)); getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(1, 1, Duration.EndOfTurn));
getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn)); getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn));
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("+1/+1 and double strike"));
// Dangerous // Dangerous
// All creatures able to block target creature this turn do so. // All creatures able to block target creature this turn do so.
getRightHalfCard().getSpellAbility().addEffect(new MustBeBlockedByAllTargetEffect(Duration.EndOfTurn)); getRightHalfCard().getSpellAbility().addEffect(new MustBeBlockedByAllTargetEffect(Duration.EndOfTurn));
getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("all creatures block"));
} }
private ArmedDangerous(final ArmedDangerous card) { private ArmedDangerous(final ArmedDangerous card) {

View file

@ -2,6 +2,7 @@ package mage.cards.f;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.cards.SplitCard; import mage.cards.SplitCard;
@ -14,7 +15,6 @@ import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetAnyTarget; import mage.target.common.TargetAnyTarget;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
@ -29,16 +29,16 @@ public final class FleshBlood extends SplitCard {
// Flesh // Flesh
// Exile target creature card from a graveyard. Put X +1/+1 counters on target creature, where X is the power of the card you exiled. // Exile target creature card from a graveyard. Put X +1/+1 counters on target creature, where X is the power of the card you exiled.
Target target = new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE);
getLeftHalfCard().getSpellAbility().addTarget(target);
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
getLeftHalfCard().getSpellAbility().addEffect(new FleshEffect()); getLeftHalfCard().getSpellAbility().addEffect(new FleshEffect());
getLeftHalfCard().getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE).withChooseHint("to exile"));
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("to put X +1/+1 counters on"));
// Blood // Blood
// Target creature you control deals damage equal to its power to any target. // Target creature you control deals damage equal to its power to any target.
getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); getRightHalfCard().getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());
getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget()); getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent().withChooseHint("to deal damage equal to its power"));
getRightHalfCard().getSpellAbility().addEffect(new BloodEffect()); getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget().withChooseHint("to deal damage to"));
} }
private FleshBlood(final FleshBlood card) { private FleshBlood(final FleshBlood card) {
@ -86,41 +86,3 @@ class FleshEffect extends OneShotEffect {
} }
} }
class BloodEffect extends OneShotEffect {
public BloodEffect() {
super(Outcome.Damage);
staticText = "Target creature you control deals damage equal to its power to any target";
}
public BloodEffect(final BloodEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanent(source.getFirstTarget());
if (sourcePermanent == null) {
sourcePermanent = (Permanent) game.getLastKnownInformation(source.getFirstTarget(), Zone.BATTLEFIELD);
}
Permanent targetCreature = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (sourcePermanent != null && targetCreature != null) {
targetCreature.damage(sourcePermanent.getPower().getValue(), sourcePermanent.getId(), source, game, false, true);
return true;
}
Player targetPlayer = game.getPlayer(source.getTargets().get(1).getFirstTarget());
if (sourcePermanent != null && targetPlayer != null) {
targetPlayer.damage(sourcePermanent.getPower().getValue(), sourcePermanent.getId(), source, game);
return true;
}
return false;
}
@Override
public BloodEffect copy() {
return new BloodEffect(this);
}
}

View file

@ -29,12 +29,12 @@ public final class GiveTake extends SplitCard {
// Give // Give
// Put three +1/+1 counters on target creature. // Put three +1/+1 counters on target creature.
getLeftHalfCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(3))); getLeftHalfCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(3)));
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("to put counters on"));
// Take // Take
// Remove all +1/+1 counters from target creature you control. Draw that many cards. // Remove all +1/+1 counters from target creature you control. Draw that many cards.
getRightHalfCard().getSpellAbility().addEffect(new TakeEffect()); getRightHalfCard().getSpellAbility().addEffect(new TakeEffect());
getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent().withChooseHint("to remove counters from"));
} }
private GiveTake(final GiveTake card) { private GiveTake(final GiveTake card) {

View file

@ -18,13 +18,12 @@ public final class ProtectServe extends SplitCard {
// Protect // Protect
// Target creature gets +2/+4 until end of turn. // Target creature gets +2/+4 until end of turn.
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(2, 4, Duration.EndOfTurn)); getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(2, 4, Duration.EndOfTurn));
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("gets +2/+4"));
// Serve // Serve
// Target creature gets -6/-0 until end of turn. // Target creature gets -6/-0 until end of turn.
getRightHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(-6, 0, Duration.EndOfTurn)); getRightHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(-6, 0, Duration.EndOfTurn));
getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("gets -6/-0"));
} }
private ProtectServe(final ProtectServe card) { private ProtectServe(final ProtectServe card) {

View file

@ -20,7 +20,7 @@ public final class ToilTrouble extends SplitCard {
// Toil // Toil
// Target player draws two cards and loses 2 life. // Target player draws two cards and loses 2 life.
getLeftHalfCard().getSpellAbility().addTarget(new TargetPlayer()); getLeftHalfCard().getSpellAbility().addTarget(new TargetPlayer().withChooseHint("to draw two cards and lose 2 life"));
getLeftHalfCard().getSpellAbility().addEffect(new DrawCardTargetEffect(2)); getLeftHalfCard().getSpellAbility().addEffect(new DrawCardTargetEffect(2));
getLeftHalfCard().getSpellAbility().addEffect(new LoseLifeTargetEffect(2)); getLeftHalfCard().getSpellAbility().addEffect(new LoseLifeTargetEffect(2));
@ -29,7 +29,7 @@ public final class ToilTrouble extends SplitCard {
Effect effect = new DamageTargetEffect(CardsInTargetHandCount.instance); Effect effect = new DamageTargetEffect(CardsInTargetHandCount.instance);
effect.setText("Trouble deals damage to target player equal to the number of cards in that player's hand"); effect.setText("Trouble deals damage to target player equal to the number of cards in that player's hand");
getRightHalfCard().getSpellAbility().addEffect(effect); getRightHalfCard().getSpellAbility().addEffect(effect);
getRightHalfCard().getSpellAbility().addTarget(new TargetPlayer()); getRightHalfCard().getSpellAbility().addTarget(new TargetPlayer().withChooseHint("to deal damage to"));
} }

View file

@ -30,14 +30,14 @@ public final class TurnBurn extends SplitCard {
Effect effect = new BecomesCreatureTargetEffect(new WeirdToken(), true, false, Duration.EndOfTurn); Effect effect = new BecomesCreatureTargetEffect(new WeirdToken(), true, false, Duration.EndOfTurn);
effect.setText("Until end of turn, target creature loses all abilities and becomes a red Weird with base power and toughness 0/1"); effect.setText("Until end of turn, target creature loses all abilities and becomes a red Weird with base power and toughness 0/1");
getLeftHalfCard().getSpellAbility().addEffect(effect); getLeftHalfCard().getSpellAbility().addEffect(effect);
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("becomes a Weird"));
// Burn // Burn
// Burn deals 2 damage to any target. // Burn deals 2 damage to any target.
effect = new DamageTargetEffect(2); effect = new DamageTargetEffect(2);
effect.setText("Burn deals 2 damage to any target"); effect.setText("Burn deals 2 damage to any target");
getRightHalfCard().getSpellAbility().addEffect(effect); getRightHalfCard().getSpellAbility().addEffect(effect);
getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget()); getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget().withChooseHint("2 damage"));
} }

View file

@ -94,4 +94,37 @@ public class CastSplitCardsWithFuseTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Juggernaut", 1); assertGraveyardCount(playerB, "Juggernaut", 1);
assertGraveyardCount(playerB, "Absolute Grace", 1); assertGraveyardCount(playerB, "Absolute Grace", 1);
} }
@Test
public void testProtectionFromFusedSpell() {
// https://github.com/magefree/mage/issues/9545
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 5);
// INSTANT
// Turn {2}{U}
// Until end of turn, target creature loses all abilities and becomes a red Weird with base power and toughness 0/1.
// Burn {1}{R}
// Burn deals 2 damage to any target.
// Fuse (You may cast one or both halves of this card from your hand.)
addCard(Zone.HAND, playerA, "Turn // Burn");
// {R}: Keeper of Kookus gains protection from red until end of turn.
addCard(Zone.BATTLEFIELD, playerB, "Keeper of Kookus");
addCard(Zone.BATTLEFIELD, playerB, "Suq'Ata Lancer");
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Turn // Burn");
addTarget(playerA, "Keeper of Kookus");
addTarget(playerA, "Suq'Ata Lancer");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{R}: ");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Turn // Burn", 1);
assertGraveyardCount(playerB, "Suq'Ata Lancer", 1);
assertPermanentCount(playerB, "Keeper of Kookus", 1);
assertPowerToughness(playerB, "Keeper of Kookus", 1, 1);
}
} }

View file

@ -16,7 +16,7 @@ public class DamageWithPowerFromOneToAnotherTargetEffect extends OneShotEffect {
String firstTargetName; String firstTargetName;
public DamageWithPowerFromOneToAnotherTargetEffect() { public DamageWithPowerFromOneToAnotherTargetEffect() {
this((String) null); this("");
} }
public DamageWithPowerFromOneToAnotherTargetEffect(String firstTargetName) { public DamageWithPowerFromOneToAnotherTargetEffect(String firstTargetName) {
@ -64,10 +64,6 @@ public class DamageWithPowerFromOneToAnotherTargetEffect extends OneShotEffect {
throw new IllegalStateException("It must have two targets, but found " + mode.getTargets().size()); throw new IllegalStateException("It must have two targets, but found " + mode.getTargets().size());
} }
String targetName = mode.getTargets().get(1).getTargetName(); return (firstTargetName.isEmpty() ? mode.getTargets().get(0).getDescription() : firstTargetName) + " deals damage equal to its power to " + mode.getTargets().get(1).getDescription();
// Target creature you control deals damage equal to its power to target creature you don't control
String sb = (this.firstTargetName != null ? this.firstTargetName : "Target " + mode.getTargets().get(0).getTargetName()) +
" deals damage equal to its power to " + (targetName.contains("other") ? "" : "target ") + targetName;
return sb;
} }
} }

View file

@ -44,7 +44,6 @@ public class Spell extends StackObjectImpl implements Card {
private static final Logger logger = Logger.getLogger(Spell.class); private static final Logger logger = Logger.getLogger(Spell.class);
private final List<SpellAbility> spellAbilities = new ArrayList<>(); private final List<SpellAbility> spellAbilities = new ArrayList<>();
private final List<Card> spellCards = new ArrayList<>();
private final Card card; private final Card card;
private final ObjectColor color; private final ObjectColor color;
@ -67,6 +66,10 @@ public class Spell extends StackObjectImpl implements Card {
private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE; private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE;
public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone, Game game) { public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone, Game game) {
this(card, ability, controllerId, fromZone, game, false);
}
private Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone, Game game, boolean isCopy) {
Card affectedCard = card; Card affectedCard = card;
// TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides) // TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides)
@ -85,12 +88,16 @@ public class Spell extends StackObjectImpl implements Card {
this.ability = ability; this.ability = ability;
this.ability.setControllerId(controllerId); this.ability.setControllerId(controllerId);
if (ability.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) { if (ability.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
spellCards.add(((SplitCard) affectedCard).getLeftHalfCard()); // if this spell is going to be a copy, these abilities will be copied in copySpell
spellAbilities.add(((SplitCard) affectedCard).getLeftHalfCard().getSpellAbility().copy()); if (!isCopy) {
spellCards.add(((SplitCard) affectedCard).getRightHalfCard()); SpellAbility left = ((SplitCard) affectedCard).getLeftHalfCard().getSpellAbility().copy();
spellAbilities.add(((SplitCard) affectedCard).getRightHalfCard().getSpellAbility().copy()); SpellAbility right = ((SplitCard) affectedCard).getRightHalfCard().getSpellAbility().copy();
left.setSourceId(ability.getSourceId());
right.setSourceId(ability.getSourceId());
spellAbilities.add(left);
spellAbilities.add(right);
}
} else { } else {
spellCards.add(affectedCard);
spellAbilities.add(ability); spellAbilities.add(ability);
} }
this.controllerId = controllerId; this.controllerId = controllerId;
@ -104,19 +111,12 @@ public class Spell extends StackObjectImpl implements Card {
for (SpellAbility spellAbility : spell.spellAbilities) { for (SpellAbility spellAbility : spell.spellAbilities) {
this.spellAbilities.add(spellAbility.copy()); this.spellAbilities.add(spellAbility.copy());
} }
for (Card spellCard : spell.spellCards) {
this.spellCards.add(spellCard.copy());
}
if (spell.spellAbilities.get(0).equals(spell.ability)) { if (spell.spellAbilities.get(0).equals(spell.ability)) {
this.ability = this.spellAbilities.get(0); this.ability = this.spellAbilities.get(0);
} else { } else {
this.ability = spell.ability.copy(); this.ability = spell.ability.copy();
} }
if (spell.spellCards.get(0).equals(spell.card)) {
this.card = spellCards.get(0);
} else {
this.card = spell.card.copy(); this.card = spell.card.copy();
}
this.fromZone = spell.fromZone; this.fromZone = spell.fromZone;
this.color = spell.color.copy(); this.color = spell.color.copy();
@ -807,15 +807,17 @@ public class Spell extends StackObjectImpl implements Card {
Card copiedPart = (Card) mapOldToNew.get(this.card.getId()); Card copiedPart = (Card) mapOldToNew.get(this.card.getId());
// copy spell // copy spell
Spell spellCopy = new Spell(copiedPart, this.ability.copySpell(this.card, copiedPart), this.controllerId, this.fromZone, game); Spell spellCopy = new Spell(copiedPart, this.ability.copySpell(this.card, copiedPart), this.controllerId, this.fromZone, game, true);
boolean firstDone = false; UUID copiedSourceId = spellCopy.ability.getSourceId();
boolean skipFirst = (this.ability.getSpellAbilityType() != SpellAbilityType.SPLIT_FUSED);
for (SpellAbility spellAbility : this.getSpellAbilities()) { for (SpellAbility spellAbility : this.getSpellAbilities()) {
if (!firstDone) { if (skipFirst) {
firstDone = true; skipFirst = false;
continue; continue;
} }
SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell
newAbility.newId(); newAbility.newId();
newAbility.setSourceId(copiedSourceId);
spellCopy.addSpellAbility(newAbility); spellCopy.addSpellAbility(newAbility);
} }
spellCopy.setCopy(true, this); spellCopy.setCopy(true, this);