From 1ce1d43e27d17e8586cf584161dc56f0955db047 Mon Sep 17 00:00:00 2001
From: Hans Mackowiak
Date: Mon, 5 Oct 2020 08:12:50 +0200
Subject: [PATCH] Copy Permanent Spell turns into token
---
.../src/main/java/forge/game/GameAction.java | 2 +-
.../effects/CopySpellAbilityEffect.java | 25 +++++-----
.../game/ability/effects/PermanentEffect.java | 10 ++++
.../java/forge/game/card/CardFactory.java | 50 ++++++-------------
.../src/main/java/forge/game/zone/Zone.java | 2 +-
.../res/cardsfolder/l/lithoform_engine.txt | 8 +++
.../v/verazol_the_split_current.txt | 15 ++++++
7 files changed, 63 insertions(+), 49 deletions(-)
create mode 100644 forge-gui/res/cardsfolder/l/lithoform_engine.txt
create mode 100644 forge-gui/res/cardsfolder/v/verazol_the_split_current.txt
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.)
+