mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 19:11:59 -08:00
* Split cards - added spliced effects support for fused spells, no more double splice pays (#6493, #6549);
This commit is contained in:
parent
b38ac2f575
commit
a75d08283f
5 changed files with 144 additions and 5 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1077,6 +1077,7 @@ public class TestPlayer implements Player {
|
||||||
List<String> data = abilities.stream()
|
List<String> data = abilities.stream()
|
||||||
.map(a -> (a.getZone() + " -> "
|
.map(a -> (a.getZone() + " -> "
|
||||||
+ a.getSourceObject(game).getIdName() + " -> "
|
+ a.getSourceObject(game).getIdName() + " -> "
|
||||||
|
+ (a.toString().startsWith("Cast ") ? "[" + a.getManaCostsToPay().getText() + "] -> " : "") // printed cost, not modified
|
||||||
+ (a.toString().length() > 0
|
+ (a.toString().length() > 0
|
||||||
? a.toString().substring(0, Math.min(20, a.toString().length()))
|
? a.toString().substring(0, Math.min(20, a.toString().length()))
|
||||||
: a.getClass().getSimpleName())
|
: a.getClass().getSimpleName())
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import mage.players.Player;
|
||||||
import mage.target.Target;
|
import mage.target.Target;
|
||||||
import mage.target.Targets;
|
import mage.target.Targets;
|
||||||
import mage.target.targetadjustment.TargetAdjuster;
|
import mage.target.targetadjustment.TargetAdjuster;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
import mage.util.ThreadLocalStringBuilder;
|
import mage.util.ThreadLocalStringBuilder;
|
||||||
import mage.watchers.Watcher;
|
import mage.watchers.Watcher;
|
||||||
|
|
@ -352,8 +353,15 @@ public abstract class AbilityImpl implements Ability {
|
||||||
return false;
|
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
|
//20101001 - 601.2e
|
||||||
if (sourceObject != null) {
|
if (needCostModification && sourceObject != null) {
|
||||||
sourceObject.adjustCosts(this, game); // still needed
|
sourceObject.adjustCosts(this, game); // still needed
|
||||||
game.getContinuousEffects().costModification(this, game);
|
game.getContinuousEffects().costModification(this, game);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import mage.MageObject;
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.MageSingleton;
|
import mage.abilities.MageSingleton;
|
||||||
import mage.abilities.SpellAbility;
|
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.StaticAbility;
|
||||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
|
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
|
||||||
|
|
@ -24,6 +23,7 @@ import mage.game.stack.Spell;
|
||||||
import mage.players.ManaPoolItem;
|
import mage.players.ManaPoolItem;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.common.TargetCardInHand;
|
import mage.target.common.TargetCardInHand;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -229,7 +229,7 @@ public class ContinuousEffects implements Serializable {
|
||||||
* "actual" meaning it becomes turned on that is defined by
|
* "actual" meaning it becomes turned on that is defined by
|
||||||
* Ability.#isInUseableZone(Game, boolean) method in
|
* Ability.#isInUseableZone(Game, boolean) method in
|
||||||
* #getLayeredEffects(Game).
|
* #getLayeredEffects(Game).
|
||||||
*
|
* <p>
|
||||||
* It must be called with different timestamp group name (otherwise sort order will be changed for add/remove effects, see Urborg and Bloodmoon test)
|
* 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
|
* @param layerEffects
|
||||||
|
|
@ -703,10 +703,17 @@ public class ContinuousEffects implements Serializable {
|
||||||
* @param game
|
* @param game
|
||||||
*/
|
*/
|
||||||
public void applySpliceEffects(Ability abilityToModify, Game game) {
|
public void applySpliceEffects(Ability abilityToModify, Game game) {
|
||||||
if (((SpellAbility) abilityToModify).getSpellAbilityType() == SpellAbilityType.SPLICE) {
|
// add effects from splice card to spell ability on activate/cast
|
||||||
// on a spliced ability of a spell can't be spliced again
|
|
||||||
|
// splice spell - spell can't be spliced again
|
||||||
|
if (CardUtil.isSpliceAbility(abilityToModify, game)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// fused spell - can be spliced only to main fused ability, not to parts
|
||||||
|
if (CardUtil.isFusedPartAbility(abilityToModify, game)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<SpliceCardEffect> spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId());
|
List<SpliceCardEffect> spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId());
|
||||||
// get the applyable splice abilities
|
// get the applyable splice abilities
|
||||||
List<Ability> spliceAbilities = new ArrayList<>();
|
List<Ability> spliceAbilities = new ArrayList<>();
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ import mage.cards.Card;
|
||||||
import mage.constants.ColoredManaSymbol;
|
import mage.constants.ColoredManaSymbol;
|
||||||
import mage.constants.EmptyNames;
|
import mage.constants.EmptyNames;
|
||||||
import mage.constants.ManaType;
|
import mage.constants.ManaType;
|
||||||
|
import mage.constants.SpellAbilityType;
|
||||||
import mage.filter.Filter;
|
import mage.filter.Filter;
|
||||||
import mage.filter.predicate.mageobject.NamePredicate;
|
import mage.filter.predicate.mageobject.NamePredicate;
|
||||||
import mage.game.CardState;
|
import mage.game.CardState;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.permanent.token.Token;
|
import mage.game.permanent.token.Token;
|
||||||
|
import mage.game.stack.Spell;
|
||||||
import mage.util.functions.CopyTokenFunction;
|
import mage.util.functions.CopyTokenFunction;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
@ -767,4 +769,26 @@ public final class CardUtil {
|
||||||
|
|
||||||
return signedP + "/" + signedT;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue