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.
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(1, 1, 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
// All creatures able to block target creature this turn do so.
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) {

View file

@ -2,6 +2,7 @@ package mage.cards.f;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
import mage.cards.Card;
import mage.cards.CardSetInfo;
import mage.cards.SplitCard;
@ -14,7 +15,6 @@ import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetAnyTarget;
import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetControlledCreaturePermanent;
@ -29,16 +29,16 @@ public final class FleshBlood extends SplitCard {
// 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.
Target target = new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE);
getLeftHalfCard().getSpellAbility().addTarget(target);
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
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
// Target creature you control deals damage equal to its power to any target.
getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget());
getRightHalfCard().getSpellAbility().addEffect(new BloodEffect());
getRightHalfCard().getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());
getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent().withChooseHint("to deal damage equal to its power"));
getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget().withChooseHint("to deal damage to"));
}
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
// Put three +1/+1 counters on target creature.
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
// Remove all +1/+1 counters from target creature you control. Draw that many cards.
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) {

View file

@ -18,13 +18,12 @@ public final class ProtectServe extends SplitCard {
// Protect
// Target creature gets +2/+4 until end of turn.
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(2, 4, Duration.EndOfTurn));
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("gets +2/+4"));
// Serve
// Target creature gets -6/-0 until end of turn.
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) {

View file

@ -20,7 +20,7 @@ public final class ToilTrouble extends SplitCard {
// Toil
// 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 LoseLifeTargetEffect(2));
@ -29,7 +29,7 @@ public final class ToilTrouble extends SplitCard {
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");
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.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().addTarget(new TargetCreaturePermanent());
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent().withChooseHint("becomes a Weird"));
// Burn
// Burn deals 2 damage to any target.
effect = new DamageTargetEffect(2);
effect.setText("Burn deals 2 damage to any target");
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, "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;
public DamageWithPowerFromOneToAnotherTargetEffect() {
this((String) null);
this("");
}
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());
}
String targetName = mode.getTargets().get(1).getTargetName();
// 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;
return (firstTargetName.isEmpty() ? mode.getTargets().get(0).getDescription() : firstTargetName) + " deals damage equal to its power to " + mode.getTargets().get(1).getDescription();
}
}

View file

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