From d374da0481a7c9f7b48b4a4ad74bb1b6319d0c4a Mon Sep 17 00:00:00 2001 From: Chris H Date: Sat, 30 Dec 2017 21:55:20 -0500 Subject: [PATCH 1/3] Refactor TokenEffect to be easier to add in TokenScripts --- .../game/ability/effects/TokenEffect.java | 372 +++++++++--------- .../java/forge/game/card/token/TokenInfo.java | 6 +- 2 files changed, 196 insertions(+), 182 deletions(-) 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 c236500e02c..4e1dd62ef07 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 @@ -18,10 +18,12 @@ package forge.game.ability.effects; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import forge.card.MagicColor; import forge.game.card.token.TokenInfo; -import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Iterables; @@ -184,6 +186,7 @@ public class TokenEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); + final Game game = host.getGame(); final SpellAbility root = sa.getRootAbility(); readParameters(sa); @@ -194,30 +197,11 @@ public class TokenEffect extends SpellAbilityEffect { cause = (SpellAbility)root.getReplacingObject("Cause"); } - String cost = ""; - - // Construct original colors - String originalColorDesc = ""; - for (final String col : this.tokenOriginalColors) { - if (col.equalsIgnoreCase("White")) { - originalColorDesc += "W "; - } else if (col.equalsIgnoreCase("Blue")) { - originalColorDesc += "U "; - } else if (col.equalsIgnoreCase("Black")) { - originalColorDesc += "B "; - } else if (col.equalsIgnoreCase("Red")) { - originalColorDesc += "R "; - } else if (col.equalsIgnoreCase("Green")) { - originalColorDesc += "G "; - } else if (col.equalsIgnoreCase("Colorless")) { - originalColorDesc = "C"; - } - } + String originalColorDesc = parseColorForImage(); final List imageNames = Lists.newArrayListWithCapacity(1); if (this.tokenImage.equals("")) { - imageNames.add(PaperToken.makeTokenFileName(TextUtil.fastReplace(originalColorDesc, - " ", ""), tokenPower, tokenToughness, tokenOriginalName)); + imageNames.add(PaperToken.makeTokenFileName(originalColorDesc, tokenPower, tokenToughness, tokenOriginalName)); } else { imageNames.add(0, this.tokenImage); } @@ -225,34 +209,7 @@ public class TokenEffect extends SpellAbilityEffect { imageNames.addAll(Arrays.asList(this.tokenAltImages)); } - final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length); - for (int i = 0; i < substitutedColors.length; i++) { - if (substitutedColors[i].equals("ChosenColor")) { - // this currently only supports 1 chosen color - substitutedColors[i] = host.getChosenColor(); - } - } - String colorDesc = ""; - for (final String col : substitutedColors) { - if (col.equalsIgnoreCase("White")) { - colorDesc += "W "; - } else if (col.equalsIgnoreCase("Blue")) { - colorDesc += "U "; - } else if (col.equalsIgnoreCase("Black")) { - colorDesc += "B "; - } else if (col.equalsIgnoreCase("Red")) { - colorDesc += "R "; - } else if (col.equalsIgnoreCase("Green")) { - colorDesc += "G "; - } else if (col.equalsIgnoreCase("Colorless")) { - colorDesc = "C"; - } - } - for (final char c : colorDesc.toCharArray()) { - cost += c + ' '; - } - - cost = colorDesc.replace('C', '1').trim(); + String cost = determineTokenColor(host); final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); @@ -269,146 +226,203 @@ public class TokenEffect extends SpellAbilityEffect { final boolean remember = sa.hasParam("RememberTokens"); final boolean imprint = sa.hasParam("ImprintTokens"); final List allTokens = Lists.newArrayList(); + + boolean combatChanged = false; + boolean inCombat = game.getPhaseHandler().inCombat(); + for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) { - final Game game = controller.getGame(); - for (int i = 0; i < finalAmount; i++) { - final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); - final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName, - cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness); - final List tokens = tokenInfo.makeTokenWithMultiplier(controller, cause != null); - - // Grant rule changes - if (this.tokenHiddenKeywords != null) { - for (final String s : this.tokenHiddenKeywords) { - for (final Card c : tokens) { - c.addHiddenExtrinsicKeyword(s); - } - } + final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); + final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName, + cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness); + final List tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null); + + grantHiddenKeywords(tokens); + grantAbilities(tokens, root); + grantTriggers(tokens, root); + grantSvars(tokens, root); + grantStatics(tokens, root); + + for (Card tok : tokens) { + if (this.tokenTapped) { + tok.setTapped(true); + } + // Should this be catching the Card that's returned? + Card c = game.getAction().moveToPlay(tok, sa); + + if (sa.hasParam("AtEOTTrig")) { + addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c); } - // Grant SVars - if (this.tokenSVars != null) { - for (final String s : this.tokenSVars) { - String actualSVar = AbilityUtils.getSVar(root, s); - String name = s; - if (actualSVar.startsWith("SVar")) { - actualSVar = actualSVar.split("SVar:")[1]; - name = actualSVar.split(":")[0]; - actualSVar = actualSVar.split(":")[1]; - } - for (final Card c : tokens) { - c.setSVar(name, actualSVar); - } - } + if (inCombat) { + combatChanged = addTokenToCombat(game, c, controller, sa, host) || combatChanged; } - // Grant abilities - if (this.tokenAbilities != null) { - for (final String s : this.tokenAbilities) { - final String actualAbility = AbilityUtils.getSVar(root, s); - for (final Card c : tokens) { - final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); - // Set intrinsic, so that effects like Clone will copy these. - grantedAbility.setIntrinsic(true); - c.addSpellAbility(grantedAbility); - } + c.updateStateForView(); + + if (remember) { + game.getCardState(sa.getHostCard()).addRemembered(c); + } + if (imprint) { + game.getCardState(sa.getHostCard()).addImprintedCard(c); + } + if (sa.hasParam("RememberSource")) { + game.getCardState(c).addRemembered(host); + } + if (sa.hasParam("TokenRemembered")) { + final Card token = game.getCardState(c); + final String remembered = sa.getParam("TokenRemembered"); + for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { + token.addRemembered(o); } } - - // Grant triggers - if (this.tokenTriggers != null) { - for (final String s : this.tokenTriggers) { - final String actualTrigger = AbilityUtils.getSVar(root, s); - for (final Card c : tokens) { - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true); - final String ability = AbilityUtils.getSVar(root, parsedTrigger.getMapParams().get("Execute")); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c)); - c.addTrigger(parsedTrigger); - } - } - } - - // Grant static abilities - if (this.tokenStaticAbilities != null) { - for (final String s : this.tokenStaticAbilities) { - final String actualAbility = AbilityUtils.getSVar(root, s); - for (final Card c : tokens) { - c.addStaticAbility(actualAbility); - } - } - } - - for (Card tok : tokens) { - if (this.tokenTapped) { - tok.setTapped(true); - } - game.getAction().moveToPlay(tok, sa); - } - game.fireEvent(new GameEventTokenCreated()); - - boolean combatChanged = false; - for (final Card c : tokens) { - if (sa.hasParam("AtEOTTrig")) { - addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c); - } - // To show the Abilities and Trigger on the Token - c.updateStateForView(); - if (this.tokenAttacking && game.getPhaseHandler().inCombat()) { - final Combat combat = game.getCombat(); - - // into battlefield attacking only should work if you are the attacking player - if (combat.getAttackingPlayer().equals(controller)) { - final FCollectionView defs = combat.getDefenders(); - final GameEntity defender = controller.getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false); - combat.addAttacker(c, defender); - combatChanged = true; - } - } - if (this.tokenBlocking != null && game.getPhaseHandler().inCombat()) { - final Combat combat = game.getPhaseHandler().getCombat(); - final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null); - if (attacker != null) { - final boolean wasBlocked = combat.isBlocked(attacker); - combat.addBlocker(attacker, c); - combat.orderAttackersForDamageAssignment(c); - - // Run triggers for new blocker and add it to damage assignment order - if (!wasBlocked) { - combat.setBlocked(attacker, true); - combat.addBlockerToDamageAssignmentOrder(attacker, c); - } - combatChanged = true; - } - } - if (remember) { - game.getCardState(sa.getHostCard()).addRemembered(c); - } - if (imprint) { - game.getCardState(sa.getHostCard()).addImprintedCard(c); - } - if (sa.hasParam("RememberSource")) { - game.getCardState(c).addRemembered(host); - } - if (sa.hasParam("TokenRemembered")) { - final Card token = game.getCardState(c); - final String remembered = sa.getParam("TokenRemembered"); - for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { - token.addRemembered(o); - } - } - } - if (tokenName.equals("Clue")) { // investigate trigger - controller.addInvestigatedThisTurn(); - } - if (combatChanged) { - game.updateCombatForView(); - game.fireEvent(new GameEventCombatChanged()); - } - allTokens.addAll(tokens); + allTokens.add(c); } + if (tokenName.equals("Clue")) { // investigate trigger + controller.addInvestigatedThisTurn(); + } + } + + game.fireEvent(new GameEventTokenCreated()); + + if (combatChanged) { + game.updateCombatForView(); + game.fireEvent(new GameEventCombatChanged()); } if (sa.hasParam("AtEOT")) { registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens); } } + + private String parseColorForImage() { + String originalColorDesc = ""; + for (final String col : this.tokenOriginalColors) { + originalColorDesc += MagicColor.toShortString(col); + if (originalColorDesc.equals("C")) { + return originalColorDesc; + } + } + return originalColorDesc; + } + + private String determineTokenColor(Card host) { + Set colorSet = new HashSet<>(); + final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length); + for (int i = 0; i < substitutedColors.length; i++) { + if (substitutedColors[i].equals("ChosenColor")) { + // this currently only supports 1 chosen color + substitutedColors[i] = host.getChosenColor(); + } + } + + StringBuilder sb = new StringBuilder(); + for (final String col : substitutedColors) { + String str = MagicColor.toShortString(col); + if (str.equals("C")) { + return "1"; + } + + sb.append(str).append(" "); + } + + return sb.toString().trim(); + } + + private void grantHiddenKeywords(List tokens) { + // Grant rule changes + if (this.tokenHiddenKeywords != null) { + for (final String s : this.tokenHiddenKeywords) { + for (final Card c : tokens) { + c.addHiddenExtrinsicKeyword(s); + } + } + } + } + + private void grantAbilities(List tokens, SpellAbility root) { + if (this.tokenAbilities != null) { + for (final String s : this.tokenAbilities) { + final String actualAbility = AbilityUtils.getSVar(root, s); + for (final Card c : tokens) { + final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); + // Set intrinsic, so that effects like Clone will copy these. + grantedAbility.setIntrinsic(true); + c.addSpellAbility(grantedAbility); + } + } + } + } + + private void grantTriggers(List tokens, SpellAbility root) { + if (this.tokenTriggers != null) { + for (final String s : this.tokenTriggers) { + final String actualTrigger = AbilityUtils.getSVar(root, s); + for (final Card c : tokens) { + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true); + final String ability = AbilityUtils.getSVar(root, parsedTrigger.getMapParams().get("Execute")); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c)); + c.addTrigger(parsedTrigger); + } + } + } + } + + private void grantSvars(List tokens, SpellAbility root) { + if (this.tokenSVars != null) { + for (final String s : this.tokenSVars) { + String actualSVar = AbilityUtils.getSVar(root, s); + String name = s; + if (actualSVar.startsWith("SVar")) { + actualSVar = actualSVar.split("SVar:")[1]; + name = actualSVar.split(":")[0]; + actualSVar = actualSVar.split(":")[1]; + } + for (final Card c : tokens) { + c.setSVar(name, actualSVar); + } + } + } + } + + private void grantStatics(List tokens, SpellAbility root) { + if (this.tokenStaticAbilities != null) { + for (final String s : this.tokenStaticAbilities) { + final String actualAbility = AbilityUtils.getSVar(root, s); + for (final Card c : tokens) { + c.addStaticAbility(actualAbility); + } + } + } + } + + private boolean addTokenToCombat(Game game, Card c, Player controller, SpellAbility sa, Card host) { + boolean combatChanged = false; + if (this.tokenAttacking) { + final Combat combat = game.getCombat(); + + // into battlefield attacking only should work if you are the attacking player + if (combat.getAttackingPlayer().equals(controller)) { + final FCollectionView defs = combat.getDefenders(); + final GameEntity defender = controller.getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false); + combat.addAttacker(c, defender); + combatChanged = true; + } + } + if (this.tokenBlocking != null) { + final Combat combat = game.getPhaseHandler().getCombat(); + final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null); + if (attacker != null) { + final boolean wasBlocked = combat.isBlocked(attacker); + combat.addBlocker(attacker, c); + combat.orderAttackersForDamageAssignment(c); + + // Run triggers for new blocker and add it to damage assignment order + if (!wasBlocked) { + combat.setBlocked(attacker, true); + combat.addBlockerToDamageAssignmentOrder(attacker, c); + } + combatChanged = true; + } + } + return combatChanged; + } } 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 6a24f644d63..258788efd51 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 @@ -133,7 +133,7 @@ public class TokenInfo { return sb.toString(); } - public List makeTokenWithMultiplier(final Player controller, final boolean applyMultiplier) { + public List makeTokenWithMultiplier(final Player controller, int amount, final boolean applyMultiplier) { final List list = Lists.newArrayList(); final Game game = controller.getGame(); @@ -158,8 +158,8 @@ public class TokenInfo { return list; } - for (int i = 0; i < multiplier; i++) { - list.add(makeOneToken(player)); + for (int i = 0; i < multiplier * amount; i++) { + list.add(makeOneToken(controller)); } return list; } From 6ca1269261a49ef8bd163f873c708874b7762898 Mon Sep 17 00:00:00 2001 From: tehdiplomat Date: Mon, 19 Feb 2018 10:50:20 -0500 Subject: [PATCH 2/3] Add ability to load tokens via scripts before --- .../main/java/forge/CardStorageReader.java | 18 ++- .../src/main/java/forge/StaticData.java | 62 +++++--- .../src/main/java/forge/card/CardDb.java | 1 + .../src/main/java/forge/card/CardRarity.java | 1 + .../src/main/java/forge/card/CardRules.java | 21 ++- .../src/main/java/forge/item/PaperToken.java | 96 ++++++++++-- .../main/java/forge/token/ITokenDatabase.java | 32 ++++ .../src/main/java/forge/token/TokenDb.java | 121 +++++++++++++++ .../game/ability/effects/TokenEffect.java | 146 +++++++++++------- .../src/main/java/forge/game/card/Card.java | 3 + .../java/forge/game/card/token/TokenInfo.java | 34 +++- .../src/main/java/forge/model/FModel.java | 4 +- .../java/forge/properties/ForgeConstants.java | 1 + 13 files changed, 439 insertions(+), 101 deletions(-) create mode 100644 forge-core/src/main/java/forge/token/ITokenDatabase.java create mode 100644 forge-core/src/main/java/forge/token/TokenDb.java diff --git a/forge-core/src/main/java/forge/CardStorageReader.java b/forge-core/src/main/java/forge/CardStorageReader.java index 493c9a270a0..0f99bf6b87d 100644 --- a/forge-core/src/main/java/forge/CardStorageReader.java +++ b/forge-core/src/main/java/forge/CardStorageReader.java @@ -43,7 +43,7 @@ import java.util.zip.ZipFile; import forge.util.BuildInfo; import org.apache.commons.lang3.time.StopWatch; - +import com.google.common.io.Files; import forge.card.CardRules; import forge.util.FileUtil; import forge.util.Localizer; @@ -71,6 +71,7 @@ public class CardStorageReader { } private static final String CARD_FILE_DOT_EXTENSION = ".txt"; + private static final String UPCOMING = "upcoming"; /** Default charset when loading from files. */ public static final String DEFAULT_CHARSET_NAME = "UTF-8"; @@ -80,6 +81,7 @@ public class CardStorageReader { private final ProgressObserver progressObserver; + private final boolean loadingTokens; private transient File cardsfolder; private transient ZipFile zip; @@ -91,6 +93,9 @@ public class CardStorageReader { public CardStorageReader(final String cardDataDir, final CardStorageReader.ProgressObserver progressObserver, boolean loadCardsLazily) { this.progressObserver = progressObserver != null ? progressObserver : CardStorageReader.ProgressObserver.emptyObserver; this.cardsfolder = new File(cardDataDir); + + this.loadingTokens = cardDataDir.contains("token"); + this.loadCardsLazily = loadCardsLazily; // These read data for lightweight classes. @@ -240,6 +245,9 @@ public class CardStorageReader { final Set result = new TreeSet<>(new Comparator() { @Override public int compare(final CardRules o1, final CardRules o2) { + if (loadingTokens) { + return String.CASE_INSENSITIVE_ORDER.compare(o1.getNormalizedName(), o2.getNormalizedName()); + } return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()); } }); @@ -252,7 +260,7 @@ public class CardStorageReader { if (!allFiles.isEmpty()) { int fileParts = zip == null ? NUMBER_OF_PARTS : 1 + NUMBER_OF_PARTS / 3; if (allFiles.size() < fileParts * 100) { - fileParts = allFiles.size() / 100; // to avoid creation of many threads for a dozen of files + fileParts = Math.max(1, allFiles.size() / 100); // to avoid creation of many threads for a dozen of files } final CountDownLatch cdlFiles = new CountDownLatch(fileParts); final List>> taskFiles = makeTaskListForFiles(allFiles, cdlFiles); @@ -377,7 +385,7 @@ public class CardStorageReader { continue; } - if (filename.equalsIgnoreCase("upcoming") && !BuildInfo.isDevelopmentVersion()) { + if (filename.equalsIgnoreCase(CardStorageReader.UPCOMING) && !BuildInfo.isDevelopmentVersion()) { // If upcoming folder exits, only load these cards on development builds continue; } @@ -402,7 +410,7 @@ public class CardStorageReader { fileInputStream = new FileInputStream(file); reader.reset(); final List lines = readScript(fileInputStream); - return reader.readCard(lines); + return reader.readCard(lines, Files.getNameWithoutExtension(file.getName())); } catch (final FileNotFoundException ex) { throw new RuntimeException("CardReader : run error -- file not found: " + file.getPath(), ex); } finally { @@ -430,7 +438,7 @@ public class CardStorageReader { zipInputStream = this.zip.getInputStream(entry); rulesReader.reset(); - return rulesReader.readCard(readScript(zipInputStream)); + return rulesReader.readCard(readScript(zipInputStream), Files.getNameWithoutExtension(entry.getName())); } catch (final IOException exn) { throw new RuntimeException(exn); // PM diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index 157dd6ba897..5bf271be201 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -9,6 +9,7 @@ import forge.item.BoosterBox; import forge.item.FatPack; import forge.item.PaperCard; import forge.item.SealedProduct; +import forge.token.TokenDb; import forge.util.storage.IStorage; import forge.util.storage.StorageBase; @@ -22,10 +23,13 @@ import java.util.*; * @author Max */ public class StaticData { - private final CardStorageReader reader; + private final CardStorageReader cardReader; + private final CardStorageReader tokenReader; + private final String blockDataFolder; private final CardDb commonCards; private final CardDb variantCards; + private final TokenDb allTokens; private final CardEdition.Collection editions; // Loaded lazily: @@ -38,32 +42,50 @@ public class StaticData { private static StaticData lastInstance = null; - public StaticData(CardStorageReader reader, String editionFolder, String blockDataFolder) { - this.reader = reader; + public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) { + this(cardReader, null, editionFolder, blockDataFolder); + } + + public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) { + this.cardReader = cardReader; + this.tokenReader = tokenReader; this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder))); this.blockDataFolder = blockDataFolder; lastInstance = this; - final Map regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - final Map variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + { + final Map regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + final Map variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (CardRules card : reader.loadCards()) { - if (null == card) continue; + for (CardRules card : cardReader.loadCards()) { + if (null == card) continue; - final String cardName = card.getName(); - if (card.isVariant()) { - variantsCards.put(cardName, card); - } else { - regularCards.put(cardName, card); + final String cardName = card.getName(); + if (card.isVariant()) { + variantsCards.put(cardName, card); + } else { + regularCards.put(cardName, card); + } } + + commonCards = new CardDb(regularCards, editions); + variantCards = new CardDb(variantsCards, editions); + + //muse initialize after establish field values for the sake of card image logic + commonCards.initialize(false, false); + variantCards.initialize(false, false); } - commonCards = new CardDb(regularCards, editions); - variantCards = new CardDb(variantsCards, editions); + { + final Map tokens = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - //muse initialize after establish field values for the sake of card image logic - commonCards.initialize(false, false); - variantCards.initialize(false, false); + for (CardRules card : tokenReader.loadCards()) { + if (null == card) continue; + + tokens.put(card.getNormalizedName(), card); + } + allTokens = new TokenDb(tokens, editions); + } } public static StaticData instance() { @@ -91,7 +113,7 @@ public class StaticData { PaperCard card = commonCards.getCard(cardName, setCode, artIndex); if (card == null) { attemptToLoadCard(cardName, setCode); - commonCards.getCard(cardName, setCode, artIndex); + card = commonCards.getCard(cardName, setCode, artIndex); } if (card == null) { card = commonCards.getCard(cardName, setCode, -1); @@ -105,7 +127,7 @@ public class StaticData { public void attemptToLoadCard(String encodedCardName, String setCode) { CardDb.CardRequest r = CardRequest.fromString(encodedCardName); String cardName = r.cardName; - CardRules rules = reader.attemptToLoadCard(cardName, setCode); + CardRules rules = cardReader.attemptToLoadCard(cardName, setCode); if (rules != null) { if (rules.isVariant()) { variantCards.loadCard(cardName, rules); @@ -162,6 +184,8 @@ public class StaticData { return variantCards; } + public TokenDb getAllTokens() { return allTokens; } + public PaperCard getCardByEditionDate(PaperCard card, Date editionDate) { PaperCard c = this.getCommonCards().getCardFromEdition(card.getName(), editionDate, CardDb.SetPreference.LatestCoreExp, card.getArtIndex()); diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 44ad869be66..133e8a75cd9 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -74,6 +74,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { // NO GETTERS/SETTERS HERE! public static class CardRequest { + // TODO Move Request to its own class public String cardName; public String edition; public int artIndex; diff --git a/forge-core/src/main/java/forge/card/CardRarity.java b/forge-core/src/main/java/forge/card/CardRarity.java index a67e3006d4f..7ed13dda93c 100644 --- a/forge-core/src/main/java/forge/card/CardRarity.java +++ b/forge-core/src/main/java/forge/card/CardRarity.java @@ -26,6 +26,7 @@ public enum CardRarity { Rare("R", "Rare"), MythicRare("M", "Mythic Rare"), Special("S", "Special"), // Timeshifted + None("N", "None"), // Tokens Unknown("?", "Unknown"); // In development public static final CardRarity[] FILTER_OPTIONS = new CardRarity[] { diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 811f4d96cfd..a1121fe47bd 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -34,6 +34,7 @@ import java.util.StringTokenizer; * @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $ */ public final class CardRules implements ICardCharacteristics { + private String normalizedName; private CardSplitType splitType; private ICardFace mainPart; private ICardFace otherPart; @@ -124,6 +125,9 @@ public final class CardRules implements ICardCharacteristics { } } + public String getNormalizedName() { return normalizedName; } + public void setNormalizedName(String filename) { normalizedName = filename; } + public CardAiHints getAiHints() { return aiHints; } @@ -207,12 +211,6 @@ public final class CardRules implements ICardCharacteristics { return meldWith; } -// public Set getSets() { return this.setsPrinted.keySet(); } -// public CardInSet getEditionInfo(final String setCode) { -// final CardInSet result = this.setsPrinted.get(setCode); -// return result; // if returns null, String.format("Card '%s' was never printed in set '%s'", this.getName(), setCode); -// } - // vanguard card fields, they don't use sides. private int deltaHand; private int deltaLife; @@ -260,6 +258,7 @@ public final class CardRules implements ICardCharacteristics { private CardSplitType altMode = CardSplitType.None; private String meldWith = ""; private String handLife = null; + private String normalizedName = ""; // fields to build CardAiHints private boolean removedFromAIDecks = false; @@ -287,6 +286,7 @@ public final class CardRules implements ICardCharacteristics { this.hints = null; this.has = null; this.meldWith = ""; + this.normalizedName = ""; } /** @@ -299,6 +299,8 @@ public final class CardRules implements ICardCharacteristics { faces[0].assignMissingFields(); if (null != faces[1]) faces[1].assignMissingFields(); final CardRules result = new CardRules(faces, altMode, cah); + + result.setNormalizedName(this.normalizedName); result.meldWith = this.meldWith; result.setDlUrls(pictureUrl); if (StringUtils.isNotBlank(handLife)) @@ -306,7 +308,7 @@ public final class CardRules implements ICardCharacteristics { return result; } - public final CardRules readCard(final Iterable script) { + public final CardRules readCard(final Iterable script, String filename) { this.reset(); for (String line : script) { if (line.isEmpty() || line.charAt(0) == '#') { @@ -314,9 +316,14 @@ public final class CardRules implements ICardCharacteristics { } this.parseLine(line); } + this.normalizedName = filename; return this.getCard(); } + public final CardRules readCard(final Iterable script) { + return readCard(script, null); + } + /** * Parses the line. * diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java index 7f813989332..f958afdfac0 100644 --- a/forge-core/src/main/java/forge/item/PaperToken.java +++ b/forge-core/src/main/java/forge/item/PaperToken.java @@ -4,10 +4,12 @@ import forge.ImageKeys; import forge.card.CardEdition; import forge.card.CardRarity; import forge.card.CardRules; +import forge.card.ColorSet; +import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; import java.util.Locale; - public class PaperToken implements InventoryItemFromSet, IPaperCard { private String name; private CardEdition edition; @@ -29,21 +31,91 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { return out.toString().toLowerCase(Locale.ENGLISH); } - public static String makeTokenFileName(String colors, int power, int toughness, String name) { - return makeTokenFileName(colors, String.valueOf(power), String.valueOf(toughness), name); + public static String makeTokenFileName(String colors, String power, String toughness, String types) { + return makeTokenFileName(null, colors, power, toughness, types); } - - public static String makeTokenFileName(String colors, String power, String toughness, String name) { - StringBuilder fileName = new StringBuilder(); - fileName.append(colors).append('_').append(power).append('_').append(toughness).append('_').append(name); - return makeTokenFileName(fileName.toString()); + + public static String makeTokenFileName(String name, String colors, String power, String toughness, String types) { + ArrayList build = new ArrayList<>(); + if (name != null) { + build.add(name); + } + + build.add(colors); + + if (power != null && toughness != null) { + build.add(power); + build.add(toughness); + } + build.add(types); + + String fileName = StringUtils.join(build, "_"); + return makeTokenFileName(fileName); } - - public PaperToken(final CardRules c, CardEdition edition0, final String imageFileName) { + + public static String makeTokenFileName(final CardRules rules, CardEdition edition) { + ArrayList build = new ArrayList<>(); + + String subtypes = StringUtils.join(rules.getType().getSubtypes(), " "); + if (!rules.getName().equals(subtypes)) { + return makeTokenFileName(rules.getName()); + } + + ColorSet colors = rules.getColor(); + + if (colors.isColorless()) { + build.add("C"); + } else { + String color = ""; + if (colors.hasWhite()) color += "W"; + if (colors.hasBlue()) color += "U"; + if (colors.hasBlack()) color += "B"; + if (colors.hasRed()) color += "R"; + if (colors.hasGreen()) color += "G"; + + build.add(color); + } + + if (rules.getPower() != null && rules.getToughness() != null) { + build.add(rules.getPower()); + build.add(rules.getToughness()); + } + + String cardTypes = ""; + if (rules.getType().isArtifact()) cardTypes += "A"; + if (rules.getType().isEnchantment()) cardTypes += "E"; + + if (!cardTypes.isEmpty()) { + build.add(cardTypes); + } + + build.add(subtypes); + + // Are these keywords sorted? + for(String keyword : rules.getMainPart().getKeywords()) { + build.add(keyword); + } + + build.add(edition.getCode()); + + // Should future image file names be all lower case? Instead of Up case sets? + return StringUtils.join(build, "_").toLowerCase(); + } + + public PaperToken(final CardRules c) { this(c, null, null); } + public PaperToken(final CardRules c, final String fileName) { this(c, null, fileName); } + public PaperToken(final CardRules c, CardEdition edition) { this(c, edition, null); } + public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) { this.card = c; this.name = c.getName(); this.edition = edition0; - this.imageFileName = String.format("%s%s", null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode(), imageFileName); + + if (imageFileName == null) { + this.imageFileName = makeTokenFileName(c, edition0); + } else { + String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode(); + this.imageFileName = String.format("%s%s", formatEdition, imageFileName); + } } @Override public String getName() { return name; } @@ -54,7 +126,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { @Override public boolean isFoil() { return false; } @Override public CardRules getRules() { return card; } - @Override public CardRarity getRarity() { return CardRarity.Common; } // They don't have rarity though! + @Override public CardRarity getRarity() { return CardRarity.None; } // Unfortunately this is a property of token, cannot move it outside of class public String getImageFilename() { return imageFileName; } diff --git a/forge-core/src/main/java/forge/token/ITokenDatabase.java b/forge-core/src/main/java/forge/token/ITokenDatabase.java new file mode 100644 index 00000000000..f2f54142d02 --- /dev/null +++ b/forge-core/src/main/java/forge/token/ITokenDatabase.java @@ -0,0 +1,32 @@ +package forge.token; + +import com.google.common.base.Predicate; +import forge.card.CardDb; +import forge.item.PaperToken; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +public interface ITokenDatabase extends Iterable { + PaperToken getToken(String tokenName); + PaperToken getToken(String tokenName, String edition); + PaperToken getToken(String tokenName, String edition, int artIndex); + PaperToken getTokenFromEdition(String tokenName, CardDb.SetPreference fromSet); + PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet); + PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet, int artIndex); + + PaperToken getFoiled(PaperToken cpi); + + int getPrintCount(String tokenName, String edition); + int getMaxPrintCount(String tokenName); + + int getArtCount(String tokenName, String edition); + + Collection getUniqueTokens(); + List getAllTokens(); + List getAllTokens(String tokenName); + List getAllTokens(Predicate predicate); + + Predicate wasPrintedInSets(List allowedSetCodes); +} diff --git a/forge-core/src/main/java/forge/token/TokenDb.java b/forge-core/src/main/java/forge/token/TokenDb.java new file mode 100644 index 00000000000..17cc073d7d6 --- /dev/null +++ b/forge-core/src/main/java/forge/token/TokenDb.java @@ -0,0 +1,121 @@ +package forge.token; + +import com.google.common.base.Predicate; +import com.google.common.collect.Maps; +import forge.card.*; +import forge.item.PaperCard; +import forge.item.PaperToken; + +import java.util.*; + +public class TokenDb implements ITokenDatabase { + // Expected naming convention of scripts + // token_name + // minor_demon + // marit_lage + // gold + + // colors_power_toughness_cardtypes_sub_types_keywords + // Some examples: + // c_3_3_a_wurm_lifelink + // w_2_2_knight_first_strike + + // The image names should be the same as the script name + _set + // If that isn't found, consider falling back to the original token + + private final Map tokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); + + private final CardEdition.Collection editions; + private final Map rulesByName; + + public TokenDb(Map rules, CardEdition.Collection editions) { + this.rulesByName = rules; + this.editions = editions; + } + + @Override + public PaperToken getToken(String tokenName) { + return getToken(tokenName, CardEdition.UNKNOWN.getName()); + } + + @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; + } + } + + @Override + public PaperToken getToken(String tokenName, String edition, int artIndex) { + return null; + } + + @Override + public PaperToken getTokenFromEdition(String tokenName, CardDb.SetPreference fromSet) { + return null; + } + + @Override + public PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet) { + return null; + } + + @Override + public PaperToken getTokenFromEdition(String tokenName, Date printedBefore, CardDb.SetPreference fromSet, int artIndex) { + return null; + } + + @Override + public PaperToken getFoiled(PaperToken cpi) { + return null; + } + + @Override + public int getPrintCount(String cardName, String edition) { + return 0; + } + + @Override + public int getMaxPrintCount(String cardName) { + return 0; + } + + @Override + public int getArtCount(String cardName, String edition) { + return 0; + } + + @Override + public Collection getUniqueTokens() { + return null; + } + + @Override + public List getAllTokens() { + return null; + } + + @Override + public List getAllTokens(String tokenName) { + return null; + } + + @Override + public List getAllTokens(Predicate predicate) { + return null; + } + + @Override + public Predicate wasPrintedInSets(List allowedSetCodes) { + return null; + } + + @Override + public Iterator iterator() { + return null; + } +} 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 4e1dd62ef07..32fa16910d2 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 @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import forge.StaticData; import forge.card.MagicColor; import forge.game.card.token.TokenInfo; import org.apache.commons.lang3.StringUtils; @@ -71,10 +72,26 @@ public class TokenEffect extends SpellAbilityEffect { private String[] tokenKeywords; private String[] tokenHiddenKeywords; - private void readParameters(final SpellAbility mapParams) { + private void readMetaParams(final SpellAbility mapParams) { + this.tokenTapped = mapParams.getParamOrDefault("TokenTapped", "False").equalsIgnoreCase("True"); + this.tokenAttacking = mapParams.getParamOrDefault("TokenAttacking", "False").equalsIgnoreCase("True"); + this.tokenBlocking = mapParams.getParam("TokenBlocking"); + + this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1"); + this.tokenOwner = mapParams.getParamOrDefault("TokenOwner", "You"); + } + + private void readParameters(final SpellAbility mapParams, Card prototype) { + readMetaParams(mapParams); + if (prototype == null) { + readTokenParams(mapParams); + } + } + + private void readTokenParams(final SpellAbility mapParams) { String image; String[] keywords; - + if (mapParams.hasParam("TokenKeywords")) { // TODO: Change this Split to a semicolon or something else keywords = mapParams.getParam("TokenKeywords").split("<>"); @@ -101,9 +118,6 @@ public class TokenEffect extends SpellAbilityEffect { this.tokenAltImages = null; } - this.tokenTapped = mapParams.hasParam("TokenTapped") && mapParams.getParam("TokenTapped").equals("True"); - this.tokenAttacking = mapParams.hasParam("TokenAttacking") && mapParams.getParam("TokenAttacking").equals("True"); - if (mapParams.hasParam("TokenAbilities")) { this.tokenAbilities = mapParams.getParam("TokenAbilities").split(","); } else { @@ -124,8 +138,7 @@ public class TokenEffect extends SpellAbilityEffect { } else { this.tokenStaticAbilities = null; } - this.tokenBlocking = mapParams.getParam("TokenBlocking"); - this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1"); + this.tokenPower = mapParams.getParam("TokenPower"); this.tokenToughness = mapParams.getParam("TokenToughness"); @@ -145,19 +158,19 @@ public class TokenEffect extends SpellAbilityEffect { this.tokenKeywords = keywords; this.tokenImage = image; - if (mapParams.hasParam("TokenOwner")) { - this.tokenOwner = mapParams.getParam("TokenOwner"); - } else { - this.tokenOwner = "You"; - } } @Override protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); final Card host = sa.getHostCard(); + Card prototype = loadTokenPrototype(sa); - readParameters(sa); + if (prototype != null) { + return sa.getDescription(); + } + + readParameters(sa, prototype); final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); @@ -183,13 +196,26 @@ public class TokenEffect extends SpellAbilityEffect { return sb.toString(); } + private Card loadTokenPrototype(SpellAbility sa) { + String script = sa.getParamOrDefault("TokenScript", null); + + String edition = sa.getHostCard().getPaperCard().getEdition(); + + PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition); + if (token != null) { + tokenName = token.getName(); + return Card.fromPaperCard(token, null, sa.getHostCard().getGame()); + } + + return null; + } + @Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); final Game game = host.getGame(); final SpellAbility root = sa.getRootAbility(); - readParameters(sa); - + // Cause of the Token Effect, in general it should be this // but if its a Replacement Effect, it might be something else or null SpellAbility cause = sa; @@ -197,32 +223,6 @@ public class TokenEffect extends SpellAbilityEffect { cause = (SpellAbility)root.getReplacingObject("Cause"); } - String originalColorDesc = parseColorForImage(); - - final List imageNames = Lists.newArrayListWithCapacity(1); - if (this.tokenImage.equals("")) { - imageNames.add(PaperToken.makeTokenFileName(originalColorDesc, tokenPower, tokenToughness, tokenOriginalName)); - } else { - imageNames.add(0, this.tokenImage); - } - if (this.tokenAltImages != null) { - imageNames.addAll(Arrays.asList(this.tokenAltImages)); - } - - String cost = determineTokenColor(host); - - final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); - final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); - final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); - - final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length); - for (int i = 0; i < substitutedTypes.length; i++) { - if (substitutedTypes[i].equals("ChosenType")) { - substitutedTypes[i] = host.getChosenType(); - } - } - final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; - final boolean remember = sa.hasParam("RememberTokens"); final boolean imprint = sa.hasParam("ImprintTokens"); final List allTokens = Lists.newArrayList(); @@ -230,17 +230,59 @@ public class TokenEffect extends SpellAbilityEffect { boolean combatChanged = false; boolean inCombat = game.getPhaseHandler().inCombat(); - for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) { - final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); - final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName, - cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness); - final List tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null); + Card prototype = loadTokenPrototype(sa); - grantHiddenKeywords(tokens); - grantAbilities(tokens, root); - grantTriggers(tokens, root); - grantSvars(tokens, root); - grantStatics(tokens, root); + readParameters(sa, prototype); + final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); + + TokenInfo tokenInfo; + + if (prototype == null) { + String originalColorDesc = parseColorForImage(); + + final List imageNames = Lists.newArrayListWithCapacity(1); + if (this.tokenImage.equals("")) { + imageNames.add(PaperToken.makeTokenFileName(originalColorDesc, tokenPower, tokenToughness, tokenOriginalName)); + } else { + imageNames.add(0, this.tokenImage); + } + if (this.tokenAltImages != null) { + imageNames.addAll(Arrays.asList(this.tokenAltImages)); + } + + String cost = determineTokenColor(host); + + final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); + final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); + + final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length); + for (int i = 0; i < substitutedTypes.length; i++) { + if (substitutedTypes[i].equals("ChosenType")) { + substitutedTypes[i] = host.getChosenType(); + } + } + final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; + + final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); + tokenInfo = new TokenInfo(substitutedName, imageName, + cost, substitutedTypes, this.tokenKeywords, finalPower, finalToughness); + } else { + tokenInfo = new TokenInfo(prototype); + } + + for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) { + List tokens; + + if (prototype == null) { + tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null); + grantHiddenKeywords(tokens); + grantSvars(tokens, root); + grantAbilities(tokens, root); + grantTriggers(tokens, root); + grantStatics(tokens, root); + } else { + tokens = TokenInfo.makeTokensFromPrototype(prototype, controller, finalAmount, cause != null); + } for (Card tok : tokens) { if (this.tokenTapped) { @@ -277,7 +319,7 @@ public class TokenEffect extends SpellAbilityEffect { } allTokens.add(c); } - if (tokenName.equals("Clue")) { // investigate trigger + if ("Clue".equals(tokenName)) { // investigate trigger controller.addInvestigatedThisTurn(); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 9d160f25d16..d217ac8c53c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5405,6 +5405,9 @@ public class Card extends GameEntity implements Comparable { public static Card fromPaperCard(IPaperCard pc, Player owner) { return CardFactory.getCard(pc, owner, owner == null ? null : owner.getGame()); } + public static Card fromPaperCard(IPaperCard pc, Player owner, Game game) { + return CardFactory.getCard(pc, owner, game); + } private static final Map cp2card = Maps.newHashMap(); public static Card getCardForUi(IPaperCard pc) { 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 258788efd51..adfdc2e143d 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 @@ -8,6 +8,7 @@ import forge.ImageKeys; import forge.card.CardType; import forge.game.Game; import forge.game.card.Card; +import forge.game.card.CardFactory; import forge.game.card.CardFactoryUtil; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; @@ -133,10 +134,7 @@ public class TokenInfo { return sb.toString(); } - public List makeTokenWithMultiplier(final Player controller, int amount, final boolean applyMultiplier) { - final List list = Lists.newArrayList(); - final Game game = controller.getGame(); - + static private int calculateMultiplier(final Game game, final Player controller, final boolean applyMultiplier) { int multiplier = 1; Player player = controller; @@ -155,8 +153,16 @@ public class TokenInfo { break; } default: - return list; + return 0; } + return multiplier; + } + + public List makeTokenWithMultiplier(final Player controller, int amount, final boolean applyMultiplier) { + final List list = Lists.newArrayList(); + final Game game = controller.getGame(); + + int multiplier = calculateMultiplier(game, controller, applyMultiplier); for (int i = 0; i < multiplier * amount; i++) { list.add(makeOneToken(controller)); @@ -164,6 +170,24 @@ public class TokenInfo { return list; } + static public List makeTokensFromPrototype(Card prototype, final Player controller, int amount, final boolean applyMultiplier) { + final List list = Lists.newArrayList(); + final Game game = controller.getGame(); + + int multiplier = calculateMultiplier(game, controller, applyMultiplier); + long timestamp = game.getNextTimestamp(); + prototype.setController(controller, timestamp); + for (int i = 0; i < multiplier * amount; i++) { + Card copy = CardFactory.copyCard(prototype, true); + copy.setTimestamp(timestamp); + copy.setOwner(controller); + copy.setToken(true); + list.add(copy); + } + + return list; + } + public Card makeOneToken(final Player controller) { final Game game = controller.getGame(); final Card c = toCard(game); diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index 434a06a857f..4be373b2543 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -145,7 +145,9 @@ public final class FModel { final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge, FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY)); - magicDb = new StaticData(reader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR); + final CardStorageReader tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge, + FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY)); + magicDb = new StaticData(reader, tokenReader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR); //create profile dirs if they don't already exist for (final String dname : ForgeConstants.PROFILE_DIRS) { diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 67b7cabb440..ed1bd6a54d9 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -58,6 +58,7 @@ public final class ForgeConstants { public static final String DRAFT_RANKINGS_FILE = DRAFT_DIR + "rankings.txt"; public static final String SEALED_DIR = RES_DIR + "sealed" + PATH_SEPARATOR; public static final String CARD_DATA_DIR = RES_DIR + "cardsfolder" + PATH_SEPARATOR; + public static final String TOKEN_DATA_DIR = RES_DIR + "tokenscripts" + PATH_SEPARATOR; public static final String EDITIONS_DIR = RES_DIR + "editions" + PATH_SEPARATOR; public static final String BLOCK_DATA_DIR = RES_DIR + "blockdata" + PATH_SEPARATOR; public static final String DECK_CUBE_DIR = RES_DIR + "cube" + PATH_SEPARATOR; From 56250bea638271e7d57c4135134e8c5c0a84ef2a Mon Sep 17 00:00:00 2001 From: tehdiplomat Date: Sun, 25 Feb 2018 15:36:16 -0500 Subject: [PATCH 3/3] Added token scripts to be used with Wurmcoil Engine and Tolsimir Wolfblood The actual changes to use them currently not committed. --- forge-gui/res/tokenscripts/c_3_3_a_wurm_deathtouch.txt | 6 ++++++ forge-gui/res/tokenscripts/c_3_3_a_wurm_lifelink.txt | 6 ++++++ forge-gui/res/tokenscripts/voja.txt | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 forge-gui/res/tokenscripts/c_3_3_a_wurm_deathtouch.txt create mode 100644 forge-gui/res/tokenscripts/c_3_3_a_wurm_lifelink.txt create mode 100644 forge-gui/res/tokenscripts/voja.txt diff --git a/forge-gui/res/tokenscripts/c_3_3_a_wurm_deathtouch.txt b/forge-gui/res/tokenscripts/c_3_3_a_wurm_deathtouch.txt new file mode 100644 index 00000000000..aae13dca8c7 --- /dev/null +++ b/forge-gui/res/tokenscripts/c_3_3_a_wurm_deathtouch.txt @@ -0,0 +1,6 @@ +Name:Wurm +ManaCost:no cost +Types:Artifact Creature Wurm +PT:3/3 +K:Deathtouch +Oracle:Deathtouch \ No newline at end of file diff --git a/forge-gui/res/tokenscripts/c_3_3_a_wurm_lifelink.txt b/forge-gui/res/tokenscripts/c_3_3_a_wurm_lifelink.txt new file mode 100644 index 00000000000..4beffff5f52 --- /dev/null +++ b/forge-gui/res/tokenscripts/c_3_3_a_wurm_lifelink.txt @@ -0,0 +1,6 @@ +Name:Wurm +ManaCost:no cost +Types:Artifact Creature Wurm +PT:3/3 +K:Lifelink +Oracle:Lifelink \ No newline at end of file diff --git a/forge-gui/res/tokenscripts/voja.txt b/forge-gui/res/tokenscripts/voja.txt new file mode 100644 index 00000000000..e22806572ae --- /dev/null +++ b/forge-gui/res/tokenscripts/voja.txt @@ -0,0 +1,6 @@ +Name:Voja +ManaCost:no cost +Types:Legendary Creature Wolf +Colors:white,green +PT:2/2 +Oracle: \ No newline at end of file