diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
index ec86828e6d8..323d77d8854 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
@@ -8,6 +8,7 @@ import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
+import forge.game.ability.effects.TokenEffect;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
@@ -42,14 +43,11 @@ import java.util.List;
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
*/
public class TokenAi extends SpellAbilityAi {
-
-
private String tokenAmount;
- private String tokenName;
- private String[] tokenTypes;
- private String[] tokenKeywords;
private String tokenPower;
private String tokenToughness;
+
+ private Card actualToken;
/**
*
* Constructor for AbilityFactory_Token.
@@ -58,23 +56,28 @@ public class TokenAi extends SpellAbilityAi {
* a {@link forge.game.ability.AbilityFactory} object.
*/
private void readParameters(final SpellAbility mapParams) {
- String[] keywords;
-
- if (mapParams.hasParam("TokenKeywords")) {
- // TODO: Change this Split to a semicolon or something else
- keywords = mapParams.getParam("TokenKeywords").split("<>");
- } else {
- keywords = new String[0];
- }
-
-
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
- this.tokenPower = mapParams.getParam("TokenPower");
- this.tokenToughness = mapParams.getParam("TokenToughness");
- this.tokenName = mapParams.getParam("TokenName");
- this.tokenTypes = mapParams.getParam("TokenTypes").split(",");
- this.tokenKeywords = keywords;
+ TokenEffect effect = new TokenEffect();
+
+ this.actualToken = effect.loadTokenPrototype(mapParams);
+
+ if (actualToken == null) {
+ String[] keywords;
+
+ if (mapParams.hasParam("TokenKeywords")) {
+ // TODO: Change this Split to a semicolon or something else
+ keywords = mapParams.getParam("TokenKeywords").split("<>");
+ } else {
+ keywords = new String[0];
+ }
+
+ this.tokenPower = mapParams.getParam("TokenPower");
+ this.tokenToughness = mapParams.getParam("TokenToughness");
+ } else {
+ this.tokenPower = actualToken.getBasePowerString();
+ this.tokenToughness = actualToken.getBaseToughnessString();
+ }
}
@Override
@@ -103,8 +106,11 @@ public class TokenAi extends SpellAbilityAi {
}
}
- final Card token = spawnToken(ai, sa);
- if (token == null) {
+ if (actualToken == null) {
+ actualToken = spawnToken(ai, sa);
+ }
+
+ if (actualToken == null) {
final AbilitySub sub = sa.getSubAbility();
if (pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai))) {
return true; // planeswalker plus ability or sub-ability is
@@ -130,24 +136,21 @@ public class TokenAi extends SpellAbilityAi {
}
}
- if (canInterruptSacrifice(ai, sa, token)) {
+ if (canInterruptSacrifice(ai, sa, actualToken)) {
return true;
}
- boolean haste = false;
+ boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
boolean oneShot = sa.getSubAbility() != null
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
- for (final String kw : this.tokenKeywords) {
- if (kw.equals("Haste")) {
- haste = true;
- }
- }
+ boolean isCreature = this.actualToken.getType().isCreature();
+
// Don't generate tokens without haste before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
boolean buff = false;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
- if ("Creature".equals(c.getSVar("BuffedBy"))) {
+ if (isCreature && "Creature".equals(c.getSVar("BuffedBy"))) {
buff = true;
}
}
@@ -180,12 +183,9 @@ public class TokenAi extends SpellAbilityAi {
}
// Don't kill AIs Legendary tokens
- for (final String type : this.tokenTypes) {
- if (type.equals("Legendary")) {
- if (ai.isCardInPlay(this.tokenName)) {
- return false;
- }
- }
+ if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
+ // TODO Check if Token is useless due to an aura or counters?
+ return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -388,6 +388,7 @@ public class TokenAi extends SpellAbilityAi {
* @param notNull if the token would not survive, still return it
* @return token creature created by ability
*/
+ // TODO Is this just completely copied from TokenEffect? Let's just call that thing
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
final Card host = sa.getHostCard();
diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java
index 022a915fdb8..d9afaef296b 100644
--- a/forge-core/src/main/java/forge/card/CardEdition.java
+++ b/forge-core/src/main/java/forge/card/CardEdition.java
@@ -123,12 +123,19 @@ public final class CardEdition implements Comparable { // immutable
private boolean smallSetOverride = false;
private String boosterMustContain = "";
private final CardInSet[] cards;
+ private final String[] tokenNormalized;
private int boosterArts = 1;
private SealedProduct.Template boosterTpl = null;
private CardEdition(CardInSet[] cards) {
this.cards = cards;
+ tokenNormalized = null;
+ }
+
+ private CardEdition(CardInSet[] cards, String[] tokens) {
+ this.cards = cards;
+ this.tokenNormalized = tokens;
}
/**
@@ -254,6 +261,7 @@ public final class CardEdition implements Comparable { // immutable
protected CardEdition read(File file) {
final Map> contents = FileSection.parseSections(FileUtil.readFile(file));
+ List tokenNormalized = new ArrayList<>();
List processedCards = new ArrayList<>();
if (contents.containsKey("cards")) {
for(String line : contents.get("cards")) {
@@ -277,7 +285,19 @@ public final class CardEdition implements Comparable { // immutable
}
}
- CardEdition res = new CardEdition(processedCards.toArray(new CardInSet[processedCards.size()]));
+ if (contents.containsKey("tokens")) {
+ for(String line : contents.get("tokens")) {
+ if (StringUtils.isBlank(line))
+ continue;
+
+ tokenNormalized.add(line);
+ }
+ }
+
+ CardEdition res = new CardEdition(
+ processedCards.toArray(new CardInSet[processedCards.size()]),
+ tokenNormalized.toArray(new String[tokenNormalized.size()])
+ );
FileSection section = FileSection.parse(contents.get("metadata"), "=");
res.name = section.get("name");
diff --git a/forge-core/src/main/java/forge/token/TokenDb.java b/forge-core/src/main/java/forge/token/TokenDb.java
index d3fe2aac9ce..f4275e4c86b 100644
--- a/forge-core/src/main/java/forge/token/TokenDb.java
+++ b/forge-core/src/main/java/forge/token/TokenDb.java
@@ -39,13 +39,19 @@ public class TokenDb implements ITokenDatabase {
@Override
public PaperToken getToken(String tokenName, String edition) {
- try {
- PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
- // TODO Cache the token after it's referenced
- return pt;
- } catch(Exception e) {
- return null;
+ String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
+
+ if (!tokensByName.containsKey(fullName)) {
+ try {
+ PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
+ tokensByName.put(fullName, pt);
+ return pt;
+ } catch(Exception e) {
+ return null;
+ }
}
+
+ return tokensByName.get(fullName);
}
@Override
diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
index 6a3ee323531..6abd7bdebc1 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java
@@ -199,17 +199,15 @@ public class TokenEffect extends SpellAbilityEffect {
return sb.toString();
}
- private Card loadTokenPrototype(SpellAbility sa) {
- String script = sa.getParamOrDefault("TokenScript", null);
-
- PaperToken token = null;
- try {
- String edition = sa.getHostCard().getPaperCard().getEdition();
- token = StaticData.instance().getAllTokens().getToken(script, edition);
- } catch(NullPointerException e) {
- // A non-PaperCard creates a new token. We probably want to delegate to the original creator
- System.out.println("Token created by: " + sa.getHostCard() + " has no PaperCard associated to it.");
+ public Card loadTokenPrototype(SpellAbility sa) {
+ if (!sa.hasParam("TokenScript")) {
+ return null;
}
+
+ String script = sa.getParam("TokenScript");
+ String edition = sa.getHostCard().getSetCode();
+ PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition);
+
if (token != null) {
tokenName = token.getName();
return Card.fromPaperCard(token, null, sa.getHostCard().getGame());
@@ -275,7 +273,10 @@ public class TokenEffect extends SpellAbilityEffect {
tokenInfo = new TokenInfo(substitutedName, imageName,
cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness);
} else {
- tokenInfo = new TokenInfo(prototype);
+ // TODO: Substitute type name for Chosen tokens
+ // TODO: If host has has it's color/type altered make sure that's appropriately applied
+ // TODO: Lock down final power and toughness if it's actually X values
+ tokenInfo = new TokenInfo(prototype, host);
}
for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) {
diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
index 6b52497c9c7..b4219bcc592 100644
--- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
+++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
@@ -42,6 +42,7 @@ public class TokenInfo {
}
public TokenInfo(Card c) {
+ // TODO: Figure out how to handle legacy images?
this.name = c.getName();
this.imageName = ImageKeys.getTokenImageName(c.getImageKey());
this.manaCost = c.getManaCost().toString();
@@ -58,6 +59,12 @@ public class TokenInfo {
this.baseToughness = c.getBaseToughness();
}
+ public TokenInfo(Card c, Card source) {
+ // TODO If Source has type/color changes on it, apply them now.
+ // Permanently apply them for casccading tokens? Reef Worm?
+ this(c);
+ }
+
public TokenInfo(String str) {
final String[] tokenInfo = str.split(",");
int power = 0;
diff --git a/forge-gui-desktop/src/main/java/forge/ImageFetcher.java b/forge-gui-desktop/src/main/java/forge/ImageFetcher.java
index 09b6b2141cb..6b0d878b721 100644
--- a/forge-gui-desktop/src/main/java/forge/ImageFetcher.java
+++ b/forge-gui-desktop/src/main/java/forge/ImageFetcher.java
@@ -75,10 +75,10 @@ public class ImageFetcher {
}
}
final String filename = imageKey.substring(2) + ".jpg";
- final String tokenUrl = tokenImages.get(filename);
+ String tokenUrl = tokenImages.get(filename);
if (tokenUrl == null) {
- System.err.println("Token " + imageKey + " not found in: " + ForgeConstants.IMAGE_LIST_TOKENS_FILE);
- return;
+ System.err.println("No specified file.. Attempting to download from default Url");
+ tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename);
}
destFile = new File(ForgeConstants.CACHE_TOKEN_PICS_DIR, filename);
downloadUrls.add(tokenUrl);
diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java b/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java
index b5ce44df1cb..db8c95d195a 100644
--- a/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java
+++ b/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java
@@ -76,6 +76,8 @@ public class GuiDownloadSetPicturesLQ extends GuiDownloadService {
// Add missing tokens to the list of things to download.
addMissingItems(downloads, ForgeConstants.IMAGE_LIST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR);
+ // TODO Add TokenScript images via Editions files?
+
return downloads;
}