From 099970de6ac5a4fb189a7c74d6a6f036d673ab8a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 15 Jul 2021 09:14:38 +0000 Subject: [PATCH] Token table --- .../src/main/java/forge/ai/SpellApiToAi.java | 1 + .../main/java/forge/ai/ability/AmassAi.java | 2 +- .../main/java/forge/ai/ability/TokenAi.java | 2 +- .../main/java/forge/game/ability/ApiType.java | 1 + .../game/ability/effects/AmassEffect.java | 34 +-- .../ability/effects/CopyPermanentEffect.java | 11 +- .../ability/effects/InvestigateEffect.java | 8 +- .../game/ability/effects/ReplaceEffect.java | 53 +--- .../ability/effects/ReplaceManaEffect.java | 37 +-- .../ability/effects/ReplaceTokenEffect.java | 131 ++++++++++ .../game/ability/effects/TokenEffect.java | 25 +- .../game/ability/effects/TokenEffectBase.java | 231 ++++++++++++------ .../forge/game/card/TokenCreateTable.java | 95 +++++++ .../java/forge/game/card/token/TokenInfo.java | 64 +---- .../forge/game/replacement/ReplaceToken.java | 16 +- .../game/replacement/ReplacementHandler.java | 73 ++++-- .../res/cardsfolder/a/academy_manufactor.txt | 7 +- .../a/adrix_and_nev_twincasters.txt | 5 +- .../res/cardsfolder/a/anointed_procession.txt | 6 +- .../res/cardsfolder/b/bestial_menace.txt | 6 +- .../c/chatterfang_squirrel_general.txt | 6 +- .../res/cardsfolder/c/crafty_cutpurse.txt | 5 +- .../res/cardsfolder/d/divine_visitation.txt | 4 +- .../res/cardsfolder/d/doubling_season.txt | 6 +- .../cardsfolder/f/forbidden_friendship.txt | 4 +- .../res/cardsfolder/m/mascot_exhibition.txt | 5 +- .../res/cardsfolder/p/parallel_lives.txt | 6 +- forge-gui/res/cardsfolder/p/primal_vigor.txt | 6 +- .../cardsfolder/s/selesnya_loft_gardens.txt | 5 +- .../res/cardsfolder/s/specimen_collector.txt | 4 +- .../res/cardsfolder/t/triplicate_titan.txt | 7 +- forge-gui/res/cardsfolder/upcoming/xorn.txt | 5 +- .../forge/player/PlayerControllerHuman.java | 4 +- 33 files changed, 514 insertions(+), 361 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/ReplaceTokenEffect.java create mode 100644 forge-game/src/main/java/forge/game/card/TokenCreateTable.java diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 61fe79ba314..c04f5ce1816 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -143,6 +143,7 @@ public enum SpellApiToAi { .put(ApiType.ReplaceEffect, AlwaysPlayAi.class) .put(ApiType.ReplaceDamage, AlwaysPlayAi.class) .put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class) + .put(ApiType.ReplaceToken, AlwaysPlayAi.class) .put(ApiType.RestartGame, RestartGameAi.class) .put(ApiType.Reveal, RevealAi.class) .put(ApiType.RevealHand, RevealHandAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/AmassAi.java b/forge-ai/src/main/java/forge/ai/ability/AmassAi.java index 2e8066cb5bd..b7a649acfb3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AmassAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AmassAi.java @@ -34,7 +34,7 @@ public class AmassAi extends SpellAbilityAi { final String tokenScript = "b_0_0_zombie_army"; final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa); - Card token = TokenInfo.getProtoType(tokenScript, sa, false); + Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false); if (token == null) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index 411291947a8..7827e0cfc31 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -358,7 +358,7 @@ public class TokenAi extends SpellAbilityAi { if (!sa.hasParam("TokenScript")) { throw new RuntimeException("Spell Ability has no TokenScript: " + sa); } - Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa); + Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa, ai); if (result == null) { throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript")); diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 79589cf90d0..afbb4b7ab0f 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -142,6 +142,7 @@ public enum ApiType { ReplaceEffect (ReplaceEffect.class), ReplaceMana (ReplaceManaEffect.class), ReplaceDamage (ReplaceDamageEffect.class), + ReplaceToken (ReplaceTokenEffect.class), ReplaceSplitDamage (ReplaceSplitDamageEffect.class), RestartGame (RestartGameEffect.class), Reveal (RevealEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java index fe4bc675585..0bfe529c0a5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java @@ -16,7 +16,6 @@ import forge.game.card.CardPredicates; import forge.game.card.CardZoneTable; import forge.game.card.CounterEnumType; import forge.game.card.CounterType; -import forge.game.card.token.TokenInfo; import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventTokenCreated; import forge.game.player.Player; @@ -53,37 +52,24 @@ public class AmassEffect extends TokenEffectBase { final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa); final boolean remember = sa.hasParam("RememberAmass"); - boolean useZoneTable = true; - CardZoneTable triggerList = sa.getChangeZoneTable(); - if (triggerList == null) { - triggerList = new CardZoneTable(); - useZoneTable = false; - } - if (sa.hasParam("ChangeZoneTable")) { - sa.setChangeZoneTable(triggerList); - useZoneTable = true; - } - - MutableBoolean combatChanged = new MutableBoolean(false); // create army token if needed if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) { - final String tokenScript = "b_0_0_zombie_army"; + CardZoneTable triggerList = new CardZoneTable(); + MutableBoolean combatChanged = new MutableBoolean(false); - final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false); + makeTokenTable(makeTokenTableInternal(activator, "b_0_0_zombie_army", 1, sa), false, triggerList, combatChanged, sa); - makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged); + triggerList.triggerChangesZoneAll(game, sa); + triggerList.clear(); - if (!useZoneTable) { - triggerList.triggerChangesZoneAll(game, sa); - triggerList.clear(); - } game.fireEvent(new GameEventTokenCreated()); + + if (combatChanged.isTrue()) { + game.updateCombatForView(); + game.fireEvent(new GameEventCombatChanged()); + } } - if (combatChanged.isTrue()) { - game.updateCombatForView(); - game.fireEvent(new GameEventCombatChanged()); - } Map params = Maps.newHashMap(); params.put("CounterType", CounterType.get(CounterEnumType.P1P1)); params.put("Amount", 1); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index 8061522ea74..f70bd56a975 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -20,6 +20,7 @@ import forge.game.card.CardFactory; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardZoneTable; +import forge.game.card.TokenCreateTable; import forge.game.event.GameEventCombatChanged; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -177,15 +178,18 @@ public class CopyPermanentEffect extends TokenEffectBase { } MutableBoolean combatChanged = new MutableBoolean(false); + TokenCreateTable tokenTable = new TokenCreateTable(); + for (final Card c : tgtCards) { // if it only targets player, it already got all needed cards from defined if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) { continue; } - - makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged); + tokenTable.put(controller, getProtoType(sa, c, controller), numCopies); } // end foreach Card + makeTokenTable(tokenTable, true, triggerList, combatChanged, sa); + if (!useZoneTable) { triggerList.triggerChangesZoneAll(game, sa); triggerList.clear(); @@ -196,9 +200,8 @@ public class CopyPermanentEffect extends TokenEffectBase { } } // end resolve - private Card getProtoType(final SpellAbility sa, final Card original) { + private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) { final Card host = sa.getHostCard(); - final Player newOwner = sa.getActivatingPlayer(); int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); final Card copy = new Card(id, original.getPaperCard(), host.getGame()); copy.setOwner(newOwner); diff --git a/forge-game/src/main/java/forge/game/ability/effects/InvestigateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/InvestigateEffect.java index 2d8491757a4..30746f73093 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/InvestigateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/InvestigateEffect.java @@ -6,7 +6,6 @@ import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardZoneTable; -import forge.game.card.token.TokenInfo; import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventTokenCreated; import forge.game.player.Player; @@ -36,14 +35,13 @@ public class InvestigateEffect extends TokenEffectBase { final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa); - final String tokenScript = "c_a_clue_draw"; - final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false); - + // Investigate in Sequence for (final Player p : getTargetPlayers(sa)) { for (int i = 0; i < amount; i++) { CardZoneTable triggerList = new CardZoneTable(); MutableBoolean combatChanged = new MutableBoolean(false); - makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged); + + makeTokenTable(makeTokenTableInternal(p, "c_a_clue_draw", 1, sa), false, triggerList, combatChanged, sa); triggerList.triggerChangesZoneAll(game, sa); p.addInvestigatedThisTurn(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java index 08acad9616e..48689ab377a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java @@ -3,12 +3,6 @@ package forge.game.ability.effects; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.Maps; - -import forge.game.Game; -import forge.game.GameLogEntryType; import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -17,25 +11,21 @@ import forge.game.card.Card; import forge.game.card.token.TokenInfo; import forge.game.player.Player; import forge.game.replacement.ReplacementResult; -import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; -import forge.util.TextUtil; + public class ReplaceEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card card = sa.getHostCard(); - final Game game = card.getGame(); final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName")); final String varValue = sa.getParam("VarValue"); final String type = sa.getParamOrDefault("VarType", "amount"); - final ReplacementType retype = sa.getReplacementEffect().getMode(); @SuppressWarnings("unchecked") - Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); - Map params = Maps.newHashMap(originalParams); + Map params = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); if ("Card".equals(type)) { List list = AbilityUtils.getDefinedCards(card, varValue, sa); @@ -53,7 +43,7 @@ public class ReplaceEffect extends SpellAbilityEffect { params.put(varName, list.get(0)); } } else if ("TokenScript".equals(type)) { - final Card protoType = TokenInfo.getProtoType(varValue, sa); + final Card protoType = TokenInfo.getProtoType(varValue, sa, sa.getActivatingPlayer()); if (protoType != null) { params.put(varName, protoType); } @@ -61,42 +51,7 @@ public class ReplaceEffect extends SpellAbilityEffect { params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa)); } - if (params.containsKey(AbilityKey.EffectOnly)) { - params.put(AbilityKey.EffectOnly, true); - } - - if (retype == ReplacementType.DamageDone) { - for (Map.Entry e : params.entrySet()) { - originalParams.put(e.getKey(), e.getValue()); - } - originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); - return; - } - - // need to log Updated events there, or the log is wrong order - String message = sa.getReplacementEffect().toString(); - if ( !StringUtils.isEmpty(message)) { - message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); - } - - //try to call replacementHandler with new Params - ReplacementResult result = game.getReplacementHandler().run(retype, params); - switch (result) { - case NotReplaced: - case Updated: { - for (Map.Entry e : params.entrySet()) { - originalParams.put(e.getKey(), e.getValue()); - } - // effect was updated - originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); - break; - } - default: - // effect was replaced with something else - originalParams.put(AbilityKey.ReplacementResult, result); - break; - } + params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java index bc8158111d8..aeb5ad22f3c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java @@ -4,20 +4,15 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; -import com.google.common.collect.Maps; import forge.card.ColorSet; import forge.card.MagicColor; -import forge.game.Game; -import forge.game.GameLogEntryType; import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; import forge.game.replacement.ReplacementResult; -import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; -import forge.util.TextUtil; public class ReplaceManaEffect extends SpellAbilityEffect { @@ -25,17 +20,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect { public void resolve(SpellAbility sa) { final Card card = sa.getHostCard(); final Player player = sa.getActivatingPlayer(); - final Game game = card.getGame(); // outside of Replacement Effect, unwanted result if (!sa.isReplacementAbility()) { return; } - final ReplacementType event = sa.getReplacementEffect().getMode(); @SuppressWarnings("unchecked") - Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); - Map params = Maps.newHashMap(originalParams); + Map params = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); String replaced = (String)sa.getReplacingObject(AbilityKey.Mana); if (sa.hasParam("ReplaceMana")) { @@ -79,31 +71,8 @@ public class ReplaceManaEffect extends SpellAbilityEffect { replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount"))); } params.put(AbilityKey.Mana, replaced); - - // need to log Updated events there, or the log is wrong order - String message = sa.getReplacementEffect().toString(); - if (!StringUtils.isEmpty(message)) { - message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); - } - - //try to call replacementHandler with new Params - ReplacementResult result = game.getReplacementHandler().run(event, params); - switch (result) { - case NotReplaced: - case Updated: { - for (Map.Entry e : params.entrySet()) { - originalParams.put(e.getKey(), e.getValue()); - } - // effect was updated - originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); - break; - } - default: - // effect was replaced with something else - originalParams.put(AbilityKey.ReplacementResult, result); - break; - } + // effect was updated + params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceTokenEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceTokenEffect.java new file mode 100644 index 00000000000..a9dc9337ad1 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceTokenEffect.java @@ -0,0 +1,131 @@ +package forge.game.ability.effects; + +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.ObjectUtils; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import forge.game.Game; +import forge.game.ability.AbilityKey; +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.TokenCreateTable; +import forge.game.card.token.TokenInfo; +import forge.game.player.Player; +import forge.game.replacement.ReplacementResult; +import forge.game.spellability.SpellAbility; + +public class ReplaceTokenEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + final Card card = sa.getHostCard(); + final Player p = sa.getActivatingPlayer(); + final Game game = card.getGame(); + + // ReplaceToken Effect only applies to one Player + Player affected = (Player) sa.getReplacingObject(AbilityKey.Player); + TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token); + + @SuppressWarnings("unchecked") + Map originalParams = (Map) sa + .getReplacingObject(AbilityKey.OriginalParams); + + // currently the only ones that changes the amount does double it + if ("Amount".equals(sa.getParam("Type"))) { + for (Map.Entry e : table.row(affected).entrySet()) { + if (!sa.matchesValidParam("ValidCard", e.getKey())) { + continue; + } + // currently the amount is only doubled + table.put(affected, e.getKey(), e.getValue() * 2); + } + } else if ("AddToken".equals(sa.getParam("Type"))) { + long timestamp = game.getNextTimestamp(); + + Map byController = Maps.newHashMap(); + for (Map.Entry e : table.row(affected).entrySet()) { + if (!sa.matchesValidParam("ValidCard", e.getKey())) { + continue; + } + Player contoller = e.getKey().getController(); + int old = ObjectUtils.defaultIfNull(byController.get(contoller), 0); + byController.put(contoller, old + e.getValue()); + } + + if (!byController.isEmpty()) { + // for Xorn, might matter if you could somehow create Treasure under multiple players control + if (sa.hasParam("Amount")) { + int i = AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa); + for (Map.Entry e : byController.entrySet()) { + e.setValue(i); + } + } + for (Map.Entry e : byController.entrySet()) { + for (String script : sa.getParam("TokenScript").split(",")) { + final Card token = TokenInfo.getProtoType(script, sa, p); + + if (token == null) { + throw new RuntimeException("don't find Token for TokenScript: " + script); + } + token.setController(e.getKey(), timestamp); + table.put(p, token, e.getValue()); + } + } + } + } else if ("ReplaceToken".equals(sa.getParam("Type"))) { + long timestamp = game.getNextTimestamp(); + + Map toInsertMap = Maps.newHashMap(); + Set toRemoveSet = Sets.newHashSet(); + for (Map.Entry e : table.row(affected).entrySet()) { + if (!sa.matchesValidParam("ValidCard", e.getKey())) { + continue; + } + Player controller = e.getKey().getController(); + int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0); + toInsertMap.put(controller, old + e.getValue()); + toRemoveSet.add(e.getKey()); + } + // remove replaced tokens + table.row(affected).keySet().removeAll(toRemoveSet); + + // insert new tokens + for (Map.Entry pe : toInsertMap.entrySet()) { + if (pe.getValue() <= 0) { + continue; + } + for (String script : sa.getParam("TokenScript").split(",")) { + final Card token = TokenInfo.getProtoType(script, sa, pe.getKey()); + + if (token == null) { + throw new RuntimeException("don't find Token for TokenScript: " + script); + } + + token.setController(pe.getKey(), timestamp); + table.put(affected, token, pe.getValue()); + } + } + } else if ("ReplaceController".equals(sa.getParam("Type"))) { + long timestamp = game.getNextTimestamp(); + Player newController = sa.getActivatingPlayer(); + if (sa.hasParam("NewController")) { + newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0); + } + for (Map.Entry c : table.row(affected).entrySet()) { + if (!sa.matchesValidParam("ValidCard", c.getKey())) { + continue; + } + c.getKey().setController(newController, timestamp); + } + } + + // effect was updated + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java index d902416c16e..e3f941eb852 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java @@ -23,10 +23,8 @@ import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardZoneTable; -import forge.game.card.token.TokenInfo; import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventTokenCreated; -import forge.game.player.Player; import forge.game.spellability.SpellAbility; public class TokenEffect extends TokenEffectBase { @@ -36,20 +34,6 @@ public class TokenEffect extends TokenEffectBase { return sa.getDescription(); } - public Card loadTokenPrototype(SpellAbility sa) { - if (!sa.hasParam("TokenScript")) { - return null; - } - - final Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa); - - if (result == null) { - throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript")); - } - - return result; - } - @Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); @@ -67,9 +51,8 @@ public class TokenEffect extends TokenEffectBase { } } - Card prototype = loadTokenPrototype(sa); - final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa); + MutableBoolean combatChanged = new MutableBoolean(false); boolean useZoneTable = true; CardZoneTable triggerList = sa.getChangeZoneTable(); @@ -82,10 +65,8 @@ public class TokenEffect extends TokenEffectBase { useZoneTable = true; } - MutableBoolean combatChanged = new MutableBoolean(false); - for (final Player owner : AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa)) { - makeTokens(prototype, owner, sa, finalAmount, true, false, triggerList, combatChanged); - } + makeTokenTable(AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa), + sa.getParam("TokenScript").split(","), finalAmount, false, triggerList, combatChanged, sa); if (!useZoneTable) { triggerList.triggerChangesZoneAll(game, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java index adc4a6bb571..09d9c0f4f68 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java @@ -2,123 +2,202 @@ package forge.game.ability.effects; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.commons.lang3.mutable.MutableBoolean; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.common.collect.Table; import forge.GameCommand; import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; +import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollection; +import forge.game.card.CardFactory; import forge.game.card.CardUtil; import forge.game.card.CardZoneTable; import forge.game.card.CounterType; +import forge.game.card.TokenCreateTable; import forge.game.card.token.TokenInfo; import forge.game.event.GameEventCardStatsChanged; import forge.game.player.Player; +import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; public abstract class TokenEffectBase extends SpellAbilityEffect { - protected List makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount, - boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) { + protected TokenCreateTable createTokenTable(Iterable players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) { + + TokenCreateTable tokenTable = new TokenCreateTable(); + for (final Player owner : players) { + for (String script : tokenScripts) { + final Card result = TokenInfo.getProtoType(script, sa, owner); + + if (result == null) { + throw new RuntimeException("don't find Token for TokenScript: " + script); + } + // set owner + result.setOwner(owner); + tokenTable.put(owner, result, finalAmount); + } + } + return tokenTable; + } + + protected TokenCreateTable makeTokenTableInternal(Player owner, String script, final int finalAmount, final SpellAbility sa) { + TokenCreateTable tokenTable = new TokenCreateTable(); + final Card result = TokenInfo.getProtoType(script, sa, owner, false); + + if (result == null) { + throw new RuntimeException("don't find Token for TokenScript: " + script); + } + // set owner + result.setOwner(owner); + tokenTable.put(owner, result, finalAmount); + + return tokenTable; + } + + protected TokenCreateTable makeTokenTable(Iterable players, String[] tokenScripts, final int finalAmount, final boolean clone, + CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) { + return makeTokenTable(createTokenTable(players, tokenScripts, finalAmount, sa), clone, triggerList, combatChanged, sa); + } + + protected TokenCreateTable makeTokenTable(TokenCreateTable tokenTable, final boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) { final Card host = sa.getHostCard(); final Game game = host.getGame(); - final long timestamp = game.getNextTimestamp(); + long timestamp = game.getNextTimestamp(); + + // support PlayerCollection for affected + Set toRemove = Sets.newHashSet(); + for (Player p : tokenTable.rowKeySet()) { + + final Map repParams = AbilityKey.mapFromAffected(p); + repParams.put(AbilityKey.Token, tokenTable); + repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens? + + switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) { + case NotReplaced: + break; + case Updated: { + tokenTable = (TokenCreateTable) repParams.get(AbilityKey.Token); + break; + } + default: + toRemove.add(p); + } + } + tokenTable.rowKeySet().removeAll(toRemove); final List pumpKeywords = Lists.newArrayList(); if (sa.hasParam("PumpKeywords")) { pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); } - List allTokens = Lists.newArrayList(); - for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) { - if (sa.hasParam("TokenTapped")) { - tok.setTapped(true); - } - - if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) { - continue; - } - - if (sa.hasParam("WithCounters")) { - String[] parse = sa.getParam("WithCounters").split("_"); - tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator); - } - - if (sa.hasParam("WithCountersType")) { - CounterType cType = CounterType.getType(sa.getParam("WithCountersType")); - int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa); - tok.addEtbCounter(cType, cAmount, creator); - } - - if (clone) { - tok.setCopiedPermanent(prototype); - } - - // Should this be catching the Card that's returned? - Card c = game.getAction().moveToPlay(tok, sa); - if (c == null || c.getZone() == null) { - // in case token can't enter the battlefield, it isn't created - triggerList.put(ZoneType.None, ZoneType.None, c); - continue; - } - triggerList.put(ZoneType.None, c.getZone().getZoneType(), c); - - creator.addTokensCreatedThisTurn(); - - if (clone) { - c.setCloneOrigin(host); - } - if (!pumpKeywords.isEmpty()) { - c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp); - addPumpUntil(sa, c, timestamp); - } - - if (sa.hasParam("AtEOTTrig")) { - addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c); - } - - if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) { - combatChanged.setTrue(); - } - - if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) { - attachTokenTo(tok, sa); - } - - c.updateStateForView(); - - if (sa.hasParam("RememberTokens")) { - host.addRemembered(c); - } - if (sa.hasParam("ImprintTokens")) { - host.addImprintedCard(c); - } - if (sa.hasParam("RememberSource")) { - c.addRemembered(host); - } - if (sa.hasParam("TokenRemembered")) { - final String remembered = sa.getParam("TokenRemembered"); - for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { - c.addRemembered(o); + for (final Table.Cell c : tokenTable.cellSet()) { + Card prototype = c.getColumnKey(); + Player creator = c.getRowKey(); + Player controller = prototype.getController(); + int cellAmount = c.getValue(); + for (int i = 0; i < cellAmount; i++) { + Card tok = CardFactory.copyCard(prototype, true); + // Crafty Cutpurse would change under which control it does enter, + // but it shouldn't change who creates the token + tok.setOwner(creator); + if (creator != controller) { + tok.setController(controller, timestamp); } + tok.setTimestamp(timestamp); + tok.setToken(true); + + // do effect stuff with the token + if (sa.hasParam("TokenTapped")) { + tok.setTapped(true); + } + + if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) { + continue; + } + if (sa.hasParam("WithCounters")) { + String[] parse = sa.getParam("WithCounters").split("_"); + tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator); + } + + if (sa.hasParam("WithCountersType")) { + CounterType cType = CounterType.getType(sa.getParam("WithCountersType")); + int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa); + tok.addEtbCounter(cType, cAmount, creator); + } + + if (clone) { + tok.setCopiedPermanent(prototype); + } + + // Should this be catching the Card that's returned? + Card moved = game.getAction().moveToPlay(tok, sa); + if (moved == null || moved.getZone() == null) { + // in case token can't enter the battlefield, it isn't created + triggerList.put(ZoneType.None, ZoneType.None, moved); + continue; + } + triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved); + + creator.addTokensCreatedThisTurn(); + + if (clone) { + moved.setCloneOrigin(host); + } + if (!pumpKeywords.isEmpty()) { + moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp); + addPumpUntil(sa, moved, timestamp); + } + + if (sa.hasParam("AtEOTTrig")) { + addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved); + } + + if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) { + combatChanged.setTrue(); + } + + if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) { + attachTokenTo(tok, sa); + } + + moved.updateStateForView(); + + if (sa.hasParam("RememberTokens")) { + host.addRemembered(moved); + } + if (sa.hasParam("ImprintTokens")) { + host.addImprintedCard(moved); + } + if (sa.hasParam("RememberSource")) { + moved.addRemembered(host); + } + if (sa.hasParam("TokenRemembered")) { + final String remembered = sa.getParam("TokenRemembered"); + for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { + moved.addRemembered(o); + } + } + allTokens.add(moved); } - allTokens.add(c); } if (sa.hasParam("AtEOT")) { registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens); } - return allTokens; + return tokenTable; } private boolean attachTokenTo(Card tok, SpellAbility sa) { diff --git a/forge-game/src/main/java/forge/game/card/TokenCreateTable.java b/forge-game/src/main/java/forge/game/card/TokenCreateTable.java new file mode 100644 index 00000000000..a61586898f0 --- /dev/null +++ b/forge-game/src/main/java/forge/game/card/TokenCreateTable.java @@ -0,0 +1,95 @@ +package forge.game.card; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.ObjectUtils; + +import com.google.common.collect.ForwardingTable; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Table; + +import forge.game.CardTraitBase; +import forge.game.GameObjectPredicates; +import forge.game.player.Player; + +public class TokenCreateTable extends ForwardingTable { + + Table dataMap = HashBasedTable.create(); + + public TokenCreateTable() { + } + + @Override + protected Table delegate() { + return dataMap; + } + + public int add(Player p, Card c, int i) { + int old = ObjectUtils.defaultIfNull(this.get(p, c), 0); + int newValue = old + i; + this.put(p, c, newValue); + return newValue; + } + + public int getFilterAmount(String validOwner, String validToken, final CardTraitBase ctb) { + final Card host = ctb.getHostCard(); + int result = 0; + List filteredCards = null; + List filteredPlayer = null; + + if (validOwner == null && validToken == null) { + for (Integer i : values()) { + result += i; + } + return result; + } + + if (validOwner != null) { + filteredPlayer = Lists.newArrayList(Iterables.filter(rowKeySet(), + GameObjectPredicates.restriction(validOwner.split(","), host.getController(), host, ctb))); + if (filteredPlayer.isEmpty()) { + return 0; + } + } + if (validToken != null) { + filteredCards = CardLists.getValidCardsAsList(columnKeySet(), validToken, host.getController(), host, ctb); + if (filteredCards.isEmpty()) { + return 0; + } + } + + if (filteredPlayer == null) { + for (Map.Entry> e : columnMap().entrySet()) { + for (Integer i : e.getValue().values()) { + result += i; + } + } + return result; + } + + if (filteredCards == null) { + for (Map.Entry> e : rowMap().entrySet()) { + for (Integer i : e.getValue().values()) { + result += i; + } + } + return result; + } + + for (Table.Cell c : this.cellSet()) { + if (!filteredPlayer.contains(c.getRowKey())) { + continue; + } + if (!filteredCards.contains(c.getColumnKey())) { + continue; + } + result += c.getValue(); + } + + + return result; + } +} diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index 300efe36677..82cd621cc94 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -15,15 +15,12 @@ import forge.StaticData; import forge.card.CardType; import forge.card.MagicColor; import forge.game.Game; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CardFactory; import forge.game.card.CardFactoryUtil; import forge.game.card.CardUtil; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; -import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.item.PaperToken; @@ -140,59 +137,6 @@ public class TokenInfo { return sb.toString(); } - public static List makeToken(final Card prototype, final Player owner, - final boolean applyMultiplier, final int num) { - final List list = Lists.newArrayList(); - - final Game game = owner.getGame(); - int multiplier = num; - Player player = owner; - Card proto = prototype; - - final Map repParams = AbilityKey.mapFromAffected(player); - repParams.put(AbilityKey.Token, prototype); - repParams.put(AbilityKey.TokenNum, multiplier); - repParams.put(AbilityKey.EffectOnly, applyMultiplier); - - switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) { - case NotReplaced: - break; - case Updated: { - multiplier = (int) repParams.get(AbilityKey.TokenNum); - player = (Player) repParams.get(AbilityKey.Affected); - proto = (Card) repParams.get(AbilityKey.Token); - break; - } - default: - multiplier = 0; - break; - } - if (multiplier <= 0) { - return list; - } - - long timestamp = game.getNextTimestamp(); - - for (int i = 0; i < multiplier; i++) { - // need to set owner or copyCard will fail with assign new ID - proto.setOwner(owner); - Card copy = CardFactory.copyCard(proto, true); - // need to assign player after token is copied - if (player != owner) { - copy.setController(player, timestamp); - } - copy.setTimestamp(timestamp); - copy.setToken(true); - list.add(copy); - } - - return list; - } - - static public List makeTokensFromPrototype(Card prototype, final Player owner, int amount, final boolean applyMultiplier) { - return makeToken(prototype, owner, applyMultiplier, amount); - } - public Card makeOneToken(final Player controller) { final Game game = controller.getGame(); final Card c = toCard(game); @@ -321,10 +265,10 @@ public class TokenInfo { result.getCurrentState().changeTextIntrinsic(colorMap, typeMap); } - static public Card getProtoType(final String script, final SpellAbility sa) { - return getProtoType(script, sa, true); + static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) { + return getProtoType(script, sa, owner, true); } - static public Card getProtoType(final String script, final SpellAbility sa, boolean applyTextChange) { + static public Card getProtoType(final String script, final SpellAbility sa, final Player owner, boolean applyTextChange) { // script might be null, or sa might be null if (script == null || sa == null) { return null; @@ -338,7 +282,7 @@ public class TokenInfo { if (token == null) { return null; } - final Card result = Card.fromPaperCard(token, null, game); + final Card result = Card.fromPaperCard(token, owner, game); if (sa.hasParam("TokenPower")) { String str = sa.getParam("TokenPower"); diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceToken.java b/forge-game/src/main/java/forge/game/replacement/ReplaceToken.java index 933696fbd2e..73486000a6c 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceToken.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceToken.java @@ -4,6 +4,7 @@ import java.util.Map; import forge.game.ability.AbilityKey; import forge.game.card.Card; +import forge.game.card.TokenCreateTable; import forge.game.spellability.SpellAbility; /** @@ -27,9 +28,11 @@ public class ReplaceToken extends ReplacementEffect { */ @Override public boolean canReplace(Map runParams) { + /* if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) { return false; } + //*/ if (hasParam("EffectOnly")) { final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly); @@ -41,10 +44,16 @@ public class ReplaceToken extends ReplacementEffect { if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) { return false; } + + /*/ if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) { return false; } + //*/ + if (filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)) <= 0) { + return false; + } return true; } @@ -54,8 +63,13 @@ public class ReplaceToken extends ReplacementEffect { */ @Override public void setReplacingObjects(Map runParams, SpellAbility sa) { - sa.setReplacingObject(AbilityKey.TokenNum, runParams.get(AbilityKey.TokenNum)); + sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token))); + sa.setReplacingObject(AbilityKey.Token, runParams.get(AbilityKey.Token)); sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected)); } + + public int filterAmount(final TokenCreateTable table) { + return table.getFilterAmount(getParamOrDefault("ValidPlayer", null), getParamOrDefault("ValidToken", null), this); + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index dd397fb70ca..e00dc6a0872 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import forge.game.CardTraitBase; @@ -274,20 +275,45 @@ public class ReplacementHandler { chosenRE.setOtherChoices(null); return res; } + + // Log there + String message = chosenRE.getDescription(); + if (!StringUtils.isEmpty(message)) { + if (chosenRE.getHostCard() != null) { + message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); + } + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + + // if its updated, try to call event again + if (res == ReplacementResult.Updated) { + Map params = Maps.newHashMap(runParams); + + if (params.containsKey(AbilityKey.EffectOnly)) { + params.put(AbilityKey.EffectOnly, true); + } + ReplacementResult result = run(event, params); + switch (result) { + case NotReplaced: + case Updated: { + for (Map.Entry e : params.entrySet()) { + runParams.put(e.getKey(), e.getValue()); + } + // effect was updated + runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + break; + } + default: + // effect was replaced with something else + runParams.put(AbilityKey.ReplacementResult, result); + break; + } + } + chosenRE.setHasRun(false); hasRun.remove(chosenRE); chosenRE.setOtherChoices(null); - // Updated Replacements need to be logged elsewhere because its otherwise in the wrong order - if (res != ReplacementResult.Updated) { - String message = chosenRE.getDescription(); - if (!StringUtils.isEmpty(message)) - if (chosenRE.getHostCard() != null) { - message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); - } - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); - } - return res; } @@ -300,7 +326,6 @@ public class ReplacementHandler { */ private ReplacementResult executeReplacement(final Map runParams, final ReplacementEffect replacementEffect, final Player decider, final Game game) { - final Map mapParams = replacementEffect.getMapParams(); SpellAbility effectSA = null; @@ -311,10 +336,9 @@ public class ReplacementHandler { host = game.getCardState(host); } - if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) { - final String effectSVar = mapParams.get("ReplaceWith"); + if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) { // TODO: the source of replacement effect should be the source of the original effect - effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect); + effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect); //replacementEffect.setOverridingAbility(effectSA); //effectSA.setTrigger(true); } else if (replacementEffect.getOverridingAbility() != null) { @@ -342,10 +366,10 @@ public class ReplacementHandler { // Decider gets to choose whether or not to apply the replacement. if (replacementEffect.hasParam("Optional")) { Player optDecider = decider; - if (mapParams.containsKey("OptionalDecider") && (effectSA != null)) { + if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) { effectSA.setActivatingPlayer(host.getController()); optDecider = AbilityUtils.getDefinedPlayers(host, - mapParams.get("OptionalDecider"), effectSA).get(0); + replacementEffect.getParam("OptionalDecider"), effectSA).get(0); } Card cardForUi = host.getCardForUi(); @@ -360,12 +384,12 @@ public class ReplacementHandler { } } - boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True"); - if (isPrevent || mapParams.containsKey("PreventionEffect")) { + boolean isPrevent = "True".equals(replacementEffect.getParam("Prevent")); + if (isPrevent || replacementEffect.hasParam("PreventionEffect")) { if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) { // If can't prevent damage, result is not replaced // But still put "prevented" amount for buffered SA - if (mapParams.containsKey("AlwaysReplace")) { + if (replacementEffect.hasParam("AlwaysReplace")) { runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount)); } else { runParams.put(AbilityKey.PreventedAmount, 0); @@ -377,10 +401,8 @@ public class ReplacementHandler { } } - if (mapParams.containsKey("Skip")) { - if (mapParams.get("Skip").equals("True")) { - return ReplacementResult.Skipped; // Event is skipped. - } + if ("True".equals(replacementEffect.getParam("Skip"))) { + return ReplacementResult.Skipped; // Event is skipped. } Player player = host.getController(); @@ -394,6 +416,11 @@ public class ReplacementHandler { // The SA if buffered, but replacement result should be set to Replaced runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced); } + + // these ones are special for updating + if (apiType == ApiType.ReplaceToken || apiType == ApiType.ReplaceEffect || apiType == ApiType.ReplaceMana) { + runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + } } // if the spellability is a replace effect then its some new logic diff --git a/forge-gui/res/cardsfolder/a/academy_manufactor.txt b/forge-gui/res/cardsfolder/a/academy_manufactor.txt index b3d78de8191..e7fc486ac49 100644 --- a/forge-gui/res/cardsfolder/a/academy_manufactor.txt +++ b/forge-gui/res/cardsfolder/a/academy_manufactor.txt @@ -2,11 +2,8 @@ Name:Academy Manufactor ManaCost:3 Types:Artifact Creature Assembly-Worker PT:1/3 -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ DBToken | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each. -SVar:DBToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenAmount$ X | SubAbility$ DBToken2 -SVar:DBToken2:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ X | SubAbility$ DBToken3 -SVar:DBToken3:DB$ Token | TokenScript$ c_a_treasure_sac | TokenAmount$ X -SVar:X:ReplaceCount$TokenNum +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ TokenReplace | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each. +SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Clue,Food,Treasure | TokenScript$ c_a_clue_draw,c_a_food_sac,c_a_treasure_sac DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain DeckHints:Ability$Investigate Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each. diff --git a/forge-gui/res/cardsfolder/a/adrix_and_nev_twincasters.txt b/forge-gui/res/cardsfolder/a/adrix_and_nev_twincasters.txt index e563b29233e..7a2c6832306 100644 --- a/forge-gui/res/cardsfolder/a/adrix_and_nev_twincasters.txt +++ b/forge-gui/res/cardsfolder/a/adrix_and_nev_twincasters.txt @@ -3,8 +3,7 @@ ManaCost:2 G U Types:Legendary Creature Merfolk Wizard PT:2/2 K:Ward:2 -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ DoubleToken | Description$ If one or more tokens would be created under your control, twice that many of those tokens are created instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X -SVar:X:ReplaceCount$TokenNum/Twice +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DoubleToken | Description$ If one or more tokens would be created under your control, twice that many of those tokens are created instead. +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl DeckHints:Ability$Token Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nIf one or more tokens would be created under your control, twice that many of those tokens are created instead. diff --git a/forge-gui/res/cardsfolder/a/anointed_procession.txt b/forge-gui/res/cardsfolder/a/anointed_procession.txt index bb7a722e807..fb9a3a42067 100644 --- a/forge-gui/res/cardsfolder/a/anointed_procession.txt +++ b/forge-gui/res/cardsfolder/a/anointed_procession.txt @@ -1,9 +1,7 @@ Name:Anointed Procession ManaCost:3 W Types:Enchantment -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X -SVar:X:ReplaceCount$TokenNum/Twice +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl DeckNeeds:Ability$Token -SVar:Picture:http://www.wizards.com/global/images/magic/general/anointed_procession.jpg Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. diff --git a/forge-gui/res/cardsfolder/b/bestial_menace.txt b/forge-gui/res/cardsfolder/b/bestial_menace.txt index 4359c202906..e40bda6ae26 100644 --- a/forge-gui/res/cardsfolder/b/bestial_menace.txt +++ b/forge-gui/res/cardsfolder/b/bestial_menace.txt @@ -1,9 +1,5 @@ Name:Bestial Menace ManaCost:3 G G Types:Sorcery -A:SP$ Token | Cost$ 3 G G | TokenAmount$ 1 | TokenScript$ g_1_1_snake | TokenOwner$ You | LegacyImage$ | SubAbility$ DBWolfToken | ChangeZoneTable$ True | SpellDescription$ Create a 1/1 green Snake creature token, -SVar:DBWolfToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_2_2_wolf | TokenOwner$ You | LegacyImage$ g 2 2 wolf wwk | SubAbility$ DBElephantToken | SpellDescription$ a 2/2 green Wolf creature token, -SVar:DBElephantToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_elephant | TokenOwner$ You | LegacyImage$ g 3 3 elephant wwk | SubAbility$ DBResolve | SpellDescription$ and a 3/3 green Elephant creature token. -SVar:DBResolve:DB$ ChangeZoneResolve -SVar:Picture:http://www.wizards.com/global/images/magic/general/bestial_menace.jpg +A:SP$ Token | Cost$ 3 G G | TokenAmount$ 1 | TokenScript$ g_1_1_snake,g_2_2_wolf,g_3_3_elephant | TokenOwner$ You | SpellDescription$ Create a 1/1 green Snake creature token, a 2/2 green Wolf creature token, and a 3/3 green Elephant creature token. Oracle:Create a 1/1 green Snake creature token, a 2/2 green Wolf creature token, and a 3/3 green Elephant creature token. diff --git a/forge-gui/res/cardsfolder/c/chatterfang_squirrel_general.txt b/forge-gui/res/cardsfolder/c/chatterfang_squirrel_general.txt index 0fb16a15cd3..c8aae6bab23 100644 --- a/forge-gui/res/cardsfolder/c/chatterfang_squirrel_general.txt +++ b/forge-gui/res/cardsfolder/c/chatterfang_squirrel_general.txt @@ -3,12 +3,10 @@ ManaCost:2 G Types:Legendary Creature Squirrel Warrior PT:3/3 K:Forestwalk -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ DBReplace | Description$ If one or more tokens would be created under your control, those tokens plus that many 1/1 green Squirrel creature tokens are created instead. -SVar:DBReplace:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ Y | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ Y | TokenScript$ g_1_1_squirrel +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DBReplace | Description$ If one or more tokens would be created under your control, those tokens plus that many 1/1 green Squirrel creature tokens are created instead. +SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | ValidCard$ Card.YouCtrl | TokenScript$ g_1_1_squirrel A:AB$ Pump | Cost$ B Sac | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +X | NumDef$ -X | SpellDescription$ Target creature gets +X/-X until end of turn. SVar:X:Count$xPaid -SVar:Y:ReplaceCount$TokenNum DeckHas:Ability$Token DeckHints:Type$Squirrel Oracle:Forestwalk (This creature can't be blocked as long as defending player controls a Forest.)\nIf one or more tokens would be created under your control, those tokens plus that many 1/1 green Squirrel creature tokens are created instead.\n{B}, Sacrifice X Squirrels: Target creature gets +X/-X until end of turn. diff --git a/forge-gui/res/cardsfolder/c/crafty_cutpurse.txt b/forge-gui/res/cardsfolder/c/crafty_cutpurse.txt index 911a8e65410..fab73f2e1e7 100644 --- a/forge-gui/res/cardsfolder/c/crafty_cutpurse.txt +++ b/forge-gui/res/cardsfolder/c/crafty_cutpurse.txt @@ -5,7 +5,6 @@ PT:2/2 K:Flash T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffect | TriggerDescription$ When CARDNAME enters the battlefield, each token that would be created under an opponent's control this turn is created under your control instead. SVar:TrigEffect:DB$ Effect | Name$ Crafty Cutpurse Effect | ReplacementEffects$ OppCreatEnters | SpellDescription$ Each token that would be created under an opponent's control this turn is created under your control instead. -SVar:OppCreatEnters:Event$ CreateToken | ActiveZones$ Command | ValidPlayer$ Player.Opponent | ReplaceWith$ ETBYourCtrl | Layer$ Control | Description$ Each token that would be created under an opponent's control this turn is created under your control instead. -SVar:ETBYourCtrl:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player -SVar:Picture:http://www.wizards.com/global/images/magic/general/crafty_cutpurse.jpg +SVar:OppCreatEnters:Event$ CreateToken | ActiveZones$ Command | ValidToken$ Card.OppCtrl | ReplaceWith$ ETBYourCtrl | Layer$ Control | Description$ Each token that would be created under an opponent's control this turn is created under your control instead. +SVar:ETBYourCtrl:DB$ ReplaceToken | Type$ ReplaceController | ValidCard$ Card.OppCtrl | NewController$ You Oracle:Flash\nWhen Crafty Cutpurse enters the battlefield, each token that would be created under an opponent's control this turn is created under your control instead. diff --git a/forge-gui/res/cardsfolder/d/divine_visitation.txt b/forge-gui/res/cardsfolder/d/divine_visitation.txt index 44f1729f5d4..1316838efed 100644 --- a/forge-gui/res/cardsfolder/d/divine_visitation.txt +++ b/forge-gui/res/cardsfolder/d/divine_visitation.txt @@ -1,7 +1,7 @@ Name:Divine Visitation ManaCost:3 W W Types:Enchantment -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ TokenReplace | ValidToken$ Creature | Description$ If one or more creature tokens would be created under your control, that many 4/4 white Angel creature tokens with flying and vigilance are created instead. -SVar:TokenReplace:DB$ ReplaceEffect | VarName$ Token | VarValue$ w_4_4_angel_flying_vigilance | VarType$ TokenScript +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Creature.YouCtrl | ReplaceWith$ TokenReplace | Description$ If one or more creature tokens would be created under your control, that many 4/4 white Angel creature tokens with flying and vigilance are created instead. +SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Creature.YouCtrl | TokenScript$ w_4_4_angel_flying_vigilance DeckNeeds:Ability$Token Oracle:If one or more creature tokens would be created under your control, that many 4/4 white Angel creature tokens with flying and vigilance are created instead. diff --git a/forge-gui/res/cardsfolder/d/doubling_season.txt b/forge-gui/res/cardsfolder/d/doubling_season.txt index 8e2d5d2500b..7e3d1c5f1cc 100644 --- a/forge-gui/res/cardsfolder/d/doubling_season.txt +++ b/forge-gui/res/cardsfolder/d/doubling_season.txt @@ -1,11 +1,9 @@ Name:Doubling Season ManaCost:4 G Types:Enchantment -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | EffectOnly$ True | ReplaceWith$ DoubleCounters | Description$ If an effect would put one or more counters on a permanent you control, it puts twice that many of those counters on that permanent instead. SVar:DoubleCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Y -SVar:X:ReplaceCount$TokenNum/Twice SVar:Y:ReplaceCount$CounterNum/Twice -SVar:Picture:http://www.wizards.com/global/images/magic/general/doubling_season.jpg Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead.\nIf an effect would put one or more counters on a permanent you control, it puts twice that many of those counters on that permanent instead. diff --git a/forge-gui/res/cardsfolder/f/forbidden_friendship.txt b/forge-gui/res/cardsfolder/f/forbidden_friendship.txt index fb71c9f4573..61a0d5d54c3 100755 --- a/forge-gui/res/cardsfolder/f/forbidden_friendship.txt +++ b/forge-gui/res/cardsfolder/f/forbidden_friendship.txt @@ -1,8 +1,6 @@ Name:Forbidden Friendship ManaCost:1 R Types:Sorcery -A:SP$ Token | Cost$ 1 R | TokenAmount$ 1 | TokenScript$ r_1_1_dinosaur_haste | TokenOwner$ You | LegacyImage$ r 1 1 dinosaur haste iko | ChangeZoneTable$ True | SubAbility$ DBToken | SpellDescription$ Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token. -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier iko | SubAbility$ DBResolve -SVar:DBResolve:DB$ ChangeZoneResolve +A:SP$ Token | Cost$ 1 R | TokenAmount$ 1 | TokenScript$ r_1_1_dinosaur_haste,w_1_1_human_soldier | TokenOwner$ You | SpellDescription$ Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token. DeckHas:Ability$Token Oracle:Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token. diff --git a/forge-gui/res/cardsfolder/m/mascot_exhibition.txt b/forge-gui/res/cardsfolder/m/mascot_exhibition.txt index 020f50f2b4c..220c8dfe951 100644 --- a/forge-gui/res/cardsfolder/m/mascot_exhibition.txt +++ b/forge-gui/res/cardsfolder/m/mascot_exhibition.txt @@ -1,9 +1,6 @@ Name:Mascot Exhibition ManaCost:7 Types:Sorcery Lesson -A:SP$ Token | Cost$ 7 | TokenAmount$ 1 | TokenScript$ wb_2_1_inkling_flying | TokenOwner$ You | SubAbility$ DBSpiritToken | ChangeZoneTable$ True | SpellDescription$ Create a 2/1 white and black Inkling creature token with flying, -SVar:DBSpiritToken:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_3_2_spirit | TokenOwner$ You | SubAbility$ DBElemToken | SpellDescription$ a 3/2 red and white Spirit creature token, -SVar:DBElemToken:DB$ Token | TokenAmount$ 1 | TokenScript$ ur_4_4_elemental | TokenOwner$ You | SubAbility$ DBResolve | SpellDescription$ and a 4/4 blue and red Elemental creature token. -SVar:DBResolve:DB$ ChangeZoneResolve +A:SP$ Token | Cost$ 7 | TokenAmount$ 1 | TokenScript$ wb_2_1_inkling_flying,rw_3_2_spirit,ur_4_4_elemental | TokenOwner$ You | SpellDescription$ Create a 2/1 white and black Inkling creature token with flying, a 3/2 red and white Spirit creature token, and a 4/4 blue and red Elemental creature token. DeckHas:Ability$Token Oracle:Create a 2/1 white and black Inkling creature token with flying, a 3/2 red and white Spirit creature token, and a 4/4 blue and red Elemental creature token. diff --git a/forge-gui/res/cardsfolder/p/parallel_lives.txt b/forge-gui/res/cardsfolder/p/parallel_lives.txt index e134337e6e1..ed629c1e09c 100644 --- a/forge-gui/res/cardsfolder/p/parallel_lives.txt +++ b/forge-gui/res/cardsfolder/p/parallel_lives.txt @@ -1,8 +1,6 @@ Name:Parallel Lives ManaCost:3 G Types:Enchantment -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X -SVar:X:ReplaceCount$TokenNum/Twice -SVar:Picture:http://www.wizards.com/global/images/magic/general/parallel_lives.jpg +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. diff --git a/forge-gui/res/cardsfolder/p/primal_vigor.txt b/forge-gui/res/cardsfolder/p/primal_vigor.txt index 3192f00044e..ff11199dd59 100644 --- a/forge-gui/res/cardsfolder/p/primal_vigor.txt +++ b/forge-gui/res/cardsfolder/p/primal_vigor.txt @@ -1,11 +1,9 @@ Name:Primal Vigor ManaCost:4 G Types:Enchantment -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ Player | ReplaceWith$ DoubleToken | Description$ If one or more tokens would be created, twice that many of those tokens are created instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X +R:Event$ CreateToken | ActiveZones$ Battlefield | ReplaceWith$ DoubleToken | Description$ If one or more tokens would be created, twice that many of those tokens are created instead. +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount R:Event$ AddCounter | ActiveZones$ Battlefield | ValidCard$ Creature | ValidCounterType$ P1P1 | ReplaceWith$ DoubleP1P1Counters | Description$ If one or more +1/+1 counters would be put on a creature, twice that many +1/+1 counters are put on that creature instead. SVar:DoubleP1P1Counters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Y -SVar:X:ReplaceCount$TokenNum/Twice SVar:Y:ReplaceCount$CounterNum/Twice -SVar:Picture:http://www.wizards.com/global/images/magic/general/primal_vigor.jpg Oracle:If one or more tokens would be created, twice that many of those tokens are created instead.\nIf one or more +1/+1 counters would be put on a creature, twice that many +1/+1 counters are put on that creature instead. diff --git a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt index e8bfafb6154..fae637b07de 100644 --- a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt +++ b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt @@ -2,14 +2,13 @@ Name:Selesnya Loft Gardens ManaCost:no cost Types:Plane Ravnica R:Event$ CreateToken | ActiveZones$ Command | ReplaceWith$ DoubleToken | EffectOnly$ True | Description$ If an effect would create one or more tokens, it creates twice that many of those tokens instead. -SVar:DoubleToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ Y +SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount R:Event$ AddCounter | ActiveZones$ Command | ValidCard$ Permanent | EffectOnly$ True | ReplaceWith$ DoubleCounters | Description$ If an effect would put one or more counters on a permanent, it puts twice that many of those counters on that permanent instead. SVar:DoubleCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Z -SVar:Y:ReplaceCount$TokenNum/Twice SVar:Z:ReplaceCount$CounterNum/Twice T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. SVar:RolledChaos:DB$ Effect | AILogic$ Always | Triggers$ TrigTapForMana SVar:TrigTapForMana:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land | Activator$ You | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You SVar:AIRollPlanarDieParams:Mode$ Always | MinTurn$ 1 | RollInMain1$ True -Oracle:If an effect would create one or more tokens, it creates twice that many of those tokens instead.\nIf an effect would put one or more counters on a permanent, it puts twice that many of those counters on that permanent instead.\nWhenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. +Oracle:If an effect would create one or more tokens, it creates twice that many of those tokens instead.\nIf an effect would put one or more counters on a permanent, it puts twice that many of those counters on that permanent instead.\nWhenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/s/specimen_collector.txt b/forge-gui/res/cardsfolder/s/specimen_collector.txt index 6cf90aee830..0750af15903 100644 --- a/forge-gui/res/cardsfolder/s/specimen_collector.txt +++ b/forge-gui/res/cardsfolder/s/specimen_collector.txt @@ -3,9 +3,7 @@ ManaCost:4 U Types:Creature Vedalken Wizard PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 green Squirrel creature token and a 0/3 blue Crab creature token. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel | TokenOwner$ You | ChangeZoneTable$ True | SubAbility$ DBCrabToken -SVar:DBCrabToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_0_3_crab | TokenOwner$ You | SubAbility$ DBResolve -SVar:DBResolve:DB$ ChangeZoneResolve +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel,u_0_3_crab | TokenOwner$ You T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigCopy | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, create a token that's a copy of target token you control. SVar:TrigCopy:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl | TgtPrompt$ Select target token you control DeckHas:Ability$Token diff --git a/forge-gui/res/cardsfolder/t/triplicate_titan.txt b/forge-gui/res/cardsfolder/t/triplicate_titan.txt index 4ae7b601aa2..a086b1ef3f6 100644 --- a/forge-gui/res/cardsfolder/t/triplicate_titan.txt +++ b/forge-gui/res/cardsfolder/t/triplicate_titan.txt @@ -5,10 +5,7 @@ PT:9/9 K:Flying K:Vigilance K:Trample -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenFly | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, create a 3/3 colorless Golem artifact creature token with flying, a 3/3 colorless Golem artifact creature token with vigilance, and a 3/3 colorless Golem artifact creature token with trample. -SVar:TrigTokenFly:DB$Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem_flying | ChangeZoneTable$ True | SubAbility$ DBTokenVig -SVar:DBTokenVig:DB$Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem_vigilance | SubAbility$ DBTokenTra -SVar:DBTokenTra:DB$Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem_trample | SubAbility$ DBResolve -SVar:DBResolve:DB$ ChangeZoneResolve +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a 3/3 colorless Golem artifact creature token with flying, a 3/3 colorless Golem artifact creature token with vigilance, and a 3/3 colorless Golem artifact creature token with trample. +SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem_flying,c_3_3_a_golem_vigilance,c_3_3_a_golem_trample DeckHas:Ability$Token Oracle:Flying, vigilance, trample\nWhen Triplicate Titan dies, create a 3/3 colorless Golem artifact creature token with flying, a 3/3 colorless Golem artifact creature token with vigilance, and a 3/3 colorless Golem artifact creature token with trample. diff --git a/forge-gui/res/cardsfolder/upcoming/xorn.txt b/forge-gui/res/cardsfolder/upcoming/xorn.txt index 799b0cf4f09..6e5e333bb6a 100644 --- a/forge-gui/res/cardsfolder/upcoming/xorn.txt +++ b/forge-gui/res/cardsfolder/upcoming/xorn.txt @@ -2,9 +2,8 @@ Name:Xorn ManaCost:2 R Types:Creature Elemental PT:3/2 -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Treasure | ReplaceWith$ AdditionalToken | Description$ If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token. -SVar:AdditionalToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X -SVar:X:ReplaceCount$TokenNum/Plus.1 +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Treasure | ReplaceWith$ DBReplace | Description$ If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token. +SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | Amount$ 1 | TokenScript$ c_a_treasure_sac DeckNeeds:Type$Token AI:RemoveDeck:Random Oracle:If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token. diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 7a7e894c172..8afafe6ded8 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1324,7 +1324,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } if (sa.hasParam("TokenScript")) { sa.setActivatingPlayer(player); - Card protoType = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa); + Card protoType = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa, null); for (String type : protoType.getType().getCreatureTypes()) { Integer count = typesInDeck.get(type); if (count == null) { @@ -1340,7 +1340,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont if (sa != null) { if (sa.hasParam("TokenScript")) { sa.setActivatingPlayer(player); - Card protoType = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa); + Card protoType = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa, null); for (String type : protoType.getType().getCreatureTypes()) { Integer count = typesInDeck.get(type); if (count == null) {