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