Token table

This commit is contained in:
Hans Mackowiak
2021-07-15 09:14:38 +00:00
committed by Michael Kamensky
parent 91d16eaa2d
commit 099970de6a
33 changed files with 514 additions and 361 deletions

View File

@@ -143,6 +143,7 @@ public enum SpellApiToAi {
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class) .put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class) .put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class) .put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
.put(ApiType.RestartGame, RestartGameAi.class) .put(ApiType.RestartGame, RestartGameAi.class)
.put(ApiType.Reveal, RevealAi.class) .put(ApiType.Reveal, RevealAi.class)
.put(ApiType.RevealHand, RevealHandAi.class) .put(ApiType.RevealHand, RevealHandAi.class)

View File

@@ -34,7 +34,7 @@ public class AmassAi extends SpellAbilityAi {
final String tokenScript = "b_0_0_zombie_army"; final String tokenScript = "b_0_0_zombie_army";
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa); 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) { if (token == null) {
return false; return false;

View File

@@ -358,7 +358,7 @@ public class TokenAi extends SpellAbilityAi {
if (!sa.hasParam("TokenScript")) { if (!sa.hasParam("TokenScript")) {
throw new RuntimeException("Spell Ability has no TokenScript: " + sa); 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) { if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript")); throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));

View File

@@ -142,6 +142,7 @@ public enum ApiType {
ReplaceEffect (ReplaceEffect.class), ReplaceEffect (ReplaceEffect.class),
ReplaceMana (ReplaceManaEffect.class), ReplaceMana (ReplaceManaEffect.class),
ReplaceDamage (ReplaceDamageEffect.class), ReplaceDamage (ReplaceDamageEffect.class),
ReplaceToken (ReplaceTokenEffect.class),
ReplaceSplitDamage (ReplaceSplitDamageEffect.class), ReplaceSplitDamage (ReplaceSplitDamageEffect.class),
RestartGame (RestartGameEffect.class), RestartGame (RestartGameEffect.class),
Reveal (RevealEffect.class), Reveal (RevealEffect.class),

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player; 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 int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final boolean remember = sa.hasParam("RememberAmass"); 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 // create army token if needed
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) { 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()); game.fireEvent(new GameEventTokenCreated());
if (combatChanged.isTrue()) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
} }
if (combatChanged.isTrue()) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
Map<String, Object> params = Maps.newHashMap(); Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", CounterType.get(CounterEnumType.P1P1)); params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
params.put("Amount", 1); params.put("Amount", 1);

View File

@@ -20,6 +20,7 @@ import forge.game.card.CardFactory;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.TokenCreateTable;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -177,15 +178,18 @@ public class CopyPermanentEffect extends TokenEffectBase {
} }
MutableBoolean combatChanged = new MutableBoolean(false); MutableBoolean combatChanged = new MutableBoolean(false);
TokenCreateTable tokenTable = new TokenCreateTable();
for (final Card c : tgtCards) { for (final Card c : tgtCards) {
// if it only targets player, it already got all needed cards from defined // if it only targets player, it already got all needed cards from defined
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) { if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
continue; continue;
} }
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged);
} // end foreach Card } // end foreach Card
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
@@ -196,9 +200,8 @@ public class CopyPermanentEffect extends TokenEffectBase {
} }
} // end resolve } // 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 Card host = sa.getHostCard();
final Player newOwner = sa.getActivatingPlayer();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final Card copy = new Card(id, original.getPaperCard(), host.getGame()); final Card copy = new Card(id, original.getPaperCard(), host.getGame());
copy.setOwner(newOwner); copy.setOwner(newOwner);

View File

@@ -6,7 +6,6 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player; 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 int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final String tokenScript = "c_a_clue_draw"; // Investigate in Sequence
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
CardZoneTable triggerList = new CardZoneTable(); CardZoneTable triggerList = new CardZoneTable();
MutableBoolean combatChanged = new MutableBoolean(false); 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); triggerList.triggerChangesZoneAll(game, sa);
p.addInvestigatedThisTurn(); p.addInvestigatedThisTurn();

View File

@@ -3,12 +3,6 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map; 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.GameObject;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -17,25 +11,21 @@ import forge.game.card.Card;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
public class ReplaceEffect extends SpellAbilityEffect { public class ReplaceEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = card.getGame();
final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName")); final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName"));
final String varValue = sa.getParam("VarValue"); final String varValue = sa.getParam("VarValue");
final String type = sa.getParamOrDefault("VarType", "amount"); final String type = sa.getParamOrDefault("VarType", "amount");
final ReplacementType retype = sa.getReplacementEffect().getMode();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams); Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
if ("Card".equals(type)) { if ("Card".equals(type)) {
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa); List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
@@ -53,7 +43,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(varName, list.get(0)); params.put(varName, list.get(0));
} }
} else if ("TokenScript".equals(type)) { } else if ("TokenScript".equals(type)) {
final Card protoType = TokenInfo.getProtoType(varValue, sa); final Card protoType = TokenInfo.getProtoType(varValue, sa, sa.getActivatingPlayer());
if (protoType != null) { if (protoType != null) {
params.put(varName, protoType); params.put(varName, protoType);
} }
@@ -61,42 +51,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa)); params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
} }
if (params.containsKey(AbilityKey.EffectOnly)) { params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
params.put(AbilityKey.EffectOnly, true);
}
if (retype == ReplacementType.DamageDone) {
for (Map.Entry<AbilityKey, Object> 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<AbilityKey, Object> 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;
}
} }
} }

View File

@@ -4,20 +4,15 @@ import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
public class ReplaceManaEffect extends SpellAbilityEffect { public class ReplaceManaEffect extends SpellAbilityEffect {
@@ -25,17 +20,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Player player = sa.getActivatingPlayer(); final Player player = sa.getActivatingPlayer();
final Game game = card.getGame();
// outside of Replacement Effect, unwanted result // outside of Replacement Effect, unwanted result
if (!sa.isReplacementAbility()) { if (!sa.isReplacementAbility()) {
return; return;
} }
final ReplacementType event = sa.getReplacementEffect().getMode();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams); Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
String replaced = (String)sa.getReplacingObject(AbilityKey.Mana); String replaced = (String)sa.getReplacingObject(AbilityKey.Mana);
if (sa.hasParam("ReplaceMana")) { if (sa.hasParam("ReplaceMana")) {
@@ -79,31 +71,8 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount"))); replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount")));
} }
params.put(AbilityKey.Mana, replaced); params.put(AbilityKey.Mana, replaced);
// effect was updated
// need to log Updated events there, or the log is wrong order params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
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<AbilityKey, Object> 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;
}
} }
} }

View File

@@ -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<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
.getReplacingObject(AbilityKey.OriginalParams);
// currently the only ones that changes the amount does double it
if ("Amount".equals(sa.getParam("Type"))) {
for (Map.Entry<Card, Integer> 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<Player, Integer> byController = Maps.newHashMap();
for (Map.Entry<Card, Integer> 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<Player, Integer> e : byController.entrySet()) {
e.setValue(i);
}
}
for (Map.Entry<Player, Integer> 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<Player, Integer> toInsertMap = Maps.newHashMap();
Set<Card> toRemoveSet = Sets.newHashSet();
for (Map.Entry<Card, Integer> 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<Player, Integer> 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<Card, Integer> 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);
}
}

View File

@@ -23,10 +23,8 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class TokenEffect extends TokenEffectBase { public class TokenEffect extends TokenEffectBase {
@@ -36,20 +34,6 @@ public class TokenEffect extends TokenEffectBase {
return sa.getDescription(); 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 @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); 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); final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa);
MutableBoolean combatChanged = new MutableBoolean(false);
boolean useZoneTable = true; boolean useZoneTable = true;
CardZoneTable triggerList = sa.getChangeZoneTable(); CardZoneTable triggerList = sa.getChangeZoneTable();
@@ -82,10 +65,8 @@ public class TokenEffect extends TokenEffectBase {
useZoneTable = true; useZoneTable = true;
} }
MutableBoolean combatChanged = new MutableBoolean(false); makeTokenTable(AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa),
for (final Player owner : AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa)) { sa.getParam("TokenScript").split(","), finalAmount, false, triggerList, combatChanged, sa);
makeTokens(prototype, owner, sa, finalAmount, true, false, triggerList, combatChanged);
}
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);

View File

@@ -2,123 +2,202 @@ package forge.game.ability.effects;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableBoolean;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardFactory;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.TokenCreateTable;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public abstract class TokenEffectBase extends SpellAbilityEffect { public abstract class TokenEffectBase extends SpellAbilityEffect {
protected List<Card> makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount, protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) {
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<Player> 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 Card host = sa.getHostCard();
final Game game = host.getGame(); final Game game = host.getGame();
final long timestamp = game.getNextTimestamp(); long timestamp = game.getNextTimestamp();
// support PlayerCollection for affected
Set<Player> toRemove = Sets.newHashSet();
for (Player p : tokenTable.rowKeySet()) {
final Map<AbilityKey, Object> 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<String> pumpKeywords = Lists.newArrayList(); final List<String> pumpKeywords = Lists.newArrayList();
if (sa.hasParam("PumpKeywords")) { if (sa.hasParam("PumpKeywords")) {
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
} }
List<Card> allTokens = Lists.newArrayList(); List<Card> allTokens = Lists.newArrayList();
for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) { for (final Table.Cell<Player, Card, Integer> c : tokenTable.cellSet()) {
if (sa.hasParam("TokenTapped")) { Card prototype = c.getColumnKey();
tok.setTapped(true); Player creator = c.getRowKey();
} Player controller = prototype.getController();
int cellAmount = c.getValue();
if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) { for (int i = 0; i < cellAmount; i++) {
continue; 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
if (sa.hasParam("WithCounters")) { tok.setOwner(creator);
String[] parse = sa.getParam("WithCounters").split("_"); if (creator != controller) {
tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator); tok.setController(controller, timestamp);
}
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);
} }
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")) { if (sa.hasParam("AtEOT")) {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens); registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
} }
return allTokens; return tokenTable;
} }
private boolean attachTokenTo(Card tok, SpellAbility sa) { private boolean attachTokenTo(Card tok, SpellAbility sa) {

View File

@@ -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<Player, Card, Integer> {
Table<Player, Card, Integer> dataMap = HashBasedTable.create();
public TokenCreateTable() {
}
@Override
protected Table<Player, Card, Integer> 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<Card> filteredCards = null;
List<Player> 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<Card, Map<Player, Integer>> e : columnMap().entrySet()) {
for (Integer i : e.getValue().values()) {
result += i;
}
}
return result;
}
if (filteredCards == null) {
for (Map.Entry<Player, Map<Card, Integer>> e : rowMap().entrySet()) {
for (Integer i : e.getValue().values()) {
result += i;
}
}
return result;
}
for (Table.Cell<Player, Card, Integer> c : this.cellSet()) {
if (!filteredPlayer.contains(c.getRowKey())) {
continue;
}
if (!filteredCards.contains(c.getColumnKey())) {
continue;
}
result += c.getValue();
}
return result;
}
}

View File

@@ -15,15 +15,12 @@ import forge.StaticData;
import forge.card.CardType; import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.item.PaperToken; import forge.item.PaperToken;
@@ -140,59 +137,6 @@ public class TokenInfo {
return sb.toString(); return sb.toString();
} }
public static List<Card> makeToken(final Card prototype, final Player owner,
final boolean applyMultiplier, final int num) {
final List<Card> list = Lists.newArrayList();
final Game game = owner.getGame();
int multiplier = num;
Player player = owner;
Card proto = prototype;
final Map<AbilityKey, Object> 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<Card> makeTokensFromPrototype(Card prototype, final Player owner, int amount, final boolean applyMultiplier) {
return makeToken(prototype, owner, applyMultiplier, amount);
}
public Card makeOneToken(final Player controller) { public Card makeOneToken(final Player controller) {
final Game game = controller.getGame(); final Game game = controller.getGame();
final Card c = toCard(game); final Card c = toCard(game);
@@ -321,10 +265,10 @@ public class TokenInfo {
result.getCurrentState().changeTextIntrinsic(colorMap, typeMap); result.getCurrentState().changeTextIntrinsic(colorMap, typeMap);
} }
static public Card getProtoType(final String script, final SpellAbility sa) { static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) {
return getProtoType(script, sa, true); 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 // script might be null, or sa might be null
if (script == null || sa == null) { if (script == null || sa == null) {
return null; return null;
@@ -338,7 +282,7 @@ public class TokenInfo {
if (token == null) { if (token == null) {
return null; return null;
} }
final Card result = Card.fromPaperCard(token, null, game); final Card result = Card.fromPaperCard(token, owner, game);
if (sa.hasParam("TokenPower")) { if (sa.hasParam("TokenPower")) {
String str = sa.getParam("TokenPower"); String str = sa.getParam("TokenPower");

View File

@@ -4,6 +4,7 @@ import java.util.Map;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.TokenCreateTable;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
@@ -27,9 +28,11 @@ public class ReplaceToken extends ReplacementEffect {
*/ */
@Override @Override
public boolean canReplace(Map<AbilityKey, Object> runParams) { public boolean canReplace(Map<AbilityKey, Object> runParams) {
/*
if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) { if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) {
return false; return false;
} }
//*/
if (hasParam("EffectOnly")) { if (hasParam("EffectOnly")) {
final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.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))) { if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
return false; return false;
} }
/*/
if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) { if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) {
return false; return false;
} }
//*/
if (filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)) <= 0) {
return false;
}
return true; return true;
} }
@@ -54,8 +63,13 @@ public class ReplaceToken extends ReplacementEffect {
*/ */
@Override @Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) { public void setReplacingObjects(Map<AbilityKey, Object> 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)); sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
} }
public int filterAmount(final TokenCreateTable table) {
return table.getFilterAmount(getParamOrDefault("ValidPlayer", null), getParamOrDefault("ValidToken", null), this);
}
} }

View File

@@ -28,6 +28,7 @@ import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
@@ -274,20 +275,45 @@ public class ReplacementHandler {
chosenRE.setOtherChoices(null); chosenRE.setOtherChoices(null);
return res; 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<AbilityKey, Object> 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<AbilityKey, Object> 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); chosenRE.setHasRun(false);
hasRun.remove(chosenRE); hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null); 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; return res;
} }
@@ -300,7 +326,6 @@ public class ReplacementHandler {
*/ */
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams, private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
final ReplacementEffect replacementEffect, final Player decider, final Game game) { final ReplacementEffect replacementEffect, final Player decider, final Game game) {
final Map<String, String> mapParams = replacementEffect.getMapParams();
SpellAbility effectSA = null; SpellAbility effectSA = null;
@@ -311,10 +336,9 @@ public class ReplacementHandler {
host = game.getCardState(host); host = game.getCardState(host);
} }
if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) { if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) {
final String effectSVar = mapParams.get("ReplaceWith");
// TODO: the source of replacement effect should be the source of the original effect // 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); //replacementEffect.setOverridingAbility(effectSA);
//effectSA.setTrigger(true); //effectSA.setTrigger(true);
} else if (replacementEffect.getOverridingAbility() != null) { } else if (replacementEffect.getOverridingAbility() != null) {
@@ -342,10 +366,10 @@ public class ReplacementHandler {
// Decider gets to choose whether or not to apply the replacement. // Decider gets to choose whether or not to apply the replacement.
if (replacementEffect.hasParam("Optional")) { if (replacementEffect.hasParam("Optional")) {
Player optDecider = decider; Player optDecider = decider;
if (mapParams.containsKey("OptionalDecider") && (effectSA != null)) { if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) {
effectSA.setActivatingPlayer(host.getController()); effectSA.setActivatingPlayer(host.getController());
optDecider = AbilityUtils.getDefinedPlayers(host, optDecider = AbilityUtils.getDefinedPlayers(host,
mapParams.get("OptionalDecider"), effectSA).get(0); replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
} }
Card cardForUi = host.getCardForUi(); Card cardForUi = host.getCardForUi();
@@ -360,12 +384,12 @@ public class ReplacementHandler {
} }
} }
boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True"); boolean isPrevent = "True".equals(replacementEffect.getParam("Prevent"));
if (isPrevent || mapParams.containsKey("PreventionEffect")) { if (isPrevent || replacementEffect.hasParam("PreventionEffect")) {
if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) { if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) {
// If can't prevent damage, result is not replaced // If can't prevent damage, result is not replaced
// But still put "prevented" amount for buffered SA // But still put "prevented" amount for buffered SA
if (mapParams.containsKey("AlwaysReplace")) { if (replacementEffect.hasParam("AlwaysReplace")) {
runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount)); runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount));
} else { } else {
runParams.put(AbilityKey.PreventedAmount, 0); runParams.put(AbilityKey.PreventedAmount, 0);
@@ -377,10 +401,8 @@ public class ReplacementHandler {
} }
} }
if (mapParams.containsKey("Skip")) { if ("True".equals(replacementEffect.getParam("Skip"))) {
if (mapParams.get("Skip").equals("True")) { return ReplacementResult.Skipped; // Event is skipped.
return ReplacementResult.Skipped; // Event is skipped.
}
} }
Player player = host.getController(); Player player = host.getController();
@@ -394,6 +416,11 @@ public class ReplacementHandler {
// The SA if buffered, but replacement result should be set to Replaced // The SA if buffered, but replacement result should be set to Replaced
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.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 // if the spellability is a replace effect then its some new logic

View File

@@ -2,11 +2,8 @@ Name:Academy Manufactor
ManaCost:3 ManaCost:3
Types:Artifact Creature Assembly-Worker Types:Artifact Creature Assembly-Worker
PT:1/3 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. 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:DBToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenAmount$ X | SubAbility$ DBToken2 SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Clue,Food,Treasure | TokenScript$ c_a_clue_draw,c_a_food_sac,c_a_treasure_sac
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
DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain
DeckHints:Ability$Investigate DeckHints:Ability$Investigate
Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each. Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each.

View File

@@ -3,8 +3,7 @@ ManaCost:2 G U
Types:Legendary Creature Merfolk Wizard Types:Legendary Creature Merfolk Wizard
PT:2/2 PT:2/2
K:Ward: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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ X SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl
SVar:X:ReplaceCount$TokenNum/Twice
DeckHints:Ability$Token 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. 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.

View File

@@ -1,9 +1,7 @@
Name:Anointed Procession Name:Anointed Procession
ManaCost:3 W ManaCost:3 W
Types:Enchantment 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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ X SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl
SVar:X:ReplaceCount$TokenNum/Twice
DeckNeeds:Ability$Token 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. Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead.

View File

@@ -1,9 +1,5 @@
Name:Bestial Menace Name:Bestial Menace
ManaCost:3 G G ManaCost:3 G G
Types:Sorcery 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, 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.
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
Oracle: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.

View File

@@ -3,12 +3,10 @@ ManaCost:2 G
Types:Legendary Creature Squirrel Warrior Types:Legendary Creature Squirrel Warrior
PT:3/3 PT:3/3
K:Forestwalk 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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ Y | SubAbility$ DBToken SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | ValidCard$ Card.YouCtrl | TokenScript$ g_1_1_squirrel
SVar:DBToken:DB$ Token | TokenAmount$ Y | TokenScript$ g_1_1_squirrel
A:AB$ Pump | Cost$ B Sac<X/Squirrel> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +X | NumDef$ -X | SpellDescription$ Target creature gets +X/-X until end of turn. A:AB$ Pump | Cost$ B Sac<X/Squirrel> | 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:X:Count$xPaid
SVar:Y:ReplaceCount$TokenNum
DeckHas:Ability$Token DeckHas:Ability$Token
DeckHints:Type$Squirrel 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. 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.

View File

@@ -5,7 +5,6 @@ PT:2/2
K:Flash 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. 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: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: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$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player SVar:ETBYourCtrl:DB$ ReplaceToken | Type$ ReplaceController | ValidCard$ Card.OppCtrl | NewController$ You
SVar:Picture:http://www.wizards.com/global/images/magic/general/crafty_cutpurse.jpg
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. 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.

View File

@@ -1,7 +1,7 @@
Name:Divine Visitation Name:Divine Visitation
ManaCost:3 W W ManaCost:3 W W
Types:Enchantment 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. 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$ ReplaceEffect | VarName$ Token | VarValue$ w_4_4_angel_flying_vigilance | VarType$ TokenScript SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Creature.YouCtrl | TokenScript$ w_4_4_angel_flying_vigilance
DeckNeeds:Ability$Token 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. 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.

View File

@@ -1,11 +1,9 @@
Name:Doubling Season Name:Doubling Season
ManaCost:4 G ManaCost:4 G
Types:Enchantment 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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ X 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. 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:DoubleCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Y
SVar:X:ReplaceCount$TokenNum/Twice
SVar:Y:ReplaceCount$CounterNum/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. 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.

View File

@@ -1,8 +1,6 @@
Name:Forbidden Friendship Name:Forbidden Friendship
ManaCost:1 R ManaCost:1 R
Types:Sorcery 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. 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.
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
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token. Oracle:Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token.

View File

@@ -1,9 +1,6 @@
Name:Mascot Exhibition Name:Mascot Exhibition
ManaCost:7 ManaCost:7
Types:Sorcery Lesson 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, 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.
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
DeckHas:Ability$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. 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.

View File

@@ -1,8 +1,6 @@
Name:Parallel Lives Name:Parallel Lives
ManaCost:3 G ManaCost:3 G
Types:Enchantment 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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ X SVar:DoubleToken:DB$ ReplaceToken | Type$ Amount | ValidCard$ Card.YouCtrl
SVar:X:ReplaceCount$TokenNum/Twice
SVar:Picture:http://www.wizards.com/global/images/magic/general/parallel_lives.jpg
Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. Oracle:If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead.

View File

@@ -1,11 +1,9 @@
Name:Primal Vigor Name:Primal Vigor
ManaCost:4 G ManaCost:4 G
Types:Enchantment 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. 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$ ReplaceEffect | VarName$ TokenNum | VarValue$ X 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. 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:DoubleP1P1Counters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Y
SVar:X:ReplaceCount$TokenNum/Twice
SVar:Y:ReplaceCount$CounterNum/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. 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.

View File

@@ -2,10 +2,9 @@ Name:Selesnya Loft Gardens
ManaCost:no cost ManaCost:no cost
Types:Plane Ravnica 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. 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. 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:DoubleCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Z
SVar:Y:ReplaceCount$TokenNum/Twice
SVar:Z:ReplaceCount$CounterNum/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. 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:RolledChaos:DB$ Effect | AILogic$ Always | Triggers$ TrigTapForMana

View File

@@ -3,9 +3,7 @@ ManaCost:4 U
Types:Creature Vedalken Wizard Types:Creature Vedalken Wizard
PT:2/1 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. 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:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel,u_0_3_crab | TokenOwner$ You
SVar:DBCrabToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_0_3_crab | TokenOwner$ You | SubAbility$ DBResolve
SVar:DBResolve:DB$ ChangeZoneResolve
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. 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 SVar:TrigCopy:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl | TgtPrompt$ Select target token you control
DeckHas:Ability$Token DeckHas:Ability$Token

View File

@@ -5,10 +5,7 @@ PT:9/9
K:Flying K:Flying
K:Vigilance K:Vigilance
K:Trample 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. 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:TrigTokenFly:DB$Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem_flying | ChangeZoneTable$ True | SubAbility$ DBTokenVig 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
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
DeckHas:Ability$Token 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. 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.

View File

@@ -2,9 +2,8 @@ Name:Xorn
ManaCost:2 R ManaCost:2 R
Types:Creature Elemental Types:Creature Elemental
PT:3/2 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. 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:AdditionalToken:DB$ ReplaceEffect | VarName$ TokenNum | VarValue$ X SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | Amount$ 1 | TokenScript$ c_a_treasure_sac
SVar:X:ReplaceCount$TokenNum/Plus.1
DeckNeeds:Type$Token DeckNeeds:Type$Token
AI:RemoveDeck:Random AI:RemoveDeck:Random
Oracle:If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token. Oracle:If you would create one or more Treasure tokens, instead create those tokens plus an additional Treasure token.

View File

@@ -1324,7 +1324,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
} }
if (sa.hasParam("TokenScript")) { if (sa.hasParam("TokenScript")) {
sa.setActivatingPlayer(player); 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()) { for (String type : protoType.getType().getCreatureTypes()) {
Integer count = typesInDeck.get(type); Integer count = typesInDeck.get(type);
if (count == null) { if (count == null) {
@@ -1340,7 +1340,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (sa != null) { if (sa != null) {
if (sa.hasParam("TokenScript")) { if (sa.hasParam("TokenScript")) {
sa.setActivatingPlayer(player); 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()) { for (String type : protoType.getType().getCreatureTypes()) {
Integer count = typesInDeck.get(type); Integer count = typesInDeck.get(type);
if (count == null) { if (count == null) {