diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java new file mode 100644 index 00000000000..e9d2b3d5b1d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithSpliceTest.java @@ -0,0 +1,99 @@ +package org.mage.test.cards.split; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class CastSplitCardsWithSpliceTest extends CardTestPlayerBase { + + // splice cost applies for one fused spell, not to every part + // https://github.com/magefree/mage/issues/6493 + + @Test + public void test_ThaliaGuardianOfThraben_CostModification_Fused() { + removeAllCardsFromHand(playerA); + + // Noncreature spells cost {1} more to cast. + addCard(Zone.BATTLEFIELD, playerA, "Thalia, Guardian of Thraben", 1); + // + // Wear {1}{R} Destroy target artifact. + // Tear {W} Destroy target enchantment. + addCard(Zone.HAND, playerA, "Wear // Tear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerB, "Bident of Thassa", 1); // Legendary Enchantment Artifact + addCard(Zone.BATTLEFIELD, playerB, "Bow of Nylea", 1); // Legendary Enchantment Artifact + + // cast fused + // old bug: https://github.com/magefree/mage/issues/6493 + // * getPlayable checks fused cost (+1 modification) + // * activate checks fused cost (+1 modification) andeach card's part (+1 modification) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Wear // Tear"); + addTarget(playerA, "Bident of Thassa"); + addTarget(playerA, "Bow of Nylea"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Wear // Tear", 1); + assertGraveyardCount(playerB, "Bident of Thassa", 1); + assertGraveyardCount(playerB, "Bow of Nylea", 1); + assertHandCount(playerA, 0); + + // must used all mana + assertTappedCount("Mountain", true, 3); + assertTappedCount("Plains", true, 1); + } + + @Test + public void test_ThaliaGuardianOfThraben_CostModification_FusedWithSplice() { + removeAllCardsFromHand(playerA); + + // Noncreature spells cost {1} more to cast. + addCard(Zone.BATTLEFIELD, playerA, "Thalia, Guardian of Thraben", 1); + // + // Draw a card. + // Splice onto instant or sorcery {2}{U} + addCard(Zone.HAND, playerA, "Everdream", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); // for splice + // + // Wear {1}{R} Destroy target artifact. + // Tear {W} Destroy target enchantment. + addCard(Zone.HAND, playerA, "Wear // Tear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerB, "Bident of Thassa", 1); // Legendary Enchantment Artifact + addCard(Zone.BATTLEFIELD, playerB, "Bow of Nylea", 1); // Legendary Enchantment Artifact + + // cast fused with splice + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Wear // Tear"); + setChoice(playerA, "Yes"); // use splice + addTarget(playerA, "Everdream"); // card to splice + addTarget(playerA, "Bident of Thassa"); // target left + addTarget(playerA, "Bow of Nylea"); // target right + + // must used all mana + //showAvaileableMana("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Wear // Tear", 1); + assertGraveyardCount(playerB, "Bident of Thassa", 1); + assertGraveyardCount(playerB, "Bow of Nylea", 1); + assertHandCount(playerA, 2); // splice card + draw effect from spliced + + // must used all mana + assertTappedCount("Island", true, 3); + assertTappedCount("Mountain", true, 3); + assertTappedCount("Plains", true, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 0cff76fc8d5..a4627fe0f8d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1077,6 +1077,7 @@ public class TestPlayer implements Player { List data = abilities.stream() .map(a -> (a.getZone() + " -> " + a.getSourceObject(game).getIdName() + " -> " + + (a.toString().startsWith("Cast ") ? "[" + a.getManaCostsToPay().getText() + "] -> " : "") // printed cost, not modified + (a.toString().length() > 0 ? a.toString().substring(0, Math.min(20, a.toString().length())) : a.getClass().getSimpleName()) diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index f09b94f6164..62a2d3fea1f 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -24,6 +24,7 @@ import mage.players.Player; import mage.target.Target; import mage.target.Targets; import mage.target.targetadjustment.TargetAdjuster; +import mage.util.CardUtil; import mage.util.GameLog; import mage.util.ThreadLocalStringBuilder; import mage.watchers.Watcher; @@ -352,8 +353,15 @@ public abstract class AbilityImpl implements Ability { return false; } + // fused spell contains 3 abilities (fused, left, right) + // fused cost added to fused ability, so no need cost modification for other parts + boolean needCostModification = true; + if (CardUtil.isFusedPartAbility(this, game)) { + needCostModification = false; + } + //20101001 - 601.2e - if (sourceObject != null) { + if (needCostModification && sourceObject != null) { sourceObject.adjustCosts(this, game); // still needed game.getContinuousEffects().costModification(this, game); } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index bf7d2beed13..022eefef318 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -4,7 +4,6 @@ import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.MageSingleton; -import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.common.continuous.CommanderReplacementEffect; @@ -24,6 +23,7 @@ import mage.game.stack.Spell; import mage.players.ManaPoolItem; import mage.players.Player; import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; import org.apache.log4j.Logger; import java.io.Serializable; @@ -229,7 +229,7 @@ public class ContinuousEffects implements Serializable { * "actual" meaning it becomes turned on that is defined by * Ability.#isInUseableZone(Game, boolean) method in * #getLayeredEffects(Game). - * + *

* It must be called with different timestamp group name (otherwise sort order will be changed for add/remove effects, see Urborg and Bloodmoon test) * * @param layerEffects @@ -703,10 +703,17 @@ public class ContinuousEffects implements Serializable { * @param game */ public void applySpliceEffects(Ability abilityToModify, Game game) { - if (((SpellAbility) abilityToModify).getSpellAbilityType() == SpellAbilityType.SPLICE) { - // on a spliced ability of a spell can't be spliced again + // add effects from splice card to spell ability on activate/cast + + // splice spell - spell can't be spliced again + if (CardUtil.isSpliceAbility(abilityToModify, game)) { return; } + // fused spell - can be spliced only to main fused ability, not to parts + if (CardUtil.isFusedPartAbility(abilityToModify, game)) { + return; + } + List spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId()); // get the applyable splice abilities List spliceAbilities = new ArrayList<>(); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index b418ad2fd8a..1d1b72f079e 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -10,12 +10,14 @@ import mage.cards.Card; import mage.constants.ColoredManaSymbol; import mage.constants.EmptyNames; import mage.constants.ManaType; +import mage.constants.SpellAbilityType; import mage.filter.Filter; import mage.filter.predicate.mageobject.NamePredicate; import mage.game.CardState; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; +import mage.game.stack.Spell; import mage.util.functions.CopyTokenFunction; import java.io.UnsupportedEncodingException; @@ -767,4 +769,26 @@ public final class CardUtil { return signedP + "/" + signedT; } + + public static boolean isSpliceAbility(Ability ability, Game game) { + if (ability instanceof SpellAbility) { + return ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.SPLICE; + } + return false; + } + + public static boolean isFusedPartAbility(Ability ability, Game game) { + // TODO: is works fine with copies of spells on stack? + if (ability instanceof SpellAbility) { + Spell mainSpell = game.getSpell(ability.getId()); + if (mainSpell == null) { + return true; + } else { + SpellAbility mainSpellAbility = mainSpell.getSpellAbility(); + return mainSpellAbility.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED + && !ability.equals(mainSpellAbility); + } + } + return false; + } }