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.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)

View File

@@ -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;

View File

@@ -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"));

View File

@@ -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),

View File

@@ -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);
if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear();
}
game.fireEvent(new GameEventTokenCreated());
}
if (combatChanged.isTrue()) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
}
Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
params.put("Amount", 1);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
if ("Card".equals(type)) {
List<Card> 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<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;
}
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
}
}

View File

@@ -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<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) 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<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;
}
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
}
}

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.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);

View File

@@ -2,45 +2,124 @@ 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<Card> makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount,
boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) {
protected TokenCreateTable createTokenTable(Iterable<Player> 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<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 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();
if (sa.hasParam("PumpKeywords")) {
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
}
List<Card> allTokens = Lists.newArrayList();
for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) {
for (final Table.Cell<Player, Card, Integer> 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);
}
@@ -48,7 +127,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
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);
@@ -65,29 +143,29 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
}
// Should this be catching the Card that's returned?
Card c = game.getAction().moveToPlay(tok, sa);
if (c == null || c.getZone() == null) {
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, c);
triggerList.put(ZoneType.None, ZoneType.None, moved);
continue;
}
triggerList.put(ZoneType.None, c.getZone().getZoneType(), c);
triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
creator.addTokensCreatedThisTurn();
if (clone) {
c.setCloneOrigin(host);
moved.setCloneOrigin(host);
}
if (!pumpKeywords.isEmpty()) {
c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
addPumpUntil(sa, c, timestamp);
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
addPumpUntil(sa, moved, timestamp);
}
if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
}
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue();
}
@@ -95,30 +173,31 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
attachTokenTo(tok, sa);
}
c.updateStateForView();
moved.updateStateForView();
if (sa.hasParam("RememberTokens")) {
host.addRemembered(c);
host.addRemembered(moved);
}
if (sa.hasParam("ImprintTokens")) {
host.addImprintedCard(c);
host.addImprintedCard(moved);
}
if (sa.hasParam("RememberSource")) {
c.addRemembered(host);
moved.addRemembered(host);
}
if (sa.hasParam("TokenRemembered")) {
final String remembered = sa.getParam("TokenRemembered");
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
c.addRemembered(o);
moved.addRemembered(o);
}
}
allTokens.add(c);
allTokens.add(moved);
}
}
if (sa.hasParam("AtEOT")) {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
}
return allTokens;
return tokenTable;
}
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.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<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) {
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");

View File

@@ -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<AbilityKey, Object> 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<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));
}
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 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;
}
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) {
// Log there
String message = chosenRE.getDescription();
if (!StringUtils.isEmpty(message))
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);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
return res;
}
@@ -300,7 +326,6 @@ public class ReplacementHandler {
*/
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
final ReplacementEffect replacementEffect, final Player decider, final Game game) {
final Map<String, String> 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,11 +401,9 @@ public class ReplacementHandler {
}
}
if (mapParams.containsKey("Skip")) {
if (mapParams.get("Skip").equals("True")) {
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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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<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: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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -2,10 +2,9 @@ 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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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) {