mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 02:08:00 +00:00
Merge branch 'token_phase_two' into 'master'
Add more support for migrating Editions to TokenScripts See merge request core-developers/forge!885
This commit is contained in:
@@ -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;
|
||||
/**
|
||||
* <p>
|
||||
* 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();
|
||||
|
||||
|
||||
@@ -123,12 +123,19 @@ public final class CardEdition implements Comparable<CardEdition> { // 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<CardEdition> { // immutable
|
||||
protected CardEdition read(File file) {
|
||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||
|
||||
List<String> tokenNormalized = new ArrayList<>();
|
||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
||||
if (contents.containsKey("cards")) {
|
||||
for(String line : contents.get("cards")) {
|
||||
@@ -277,7 +285,19 @@ public final class CardEdition implements Comparable<CardEdition> { // 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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user