diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 16b2d97728e..6366d297ffd 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -103,7 +103,7 @@ public class GameAction { boolean wasFacedown = c.isFaceDown(); //Rule 110.5g: A token that has left the battlefield can't move to another zone - if (c.isToken() && zoneFrom != null && !fromBattlefield) { + if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) { return c; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index 43e13b56480..9d0f4b9617a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -89,7 +89,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { String prompt = Localizer.getInstance().getMessage("lblSelectMultiSpellCopyToStack", Lang.getOrdinal(multi + 1)); SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt, ImmutableMap.of()); - SpellAbility copiedSpell = CardFactory.copySpellAbilityAndPossiblyHost(card, chosen.getHostCard(), chosen, true); + SpellAbility copiedSpell = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosen); copiedSpell.getHostCard().setController(card.getController(), card.getGame().getNextTimestamp()); copiedSpell.setActivatingPlayer(controller); copies.add(copiedSpell); @@ -120,7 +120,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { mayChooseNewTargets = false; for (GameEntity o : candidates) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); resetFirstTargetOnCopy(copy, o, targetedSA); copies.add(copy); } @@ -147,18 +147,18 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { mayChooseNewTargets = false; if (sa.hasParam("ChooseOnlyOne")) { Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null); - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true); - resetFirstTargetOnCopy(copy, choice, targetedSA); + if (choice != null) { + valid = new CardCollection(choice); + } + } + + for (final Card c : valid) { + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); + resetFirstTargetOnCopy(copy, c, targetedSA); copies.add(copy); - } else { - for (final Card c : valid) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true); - resetFirstTargetOnCopy(copy, c, targetedSA); - copies.add(copy); - } } for (final Player p : players) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); resetFirstTargetOnCopy(copy, p, targetedSA); copies.add(copy); } @@ -169,8 +169,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { Localizer.getInstance().getMessage("lblSelectASpellCopy"), ImmutableMap.of()); chosenSA.setActivatingPlayer(controller); for (int i = 0; i < amount; i++) { - SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost( - card, chosenSA.getHostCard(), chosenSA, true); + SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA); // extra case for Epic to remove the keyword and the last part of the SpellAbility if (sa.hasParam("Epic")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java index f916ee8b67e..80798b20f1e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java @@ -22,6 +22,16 @@ public class PermanentEffect extends SpellAbilityEffect { sa.getHostCard().setController(p, 0); final Card host = sa.getHostCard(); + // 111.11. A copy of a permanent spell becomes a token as it resolves. + // The token has the characteristics of the spell that became that token. + // The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token. + if (host.isCopiedSpell()) { + host.setCopiedSpell(false); + host.setToken(true); + // for replacement Effects, need to add the previous copied spell to the Stack Zone + host.getGame().getStackZone().add(host); + } + final Card c = p.getGame().getAction().moveToPlay(host, p, sa); sa.setHostCard(c); diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index d73dae2bc8f..a75b5c242ba 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -125,12 +125,13 @@ public class CardFactory { * which wouldn't ordinarily get set during a simple Card.copy() call. *

* */ - private final static Card copySpellHost(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails){ - Player controller = sa.getActivatingPlayer(); + private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA){ + final Card source = sourceSA.getHostCard(); + final Card original = targetSA.getHostCard(); + Player controller = sourceSA.getActivatingPlayer(); final Card c = copyCard(original, true); // change the color of the copy (eg: Fork) - final SpellAbility sourceSA = source.getFirstSpellAbility(); if (null != sourceSA && sourceSA.hasParam("CopyIsColor")) { String tmp = ""; final String newColor = sourceSA.getParam("CopyIsColor"); @@ -148,13 +149,11 @@ public class CardFactory { c.setOwner(controller); c.setCopiedSpell(true); - if (bCopyDetails) { - c.setXManaCostPaidByColor(original.getXManaCostPaidByColor()); - c.setKickerMagnitude(original.getKickerMagnitude()); + c.setXManaCostPaidByColor(original.getXManaCostPaidByColor()); + c.setKickerMagnitude(original.getKickerMagnitude()); - for (OptionalCost cost : original.getOptionalCostsPaid()) { - c.addOptionalCostPaid(cost); - } + for (OptionalCost cost : original.getOptionalCostsPaid()) { + c.addOptionalCostPaid(cost); } return c; } @@ -174,44 +173,27 @@ public class CardFactory { * @param bCopyDetails * a boolean. */ - public final static SpellAbility copySpellAbilityAndPossiblyHost(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails) { - Player controller = sa.getActivatingPlayer(); + public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA) { + Player controller = sourceSA.getActivatingPlayer(); //it is only necessary to copy the host card if the SpellAbility is a spell, not an ability - final Card c; - if (sa.isSpell()){ - c = copySpellHost(source, original, sa, bCopyDetails); - } - else { - c = original; - } + final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA) : targetSA.getHostCard(); final SpellAbility copySA; - if (sa.isTrigger() && sa.isWrapper()) { - copySA = getCopiedTriggeredAbility((WrappedAbility)sa, c); + if (targetSA.isTrigger() && targetSA.isWrapper()) { + copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c); } else { - copySA = sa.copy(c, false); - } - - if (sa.isSpell()){ - //only update c's abilities if c is a copy. - //(it would be nice to move this into `copySpellHost`, - // so all the c-mutating code is together in one place. - // but copySA doesn't exist until after `copySpellHost` finishes executing, - // so it's hard to resolve that dependency.) - c.getCurrentState().setNonManaAbilities(copySA); + copySA = targetSA.copy(c, false); } copySA.setCopied(true); //remove all costs if (!copySA.isTrigger()) { - copySA.setPayCosts(new Cost("", sa.isAbility())); + copySA.setPayCosts(new Cost("", targetSA.isAbility())); } copySA.setActivatingPlayer(controller); - if (bCopyDetails) { - copySA.setPaidHash(sa.getPaidHash()); - } + copySA.setPaidHash(targetSA.getPaidHash()); return copySA; } diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-game/src/main/java/forge/game/zone/Zone.java index 5b3304b081f..fba88093d8d 100644 --- a/forge-game/src/main/java/forge/game/zone/Zone.java +++ b/forge-game/src/main/java/forge/game/zone/Zone.java @@ -115,7 +115,7 @@ public class Zone implements java.io.Serializable, Iterable { // Do not add Tokens to other zones than the battlefield. // But Effects/Emblems count as Tokens too, so allow Command too. - if (zoneType == ZoneType.Battlefield || !c.isToken()) { + if (zoneType == ZoneType.Battlefield || zoneType == ZoneType.Stack || !c.isToken()) { c.setZone(this); if (index == null) { diff --git a/forge-gui/res/cardsfolder/l/lithoform_engine.txt b/forge-gui/res/cardsfolder/l/lithoform_engine.txt new file mode 100644 index 00000000000..381616a58d9 --- /dev/null +++ b/forge-gui/res/cardsfolder/l/lithoform_engine.txt @@ -0,0 +1,8 @@ +Name:Lithoform Engine +ManaCost:4 +Types:Legendary Artifact +A:AB$ CopySpellAbility | Cost$ 2 T | TgtPrompt$ Select target activated or triggered ability you control | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card | StackDescription$ SpellDescription | SpellDescription$ Copy target activated or triggered ability you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 3 T | TgtPrompt$ Select target instant or sorcery spell you control | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 4 T | TgtPrompt$ Select target permanent spell you control | ValidTgts$ Permanent.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target permanent spell you control. (The copy becomes a token.) +Oracle:{2}, {T}: Copy target activated or triggered ability you control. You may choose new targets for the copy.\n{3}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy.\n{4}, {T}: Copy target permanent spell you control. (The copy becomes a token.) + diff --git a/forge-gui/res/cardsfolder/v/verazol_the_split_current.txt b/forge-gui/res/cardsfolder/v/verazol_the_split_current.txt new file mode 100644 index 00000000000..e49b3234d2f --- /dev/null +++ b/forge-gui/res/cardsfolder/v/verazol_the_split_current.txt @@ -0,0 +1,15 @@ +Name:Verazol, the Split Current +ManaCost:X G U +Types:Legendary Creature Serpent +PT:0/0 +K:etbCounter:P1P1:Y:no Condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each mana spent to cast it. +SVar:X:Count$xPaid +SVar:Y:Count$FirstSpellTotalManaSpent +T:Mode$ SpellCast | ValidSA$ Spell.Kicked | ValidActivatingPlayer$ You | Execute$ DBRemoveCounters | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever you cast a kicked spell, you may remove two +1/+1 counters from CARDNAME. +SVar:DBRemoveCounters:DB$ RemoveCounter | CounterType$ P1P1 | CounterNum$ 2 | RememberRemoved$ True | SubAbility$ DBCopy +SVar:DBCopy:DB$ CopySpellAbility | ConditionCheckSVar$ Z | ConditionSVarCompare$ GE1 | References$ Z | SubAbility$ DBCleanup | Defined$ TriggeredSpellAbility | AILogic$ Always | SpellDescription$ If you do, copy that spell. You may choose new targets for that copy. (A copy of a permanent spell becomes a token.) +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Z:Count$RememberedSize +DeckHas:Ability$Counters +Oracle:Verazol, the Split Current enters the battlefield with a +1/+1 counter on it for each mana spent to cast it.\nWhenever you cast a kicked spell, you may remove two +1/+1 counters from Verazol, the Split Current. If you do, copy that spell. You may choose new targets for that copy. (A copy of a permanent spell becomes a token.) +