Compare commits

..

2 Commits

Author SHA1 Message Date
Hans Mackowiak
f14454e992 fix Tests for now 2025-06-01 23:21:49 +02:00
Hans Mackowiak
e4799a4f73 ImageKeys: unify PaperCard imageKeys 2025-06-01 23:09:27 +02:00
573 changed files with 1197 additions and 2007 deletions

View File

@@ -81,7 +81,7 @@ jobs:
export _JAVA_OPTIONS="-Xmx2g"
d=$(date +%m.%d)
# build only desktop and only try to move desktop files
mvn -U -B clean -P windows-linux install -DskipTests -Dskip.flatten=true -e -T 1C release:clean release:prepare release:perform
mvn -U -B clean -P windows-linux install -e -T 1C release:clean release:prepare release:perform -DskipTests
mkdir izpack
# move bz2 and jar from work dir to izpack dir
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
@@ -128,44 +128,6 @@ jobs:
removeArtifacts: true
makeLatest: true
- name: 🔧 Install XML tools
run: sudo apt-get install -y libxml2-utils
- name: 🔼 Bump versionCode in root POM
id: bump_version
run: |
cd /home/runner/work/forge/forge/
current_version=$(xmllint --xpath "//*[local-name()='versionCode']/text()" pom.xml)
echo "Current versionCode: $current_version"
IFS='.' read -r major minor patch <<< "${current_version}"
new_patch=$(printf "%02d" $((10#$patch + 1)))
new_version="${major}.${minor}.${new_patch}"
sed -i -E "s|<versionCode>.*</versionCode>|<versionCode>${new_version}</versionCode>|" pom.xml
echo "version_code=${new_version}" >> $GITHUB_OUTPUT
- name: ♻️ Restore {revision} in child POMs
run: |
find . -name pom.xml ! -path "./pom.xml" | while read -r pom; do
sed -i -E 's|<version>2\.0+\.[0-9]+(-SNAPSHOT)?</version>|<version>${revision}</version>|' "$pom"
done
- name: 💾 Commit restored {revision}
run: |
# Add only pom.xml files
find . -name pom.xml -exec git add {} \;
# Commit if there are changes
if git diff --cached --quiet; then
echo "No pom.xml changes to commit."
else
git commit -m "Restore POM files for preparation of next release" || echo "No changes to commit"
git push
fi
- name: Send failure notification to Discord
if: failure() # This step runs only if the job fails
run: |

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -33,11 +33,6 @@ public final class ImageKeys {
public static final String RADIATION_IMAGE = "radiation";
public static final String BACKFACE_POSTFIX = "$alt";
public static final String SPECFACE_W = "$wspec";
public static final String SPECFACE_U = "$uspec";
public static final String SPECFACE_B = "$bspec";
public static final String SPECFACE_R = "$rspec";
public static final String SPECFACE_G = "$gspec";
private static String CACHE_CARD_PICS_DIR, CACHE_TOKEN_PICS_DIR, CACHE_ICON_PICS_DIR, CACHE_BOOSTER_PICS_DIR,
CACHE_FATPACK_PICS_DIR, CACHE_BOOSTERBOX_PICS_DIR, CACHE_PRECON_PICS_DIR, CACHE_TOURNAMENTPACK_PICS_DIR;
@@ -97,13 +92,28 @@ public final class ImageKeys {
return cachedCards.get(key);
}
public static File getImageFile(String key) {
return getImageFile(key, false);
}
public static File getImageFile(String key, boolean artCrop) {
if (StringUtils.isEmpty(key))
return null;
final String dir;
final String filename;
String[] tempdata = null;
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
if (key.startsWith(ImageKeys.CARD_PREFIX)) {
tempdata = key.substring(ImageKeys.CARD_PREFIX.length()).split("\\|");
String tokenname = tempdata[0];
if (tempdata.length > 1) {
tokenname += "_" + tempdata[1];
}
if (tempdata.length > 2) {
tokenname += "_" + tempdata[2];
}
filename = tokenname ;
dir = CACHE_CARD_PICS_DIR;
} else if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
tempdata = key.substring(ImageKeys.TOKEN_PREFIX.length()).split("\\|");
String tokenname = tempdata[0];
if (tempdata.length > 1) {
@@ -154,7 +164,31 @@ public final class ImageKeys {
cachedCards.put(filename, file);
return file;
}
if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
if (tempdata != null && dir.equals(CACHE_CARD_PICS_DIR)) {
String setlessFilename = tempdata[0] + (artCrop ? ".artcrop" : ".fullborder");
String setCode = tempdata.length > 1 ? tempdata[1] : "";
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";
if (!setCode.isEmpty()) {
if (!collectorNumber.isEmpty()) {
file = findFile(dir, setCode + "/" + collectorNumber + "_" + setlessFilename);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
}
file = findFile(dir, setCode + "/" + setlessFilename);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
}
file = findFile(dir, setlessFilename);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
}
if (tempdata != null && dir.equals(CACHE_TOKEN_PICS_DIR)) {
String setlessFilename = tempdata[0];
String setCode = tempdata.length > 1 ? tempdata[1] : "";
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";

View File

@@ -433,6 +433,17 @@ public final class CardEdition implements Comparable<CardEdition> {
public Multimap<String, EditionEntry> getTokens() { return tokenMap; }
public EditionEntry getTokenFromCollectorNumber(String collectorNumber) {
if(collectorNumber == null || collectorNumber.isEmpty())
return null;
for(EditionEntry c : this.tokenMap.values()) {
//Could build a map for this one too if it's used for more than one-offs.
if (c.collectorNumber.equalsIgnoreCase(collectorNumber))
return c;
}
return null;
}
public String getTokenSet(String token) {
if (tokenMap.containsKey(token)) {
return this.getCode();

View File

@@ -20,6 +20,8 @@ package forge.card;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.StaticData;
import forge.card.mana.IParserManaCost;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
@@ -149,6 +151,10 @@ public final class CardRules implements ICardCharacteristics {
return splitType;
}
public boolean hasBackSide() {
return CardSplitType.DUAL_FACED_CARDS.contains(splitType) || splitType == CardSplitType.Flip;
}
public ICardFace getMainPart() {
return mainPart;
}
@@ -165,20 +171,32 @@ public final class CardRules implements ICardCharacteristics {
return Iterables.concat(Arrays.asList(mainPart, otherPart), specializedParts.values());
}
public ICardFace getWSpecialize() {
return specializedParts.get(CardStateName.SpecializeW);
}
public ICardFace getUSpecialize() {
return specializedParts.get(CardStateName.SpecializeU);
}
public ICardFace getBSpecialize() {
return specializedParts.get(CardStateName.SpecializeB);
}
public ICardFace getRSpecialize() {
return specializedParts.get(CardStateName.SpecializeR);
}
public ICardFace getGSpecialize() {
return specializedParts.get(CardStateName.SpecializeG);
public String getImageName(CardStateName state) {
if (splitType == CardSplitType.Split) {
return mainPart.getName() + otherPart.getName();
} else if (state.equals(splitType.getChangedStateName())) {
if (otherPart != null) {
return otherPart.getName();
} else if (this.hasBackSide()) {
if (!getMeldWith().isEmpty()) {
final CardDb db = StaticData.instance().getCommonCards();
return db.getRules(getMeldWith()).getOtherPart().getName();
}
return null;
}
}
switch (state) {
case SpecializeW:
case SpecializeU:
case SpecializeB:
case SpecializeR:
case SpecializeG:
ICardFace face = specializedParts.get(state);
return face != null ? face.getName() : null;
default:
return getName();
}
}
public String getName() {

View File

@@ -24,7 +24,6 @@ import forge.util.CardTranslation;
import forge.util.ImageUtil;
import forge.util.Localizer;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.*;
@@ -347,34 +346,13 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
if (pc == null) {
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
pc = readObjectAlternate(name, edition);
if (pc == null) {
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
}
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found"));
}
}
rules = pc.getRules();
rarity = pc.getRarity();
}
private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException {
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition);
if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name, edition);
}
if (pc == null) {
pc = StaticData.instance().getCommonCards().getCard(name);
if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name);
}
}
return pc;
}
@Serial
private Object readResolve() throws ObjectStreamException {
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.
@@ -385,20 +363,14 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
@Override
public String getImageKey(boolean altState) {
String normalizedName = StringUtils.stripAccents(name);
String imageKey = ImageKeys.CARD_PREFIX + normalizedName + CardDb.NameSetSeparator
+ edition + CardDb.NameSetSeparator + artIndex;
if (altState) {
imageKey += ImageKeys.BACKFACE_POSTFIX;
}
return imageKey;
return altState ? this.getCardAltImageKey() : this.getCardImageKey();
}
private String cardImageKey = null;
@Override
public String getCardImageKey() {
if (this.cardImageKey == null)
this.cardImageKey = ImageUtil.getImageKey(this, "", true);
this.cardImageKey = ImageUtil.getImageKey(this, CardStateName.Original);
return cardImageKey;
}
@@ -407,9 +379,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardAltImageKey() {
if (this.cardAltImageKey == null){
if (this.hasBackFace())
this.cardAltImageKey = ImageUtil.getImageKey(this, "back", true);
this.cardAltImageKey = ImageUtil.getImageKey(this, this.getRules().getSplitType().getChangedStateName());
else // altImageKey will be the same as cardImageKey
this.cardAltImageKey = ImageUtil.getImageKey(this, "", true);
this.cardAltImageKey = getCardImageKey();
}
return cardAltImageKey;
}
@@ -419,9 +391,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardWSpecImageKey() {
if (this.cardWSpecImageKey == null) {
if (this.rules.getSplitType() == CardSplitType.Specialize)
this.cardWSpecImageKey = ImageUtil.getImageKey(this, "white", true);
this.cardWSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeW);
else // just use cardImageKey
this.cardWSpecImageKey = ImageUtil.getImageKey(this, "", true);
this.cardWSpecImageKey = getCardImageKey();
}
return cardWSpecImageKey;
}
@@ -431,9 +403,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardUSpecImageKey() {
if (this.cardUSpecImageKey == null) {
if (this.rules.getSplitType() == CardSplitType.Specialize)
this.cardUSpecImageKey = ImageUtil.getImageKey(this, "blue", true);
this.cardUSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeU);
else // just use cardImageKey
this.cardUSpecImageKey = ImageUtil.getImageKey(this, "", true);
this.cardUSpecImageKey = getCardImageKey();
}
return cardUSpecImageKey;
}
@@ -443,9 +415,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardBSpecImageKey() {
if (this.cardBSpecImageKey == null) {
if (this.rules.getSplitType() == CardSplitType.Specialize)
this.cardBSpecImageKey = ImageUtil.getImageKey(this, "black", true);
this.cardBSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeB);
else // just use cardImageKey
this.cardBSpecImageKey = ImageUtil.getImageKey(this, "", true);
this.cardBSpecImageKey = getCardImageKey();
}
return cardBSpecImageKey;
}
@@ -455,9 +427,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardRSpecImageKey() {
if (this.cardRSpecImageKey == null) {
if (this.rules.getSplitType() == CardSplitType.Specialize)
this.cardRSpecImageKey = ImageUtil.getImageKey(this, "red", true);
this.cardRSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeR);
else // just use cardImageKey
this.cardRSpecImageKey = ImageUtil.getImageKey(this, "", true);
this.cardRSpecImageKey = getCardImageKey();
}
return cardRSpecImageKey;
}
@@ -467,18 +439,16 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public String getCardGSpecImageKey() {
if (this.cardGSpecImageKey == null) {
if (this.rules.getSplitType() == CardSplitType.Specialize)
this.cardGSpecImageKey = ImageUtil.getImageKey(this, "green", true);
this.cardGSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeG);
else // just use cardImageKey
this.cardGSpecImageKey = ImageUtil.getImageKey(this, "", true);
this.cardGSpecImageKey = getCardImageKey();
}
return cardGSpecImageKey;
}
@Override
public boolean hasBackFace(){
CardSplitType cst = this.rules.getSplitType();
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld
|| cst == CardSplitType.Modal;
return this.rules.hasBackSide();
}
@Override

View File

@@ -5,6 +5,7 @@ import forge.StaticData;
import forge.card.CardDb;
import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.card.CardStateName;
import forge.item.IPaperCard;
import forge.item.PaperCard;
import org.apache.commons.lang3.StringUtils;
@@ -24,20 +25,17 @@ public class ImageUtil {
key = imageKey.substring(ImageKeys.CARD_PREFIX.length());
else
return null;
if (key.endsWith(ImageKeys.BACKFACE_POSTFIX)) {
key = key.substring(0, key.length() - ImageKeys.BACKFACE_POSTFIX.length());
}
if (key.isEmpty())
return null;
CardDb db = StaticData.instance().getCommonCards();
PaperCard cp = null;
//db shouldn't be null
if (db != null) {
cp = db.getCard(key);
if (cp == null) {
db = StaticData.instance().getVariantCards();
if (db != null)
cp = db.getCard(key);
}
}
String[] tempdata = key.split("\\|");
PaperCard cp = StaticData.instance().fetchCard(tempdata[0], tempdata[1], tempdata[2]);
if (cp == null)
System.err.println("Can't find PaperCard from key: " + key);
// return cp regardless if it's null
@@ -54,6 +52,21 @@ public class ImageUtil {
return key;
}
public static String getImageRelativePath(String name, String set, String collectorNumber, boolean artChop) {
StringBuilder sb = new StringBuilder();
sb.append(set).append("/");
if (!collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
sb.append(collectorNumber).append("_");
}
sb.append(StringUtils.stripAccents(name));
sb.append(artChop ? ".artcrop" : ".fullborder");
sb.append(".jpg");
return sb.toString();
}
public static String getImageRelativePath(PaperCard cp, String face, boolean includeSet, boolean isDownloadUrl) {
final String nameToUse = cp == null ? null : getNameToUse(cp, face);
if (nameToUse == null) {
@@ -123,25 +136,15 @@ public class ImageUtil {
else
return null;
} else if (face.equals("white")) {
if (card.getWSpecialize() != null) {
return card.getWSpecialize().getName();
}
return card.getImageName(CardStateName.SpecializeW);
} else if (face.equals("blue")) {
if (card.getUSpecialize() != null) {
return card.getUSpecialize().getName();
}
return card.getImageName(CardStateName.SpecializeU);
} else if (face.equals("black")) {
if (card.getBSpecialize() != null) {
return card.getBSpecialize().getName();
}
return card.getImageName(CardStateName.SpecializeB);
} else if (face.equals("red")) {
if (card.getRSpecialize() != null) {
return card.getRSpecialize().getName();
}
return card.getImageName(CardStateName.SpecializeR);
} else if (face.equals("green")) {
if (card.getGSpecialize() != null) {
return card.getGSpecialize().getName();
}
return card.getImageName(CardStateName.SpecializeG);
} else if (CardSplitType.Split == cp.getRules().getSplitType()) {
return card.getMainPart().getName() + card.getOtherPart().getName();
} else if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
@@ -150,50 +153,86 @@ public class ImageUtil {
return cp.getName();
}
public static String getNameToUse(PaperCard cp, CardStateName face) {
if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
return cp.getFunctionalVariant();
}
final CardRules card = cp.getRules();
return card.getImageName(face);
}
public static String getImageKey(PaperCard cp, String face, boolean includeSet) {
return getImageRelativePath(cp, face, includeSet, false);
}
public static String getImageKey(PaperCard cp, CardStateName face) {
String name = getNameToUse(cp, face);
String number = cp.getCollectorNumber();
String suffix = "";
switch (face) {
case SpecializeB:
number += "b";
break;
case SpecializeG:
number += "g";
break;
case SpecializeR:
number += "r";
break;
case SpecializeU:
number += "u";
break;
case SpecializeW:
number += "w";
break;
case Meld:
case Modal:
case Secondary:
case Transformed:
suffix = ImageKeys.BACKFACE_POSTFIX;
break;
case Flipped:
break; // add info to rotate the image?
default:
break;
};
return ImageKeys.CARD_PREFIX + name + CardDb.NameSetSeparator + cp.getEdition()
+ CardDb.NameSetSeparator + number + CardDb.NameSetSeparator + cp.getArtIndex() + suffix;
}
public static String getDownloadUrl(PaperCard cp, String face) {
return getImageRelativePath(cp, face, true, true);
}
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop){
return getScryfallDownloadUrl(cp, face, setCode, langCode, useArtCrop, false);
public static String getScryfallDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam, boolean useArtCrop){
return getScryfallDownloadUrl(collectorNumber, setCode, langCode, faceParam, useArtCrop, false);
}
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
String editionCode;
if ((setCode != null) && (setCode.length() > 0))
editionCode = setCode;
else
editionCode = cp.getEdition().toLowerCase();
String cardCollectorNumber = cp.getCollectorNumber();
public static String getScryfallDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam, boolean useArtCrop, boolean hyphenateAlchemy){
// Hack to account for variations in Arabian Nights
cardCollectorNumber = cardCollectorNumber.replace("+", "");
collectorNumber = collectorNumber.replace("+", "");
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
if (cardCollectorNumber.startsWith("OHOP")) {
editionCode = "ohop";
cardCollectorNumber = cardCollectorNumber.substring("OHOP".length());
} else if (cardCollectorNumber.startsWith("OPCA")) {
editionCode = "opca";
cardCollectorNumber = cardCollectorNumber.substring("OPCA".length());
} else if (cardCollectorNumber.startsWith("OPC2")) {
editionCode = "opc2";
cardCollectorNumber = cardCollectorNumber.substring("OPC2".length());
if (collectorNumber.startsWith("OHOP")) {
setCode = "ohop";
collectorNumber = collectorNumber.substring("OHOP".length());
} else if (collectorNumber.startsWith("OPCA")) {
setCode = "opca";
collectorNumber = collectorNumber.substring("OPCA".length());
} else if (collectorNumber.startsWith("OPC2")) {
setCode = "opc2";
collectorNumber = collectorNumber.substring("OPC2".length());
} else if (hyphenateAlchemy) {
if (!cardCollectorNumber.startsWith("A")) {
if (!collectorNumber.startsWith("A")) {
return null;
}
cardCollectorNumber = cardCollectorNumber.replace("A", "A-");
collectorNumber = collectorNumber.replace("A", "A-");
}
String versionParam = useArtCrop ? "art_crop" : "normal";
String faceParam = "";
if (cp.getRules().getOtherPart() != null) {
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
if (!faceParam.isEmpty()) {
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
}
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumber,
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, collectorNumber,
langCode, versionParam, faceParam);
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -548,20 +548,20 @@ public class GameAction {
copied.updateStateForView();
// we don't want always trigger before counters are placed
game.getTriggerHandler().suppressMode(TriggerType.Always);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
// needed for counters + ascend
if (!suppress && toBattlefield) {
game.getTriggerHandler().registerActiveTrigger(copied, false);
}
// do ETB counters after zone add
table.replaceCounterEffect(game, null, true, true, params);
game.getTriggerHandler().clearSuppression(TriggerType.Always);
if (!table.isEmpty()) {
// we don't want always trigger before counters are placed
game.getTriggerHandler().suppressMode(TriggerType.Always);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
// do ETB counters after zone add
table.replaceCounterEffect(game, null, true, true, params);
game.getTriggerHandler().clearSuppression(TriggerType.Always);
}
// update static abilities after etb counters have been placed
checkStaticAbilities();
@@ -2794,7 +2794,6 @@ public class GameAction {
if (aura == null) {
return false;
}
aura.setActivatingPlayer(source.getController());
Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
boolean canTargetPlayer = false;

View File

@@ -6515,29 +6515,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
if(uiCard != null)
uiCard.currentState.setImageKey(iFN);
}
public final void setImageKey(final IPaperCard ipc, final CardStateName stateName) {
if (ipc == null)
return;
switch (stateName) {
case SpecializeB:
setImageKey(ipc.getCardBSpecImageKey());
break;
case SpecializeR:
setImageKey(ipc.getCardRSpecImageKey());
break;
case SpecializeG:
setImageKey(ipc.getCardGSpecImageKey());
break;
case SpecializeU:
setImageKey(ipc.getCardUSpecImageKey());
break;
case SpecializeW:
setImageKey(ipc.getCardWSpecImageKey());
break;
default:
break;
}
}
public String getImageKey(CardStateName state) {
if (!getRenderForUI()) {
@@ -6837,7 +6814,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
public final void setSpecialized(final boolean bool) {
specialized = bool;
setImageKey(getPaperCard(), getCurrentStateName());
}
public final boolean canSpecialize() {
return getRules() != null && getRules().getSplitType() == CardSplitType.Specialize;

View File

@@ -187,7 +187,7 @@ public class CardFactory {
c.setRarity(cp.getRarity());
// Would like to move this away from in-game entities
String originalPicture = cp.getImageKey(false);
String originalPicture = cp.getCardImageKey();
c.setImageKey(originalPicture);
if(cp.isToken())
@@ -198,11 +198,11 @@ public class CardFactory {
if (c.hasAlternateState()) {
if (c.isFlipCard()) {
c.setState(CardStateName.Flipped, false);
c.setImageKey(cp.getImageKey(true));
c.setImageKey(cp.getCardAltImageKey());
}
else if (c.isDoubleFaced() && cardRules != null) {
c.setState(cardRules.getSplitType().getChangedStateName(), false);
c.setImageKey(cp.getImageKey(true));
c.setImageKey(cp.getCardAltImageKey());
}
else if (c.isSplitCard()) {
c.setState(CardStateName.LeftSplit, false);
@@ -216,23 +216,23 @@ public class CardFactory {
c.setImageKey(originalPicture);
} else if (c.canSpecialize()) {
c.setState(CardStateName.SpecializeW, false);
c.setImageKey(cp.getImageKey(false) + ImageKeys.SPECFACE_W);
c.setImageKey(cp.getCardWSpecImageKey());
c.setSetCode(cp.getEdition());
c.setRarity(cp.getRarity());
c.setState(CardStateName.SpecializeU, false);
c.setImageKey(cp.getImageKey(false) + ImageKeys.SPECFACE_U);
c.setImageKey(cp.getCardUSpecImageKey());
c.setSetCode(cp.getEdition());
c.setRarity(cp.getRarity());
c.setState(CardStateName.SpecializeB, false);
c.setImageKey(cp.getImageKey(false) + ImageKeys.SPECFACE_B);
c.setImageKey(cp.getCardBSpecImageKey());
c.setSetCode(cp.getEdition());
c.setRarity(cp.getRarity());
c.setState(CardStateName.SpecializeR, false);
c.setImageKey(cp.getImageKey(false) + ImageKeys.SPECFACE_R);
c.setImageKey(cp.getCardRSpecImageKey());
c.setSetCode(cp.getEdition());
c.setRarity(cp.getRarity());
c.setState(CardStateName.SpecializeG, false);
c.setImageKey(cp.getImageKey(false) + ImageKeys.SPECFACE_G);
c.setImageKey(cp.getCardGSpecImageKey());
c.setSetCode(cp.getEdition());
c.setRarity(cp.getRarity());
}

View File

@@ -52,7 +52,6 @@ import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class CardState extends GameObject implements IHasSVars, ITranslatable {
private String name = "";
@@ -367,16 +366,14 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
public final FCollectionView<SpellAbility> getManaAbilities() {
FCollection<SpellAbility> newCol = new FCollection<>();
updateSpellAbilities(newCol, true);
// stream().toList() causes crash on Android, use Collectors.toList()
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList()));
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).toList());
card.updateSpellAbilities(newCol, this, true);
return newCol;
}
public final FCollectionView<SpellAbility> getNonManaAbilities() {
FCollection<SpellAbility> newCol = new FCollection<>();
updateSpellAbilities(newCol, false);
// stream().toList() causes crash on Android, use Collectors.toList()
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList()));
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).toList());
card.updateSpellAbilities(newCol, this, false);
return newCol;
}
@@ -388,10 +385,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardState leftState = getCard().getState(CardStateName.LeftSplit);
Collection<SpellAbility> leftAbilities = leftState.abilities;
if (null != mana) {
leftAbilities = leftAbilities.stream()
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
// stream().toList() causes crash on Android, use Collectors.toList()
.collect(Collectors.toList());
leftAbilities = leftAbilities.stream().filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)).toList();
}
newCol.addAll(leftAbilities);
leftState.updateSpellAbilities(newCol, mana);
@@ -400,10 +394,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardState rightState = getCard().getState(CardStateName.RightSplit);
Collection<SpellAbility> rightAbilities = rightState.abilities;
if (null != mana) {
rightAbilities = rightAbilities.stream()
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
// stream().toList() causes crash on Android, use Collectors.toList()
.collect(Collectors.toList());
rightAbilities = rightAbilities.stream().filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)).toList();
}
newCol.addAll(rightAbilities);
rightState.updateSpellAbilities(newCol, mana);
@@ -437,7 +428,8 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardTypeView type = getTypeWithChanges();
if (type.isLand()) {
if (landAbility == null) {
landAbility = new LandAbility(card, this);
landAbility = new LandAbility(card);
landAbility.setCardState(this);
}
newCol.add(landAbility);
} else if (type.isAura()) {

View File

@@ -1335,9 +1335,29 @@ public class CardView extends GameEntityView {
}
void updateImageKey(Card c) {
set(TrackableProperty.ImageKey, c.getImageKey());
// currently only works for Cards
if (!c.getGamePieceType().equals(GamePieceType.CARD)) {
return;
}
IPaperCard pc = c.getPaperCard();
if (pc != null) {
set(TrackableProperty.Artist, pc.getArtist());
}
}
void updateImageKey(CardState c) {
set(TrackableProperty.ImageKey, c.getImageKey());
// currently only works for Cards
if (!c.getCard().getGamePieceType().equals(GamePieceType.CARD)) {
return;
}
IPaperCard pc = c.getCard().getPaperCard();
if (pc != null) { // currently Artist is per Card
set(TrackableProperty.Artist, pc.getArtist());
}
}
public String getArtist() {
return get(TrackableProperty.Artist);
}
public CardTypeView getType() {

View File

@@ -22,7 +22,6 @@ import com.esotericsoftware.minlog.Log;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost;
/**
@@ -38,9 +37,6 @@ public abstract class Ability extends SpellAbility {
protected Ability(final Card sourceCard, final ManaCost manaCost) {
this(sourceCard, new Cost(manaCost, true), null);
}
protected Ability(final Card sourceCard, final ManaCost manaCost, final CardState state) {
super(sourceCard, new Cost(manaCost, true), null, state);
}
protected Ability(final Card sourceCard, final ManaCost manaCost, SpellAbilityView view0) {
this(sourceCard, new Cost(manaCost, true), view0);
}

View File

@@ -19,7 +19,6 @@ package forge.game.spellability;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost;
/**
@@ -44,9 +43,6 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
public AbilityStatic(final Card sourceCard, final ManaCost manaCost) {
super(sourceCard, manaCost);
}
public AbilityStatic(final Card sourceCard, final ManaCost manaCost, final CardState state) {
super(sourceCard, manaCost, state);
}
public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) {
super(sourceCard, abCost);

View File

@@ -21,7 +21,6 @@ import forge.card.CardStateName;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardCopyService;
import forge.game.card.CardState;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
@@ -32,8 +31,8 @@ import org.apache.commons.lang3.StringUtils;
public class LandAbility extends AbilityStatic {
public LandAbility(Card sourceCard, CardState state) {
super(sourceCard, ManaCost.NO_COST, state);
public LandAbility(Card sourceCard) {
super(sourceCard, ManaCost.NO_COST);
getRestrictions().setZone(ZoneType.Hand);
}

View File

@@ -195,18 +195,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
protected SpellAbility(final Card iSourceCard, final Cost toPay) {
this(iSourceCard, toPay, null, null);
this(iSourceCard, toPay, null);
}
protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0) {
this(iSourceCard, toPay, view0, null);
}
protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0, CardState cs) {
id = nextId();
hostCard = iSourceCard;
payCosts = toPay;
if (cs != null) {
cardState = cs;
}
if (view0 == null) {
view0 = new SpellAbilityView(this);
}
@@ -1228,14 +1222,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
try {
clone = (SpellAbility) clone();
clone.id = lki ? id : nextId();
clone.view = new SpellAbilityView(clone, lki || host.getGame() == null ? null : host.getGame().getTracker());
// don't use setHostCard to not trigger the not copied parts yet
copyHelper(clone, host, lki || keepTextChanges);
// need CardState before View
clone.view = new SpellAbilityView(clone, lki || host.getGame() == null ? null : host.getGame().getTracker());
// always set this to false, it is only set in CopyEffect
clone.mayChooseNewTargets = false;

View File

@@ -118,6 +118,7 @@ public enum TrackableProperty {
//Card State
Name(TrackableTypes.StringType),
Artist(TrackableTypes.StringType),
Colors(TrackableTypes.ColorSetType),
OriginalColors(TrackableTypes.ColorSetType),
LeftSplitColors(TrackableTypes.ColorSetType),

View File

@@ -23,7 +23,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>

View File

@@ -40,7 +40,7 @@ import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.LoadingCache;
import com.mortennobel.imagescaling.ResampleOp;
import forge.card.CardSplitType;
import forge.card.CardEdition;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
@@ -56,8 +56,8 @@ import forge.model.FModel;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinIcon;
import forge.toolbox.imaging.FCardImageRenderer;
import forge.util.Aggregates;
import forge.util.ImageUtil;
import forge.util.TextUtil;
/**
* This class stores ALL card images in a cache with soft values. this means
@@ -171,65 +171,41 @@ public class ImageCache {
IPaperCard ipc = null;
boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
String specColor = "";
if (imageKey.endsWith(ImageKeys.SPECFACE_W)) {
specColor = "white";
} else if (imageKey.endsWith(ImageKeys.SPECFACE_U)) {
specColor = "blue";
} else if (imageKey.endsWith(ImageKeys.SPECFACE_B)) {
specColor = "black";
} else if (imageKey.endsWith(ImageKeys.SPECFACE_R)) {
specColor = "red";
} else if (imageKey.endsWith(ImageKeys.SPECFACE_G)) {
specColor = "green";
}
boolean useArtCrop = "Crop".equals(FModel.getPreferences().getPref(ForgePreferences.FPref.UI_CARD_ART_FORMAT));
String fileName = imageKey;
if (altState)
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
if (!specColor.isEmpty())
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.SPECFACE_W.length());
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
ipc = ImageUtil.getPaperCardFromImageKey(imageKey);
if (ipc != null) {
if (altState) {
imageKey = ipc.getCardAltImageKey();
} else if (!specColor.isEmpty()) {
switch (specColor) {
case "white":
imageKey = ipc.getCardWSpecImageKey();
break;
case "blue":
imageKey = ipc.getCardUSpecImageKey();
break;
case "black":
imageKey = ipc.getCardBSpecImageKey();
break;
case "red":
imageKey = ipc.getCardRSpecImageKey();
break;
case "green":
imageKey = ipc.getCardGSpecImageKey();
break;
String[] tempdata = imageKey.substring(2).split("\\|"); //We want to check the edition first.
String name = tempdata[0];
String setCode = tempdata.length > 1 ? tempdata[1] : CardEdition.UNKNOWN_CODE;
String collectorNumber = tempdata.length > 3 ? tempdata[2] : IPaperCard.NO_COLLECTOR_NUMBER;
CardEdition edition = StaticData.instance().getEditions().get(setCode);
if (useArtCrop) {
CardEdition.EditionEntry ee;
if (!collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
ee = edition.getCardFromCollectorNumber(collectorNumber);
if (ee != null) { // TODO handle Specialize Collector number
ee = edition.getCardFromCollectorNumber(collectorNumber.substring(0, collectorNumber.length() - 1));
}
} else {
imageKey = ipc.getCardImageKey();
ee = Aggregates.random(edition.getCardInSet(name));
}
if (StringUtils.isBlank(imageKey))
return Pair.of(_defaultImage, true);
}
}
// Replace .full to .artcrop if art crop is preferred
// Only allow use art if the artist info is available
boolean useArtCrop = "Crop".equals(FModel.getPreferences().getPref(ForgePreferences.FPref.UI_CARD_ART_FORMAT))
&& ipc != null && !ipc.getArtist().isEmpty();
String originalKey = imageKey;
if (useArtCrop) {
if (ipc != null && ipc.getRules().getSplitType() == CardSplitType.Flip) {
// Art crop will always use front face as image key for flip cards
imageKey = ipc.getCardImageKey();
// Skip fetching if artist info is not available for art crop
if (ee != null && ee.artistName().isEmpty()) {
useArtCrop = false;
}
}
imageKey = TextUtil.fastReplace(imageKey, ".full", ".artcrop");
}
ipc = StaticData.instance().fetchCard(name, setCode, collectorNumber);
fileName = ImageUtil.getImageRelativePath(name, setCode, collectorNumber, useArtCrop);
} // TODO add artCrop for Token
String originalKey = imageKey;
// Load from file and add to cache if not found in cache initially.
BufferedImage original = getImage(imageKey);
@@ -239,16 +215,11 @@ public class ImageCache {
}
// if art crop is exist, check also if the full card image is also cached.
if (useArtCrop && original != null) {
BufferedImage cached = _CACHE.getIfPresent(originalKey);
if (cached != null)
return Pair.of(cached, false);
}
boolean noBorder = !useArtCrop && !isPreferenceEnabled(ForgePreferences.FPref.UI_RENDER_BLACK_BORDERS);
boolean fetcherEnabled = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER);
boolean isPlaceholder = (original == null) && fetcherEnabled;
String setCode = imageKey.split("/")[0].trim().toUpperCase();
String setCode = fileName.split("/")[0].trim().toUpperCase();
// If the user has indicated that they prefer Forge NOT render a black border, round the image corners
// to account for JPEG images that don't have a transparency.
@@ -301,7 +272,7 @@ public class ImageCache {
CardView card = ipc != null ? Card.getCardForUi(ipc).getView() : cardView;
String legalString = null;
original = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
if (art != null) {
if (art != null && ipc != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(StaticData.instance().getCardEdition(ipc.getEdition()).getDate());
int year = cal.get(Calendar.YEAR);

View File

@@ -18,7 +18,8 @@ final class ImageLoader extends CacheLoader<String, BufferedImage> {
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES))
return null;
File file = ImageKeys.getImageFile(key);
boolean useArtCrop = "Crop".equals(FModel.getPreferences().getPref(ForgePreferences.FPref.UI_CARD_ART_FORMAT));
File file = ImageKeys.getImageFile(key, useArtCrop);
if (file != null) {
if (!file.exists()) {
return null;

View File

@@ -77,41 +77,11 @@ public final class FImageUtil {
}
boolean altState = key.endsWith(ImageKeys.BACKFACE_POSTFIX);
String specColor = "";
if (key.endsWith(ImageKeys.SPECFACE_W)) {
specColor = "white";
} else if (key.endsWith(ImageKeys.SPECFACE_U)) {
specColor = "blue";
} else if (key.endsWith(ImageKeys.SPECFACE_B)) {
specColor = "black";
} else if (key.endsWith(ImageKeys.SPECFACE_R)) {
specColor = "red";
} else if (key.endsWith(ImageKeys.SPECFACE_G)) {
specColor = "green";
}
String imageKey = key;
if (prefix.equals(ImageKeys.CARD_PREFIX)) {
PaperCard card = ImageUtil.getPaperCardFromImageKey(key);
if (altState) {
imageKey = card.getCardAltImageKey();
} else if (!specColor.isEmpty()) {
switch (specColor) {
case "white":
imageKey = card.getCardWSpecImageKey();
break;
case "blue":
imageKey = card.getCardUSpecImageKey();
break;
case "black":
imageKey = card.getCardBSpecImageKey();
break;
case "red":
imageKey = card.getCardRSpecImageKey();
break;
case "green":
imageKey = card.getCardGSpecImageKey();
break;
}
} else {
imageKey = card.getCardImageKey();
}
@@ -119,9 +89,6 @@ public final class FImageUtil {
if(altState) {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
imageKey += "full.jpg";
} else if (!specColor.isEmpty()) {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.SPECFACE_W.length());
imageKey += "full.jpg";
}
File file = ImageKeys.getImageFile(imageKey);

View File

@@ -34,10 +34,14 @@ public class SwingImageFetcher extends ImageFetcher {
return false;
}
String newdespath = urlToDownload.contains(".fullborder.jpg") || urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) ?
TextUtil.fastReplace(destPath, ".full.jpg", ".fullborder.jpg") : destPath;
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) && !destPath.startsWith(ForgeConstants.CACHE_TOKEN_PICS_DIR))
newdespath = newdespath.replace(".jpg", ".fullborder.jpg"); //fix planes/phenomenon for round border options
String newdespath = destPath;
if (!destPath.contains(".artcrop")) {
newdespath = urlToDownload.contains(".fullborder.jpg") || urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) ?
TextUtil.fastReplace(destPath, ".full.jpg", ".fullborder.jpg") : destPath;
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) && !destPath.startsWith(ForgeConstants.CACHE_TOKEN_PICS_DIR))
newdespath = newdespath.replace(".jpg", ".fullborder.jpg"); //fix planes/phenomenon for round border options
}
URL url = new URL(urlToDownload);
System.out.println("Attempting to fetch: " + url);
BufferedImage image = ImageIO.read(url);

View File

@@ -11,7 +11,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -9,7 +9,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -35,9 +35,7 @@ import java.util.*;
* Class that represents the player (not the player sprite)
*/
public class AdventurePlayer implements Serializable, SaveFileContent {
public static final int MIN_DECK_COUNT = 10;
// this is a purely arbitrary limit, could be higher or lower; just meant as some sort of reasonable limit for the user
public static final int MAX_DECK_COUNT = 20;
public static final int NUMBER_OF_DECKS = 10;
// Player profile data.
private String name;
private int heroRace;
@@ -47,7 +45,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
// Deck data
private Deck deck;
private final ArrayList<Deck> decks = new ArrayList<Deck>(MIN_DECK_COUNT);
private final Deck[] decks = new Deck[NUMBER_OF_DECKS];
private int selectedDeckIndex = 0;
private final DifficultyData difficultyData = new DifficultyData();
@@ -93,13 +91,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return statistic;
}
public int getDeckCount() { return decks.size(); }
private void clearDecks() {
decks.clear();
for (int i = 0; i < MIN_DECK_COUNT; i++)
decks.add(new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
deck = decks.get(0);
for (int i = 0; i < NUMBER_OF_DECKS; i++) decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
deck = decks[0];
selectedDeckIndex = 0;
}
@@ -146,7 +140,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
announceCustom = usingCustomDeck = isUsingCustomDeck;
deck = startingDeck;
decks.set(0, deck);
decks[0] = deck;
cards.addAllFlat(deck.getAllCardsInASinglePool().toFlatList());
@@ -179,9 +173,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
public void setSelectedDeckSlot(int slot) {
if (slot >= 0 && slot < getDeckCount()) {
if (slot >= 0 && slot < NUMBER_OF_DECKS) {
selectedDeckIndex = slot;
deck = decks.get(selectedDeckIndex);
deck = decks[selectedDeckIndex];
setColorIdentity(DeckProxy.getColorIdentity(deck));
}
}
@@ -220,7 +214,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
public Deck getDeck(int index) {
return decks.get(index);
return decks[index];
}
public CardPool getCards() {
@@ -454,44 +448,17 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
}
// load decks
// check if this save has dynamic deck count, use set-count load if not
boolean hasDynamicDeckCount = data.containsKey("deckCount");
if (hasDynamicDeckCount) {
int dynamicDeckCount = data.readInt("deckCount");
// in case the save had previously saved more decks than the current version allows (in case of the max being lowered)
dynamicDeckCount = Math.min(MAX_DECK_COUNT, dynamicDeckCount);
for (int i = 0; i < dynamicDeckCount; i++){
// the first x elements are pre-created
if (i < MIN_DECK_COUNT) {
decks.set(i, new Deck(data.readString("deck_name_" + i)));
}
else {
decks.add(new Deck(data.readString("deck_name_" + i)));
}
decks.get(i).getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
if (data.containsKey("sideBoardCards_" + i))
decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
}
// in case we allow removing decks from the deck selection GUI, populate up to the minimum
for (int i = dynamicDeckCount++; i < MIN_DECK_COUNT; i++) {
decks.set(i, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
}
// legacy load
} else {
for (int i = 0; i < MIN_DECK_COUNT; i++) {
if (!data.containsKey("deck_name_" + i)) {
if (i == 0) decks.set(i, deck);
else decks.set(i, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
continue;
}
decks.set(i, new Deck(data.readString("deck_name_" + i)));
decks.get(i).getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
if (data.containsKey("sideBoardCards_" + i))
decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
if (!data.containsKey("deck_name_" + i)) {
if (i == 0) decks[i] = deck;
else decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
continue;
}
decks[i] = new Deck(data.readString("deck_name_" + i));
decks[i].getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
if (data.containsKey("sideBoardCards_" + i))
decks[i].getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
}
setSelectedDeckSlot(data.readInt("selectedDeckIndex"));
cards.addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("cards"))));
@@ -635,14 +602,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.storeObject("deckCards", deck.getMain().toCardList("\n").split("\n"));
if (deck.get(DeckSection.Sideboard) != null)
data.storeObject("sideBoardCards", deck.get(DeckSection.Sideboard).toCardList("\n").split("\n"));
// save decks dynamically
data.store("deckCount", getDeckCount());
for (int i = 0; i < getDeckCount(); i++) {
data.store("deck_name_" + i, decks.get(i).getName());
data.storeObject("deck_" + i, decks.get(i).getMain().toCardList("\n").split("\n"));
if (decks.get(i).get(DeckSection.Sideboard) != null)
data.storeObject("sideBoardCards_" + i, decks.get(i).get(DeckSection.Sideboard).toCardList("\n").split("\n"));
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
data.store("deck_name_" + i, decks[i].getName());
data.storeObject("deck_" + i, decks[i].getMain().toCardList("\n").split("\n"));
if (decks[i].get(DeckSection.Sideboard) != null)
data.storeObject("sideBoardCards_" + i, decks[i].get(DeckSection.Sideboard).toCardList("\n").split("\n"));
}
data.store("selectedDeckIndex", selectedDeckIndex);
data.storeObject("cards", cards.toCardList("\n").split("\n"));
@@ -969,7 +933,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
public void renameDeck(String text) {
deck = (Deck) deck.copyTo(text);
decks.set(selectedDeckIndex, deck);
decks[selectedDeckIndex] = deck;
}
public int cardSellPrice(PaperCard card) {
@@ -1218,23 +1182,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
/**
* Clears a deck by replacing the current selected deck with a new deck
* Deletes a deck by replacing the current selected deck with a new deck
*/
public void clearDeck() {
deck = decks.set(selectedDeckIndex, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
}
/**
* Actually removes the deck from the list of decks.
*/
public void deleteDeck(){
int oldIndex = selectedDeckIndex;
this.setSelectedDeckSlot(0);
decks.remove(oldIndex);
}
public void addDeck(){
decks.add(new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
public void deleteDeck() {
deck = decks[selectedDeckIndex] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
}
/**
@@ -1243,9 +1194,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
* @return int - index of new copy slot, or -1 if no slot was available
*/
public int copyDeck() {
for (int i = 0; i < MAX_DECK_COUNT; i++) {
for (int i = 0; i < decks.length; i++) {
if (isEmptyDeck(i)) {
decks.set(i, (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")"));
decks[i] = (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")");
return i;
}
}
@@ -1254,7 +1205,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
public boolean isEmptyDeck(int deckIndex) {
return decks.get(deckIndex).isEmpty() && decks.get(deckIndex).getName().equals(Forge.getLocalizer().getMessage("lblEmptyDeck"));
return decks[deckIndex].isEmpty() && decks[deckIndex].getName().equals(Forge.getLocalizer().getMessage("lblEmptyDeck"));
}
public void removeEvent(AdventureEventData completedEvent) {
@@ -1276,8 +1227,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
// 2. Count max cards across all decks in excess of unsellable
Map<PaperCard, Integer> maxCardCounts = new HashMap<>();
for (int i = 0; i < getDeckCount(); i++) {
for (final Map.Entry<PaperCard, Integer> cp : decks.get(i).getAllCardsInASinglePool()) {
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
for (final Map.Entry<PaperCard, Integer> cp : decks[i].getAllCardsInASinglePool()) {
int count = cp.getValue();
if (count > maxCardCounts.getOrDefault(cp.getKey(), 0)) {
maxCardCounts.put(cp.getKey(), cp.getValue());

View File

@@ -389,7 +389,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
@Override
public void onActivate() {
decksUsingMyCards = new ItemPool<>(InventoryItem.class);
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++) {
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) {
final Deck deck = AdventurePlayer.current().getDeck(i);
CardPool main = deck.getMain();
for (final Map.Entry<PaperCard, Integer> e : main) {
@@ -649,7 +649,6 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
Map<String, CardEdition> editionsByName = new HashMap<>();
for (CardEdition e : FModel.getMagicDb().getEditions()) {
editionsByName.put(e.getName().toLowerCase(), e);
editionsByName.put(e.getName().replace(":", "").toLowerCase(), e);
}
String sketchbookPrefix = "landscape sketchbook - ";

View File

@@ -16,12 +16,11 @@ import forge.adventure.util.Current;
public class DeckSelectScene extends UIScene {
private final IntMap<TextraButton> buttons = new IntMap<>();
private final IntMap<Label> labels = new IntMap<>();
Color defColor;
TextField textInput;
Table layout;
TextraLabel header;
TextraButton back, edit, rename, add;
TextraButton back, edit, rename;
int currentSlot = 0;
ScrollPane scrollPane;
Dialog renameDialog;
@@ -46,13 +45,13 @@ public class DeckSelectScene extends UIScene {
root.add(header).colspan(2);
root.row();
root.add(scrollPane).expand().width(window.getWidth() - 20);
this.layoutDeckButtons();
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++)
addDeckSlot(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
textInput = Controls.newTextField("");
back = ui.findActor("return");
edit = ui.findActor("edit");
rename = ui.findActor("rename");
add = ui.findActor("add");
ui.onButtonPress("return", DeckSelectScene.this::back);
ui.onButtonPress("edit", DeckSelectScene.this::edit);
ui.onButtonPress("rename", () -> {
@@ -60,44 +59,11 @@ public class DeckSelectScene extends UIScene {
showRenameDialog();
});
ui.onButtonPress("copy", DeckSelectScene.this::copy);
ui.onButtonPress("delete", DeckSelectScene.this::promptDelete);
ui.onButtonPress("add", DeckSelectScene.this::addDeck);
ui.onButtonPress("delete", DeckSelectScene.this::maybeDelete);
defColor = ui.findActor("return").getColor();
window.add(root);
}
private void refreshDeckButtons(){
clearDeckButtons();
layoutDeckButtons();
}
private void clearDeckButtons(){
int count = AdventurePlayer.current().getDeckCount();
for (int i = count; i >= 0; i--){
clearDeckButton(i);
}
layout.clearChildren();
buttons.clear();
labels.clear();
}
private void layoutDeckButtons() {
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++)
addDeckButton(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
}
private void addDeck(){
if (Current.player().getDeckCount() >= AdventurePlayer.MAX_DECK_COUNT){
showDialog(createGenericDialog(Forge.getLocalizer().getMessage("lblAddDeck"), Forge.getLocalizer().getMessage("lblMaxDeckCountReached"),
Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null));
return;
}
Current.player().addDeck();
refreshDeckButtons();
select(Current.player().getSelectedDeckIndex());
}
private void copy() {
if (Current.player().isEmptyDeck(currentSlot)) return;
int index = Current.player().copyDeck();
@@ -113,32 +79,17 @@ public class DeckSelectScene extends UIScene {
}
}
private void promptDelete() {
private void maybeDelete() {
if (Current.player().isEmptyDeck(currentSlot)) return;
Dialog deleteDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblAreYouSureProceedDelete"),
Forge.getLocalizer().getMessage("lblOK"),
Forge.getLocalizer().getMessage("lblAbort"), this::clearOrDelete, this::removeDialog);
Forge.getLocalizer().getMessage("lblAbort"), this::delete, this::removeDialog);
showDialog(deleteDialog);
}
/**
* Clears or deletes the currently selected deck.
*/
private void clearOrDelete(){
if (currentSlot >= AdventurePlayer.MIN_DECK_COUNT){
Current.player().deleteDeck();
}
else {
Current.player().clearDeck();
}
refreshDeckButtons();
select(0);
removeDialog();
}
private void clear() {
Current.player().clearDeck();
private void delete() {
Current.player().deleteDeck();
updateDeckButton(currentSlot);
removeDialog();
}
@@ -166,18 +117,7 @@ public class DeckSelectScene extends UIScene {
showDialog(renameDialog);
}
/**
* Not sure if this is strictly necessary in Java but wouldn't want to leak before clearing the layout table.
*/
private void clearDeckButton(int i){
if (buttons.containsKey(i)) {
TextraButton button = buttons.remove(i);
button.clearListeners();
button.clearActions();
}
}
private TextraButton addDeckButton(String name, int i) {
private TextraButton addDeckSlot(String name, int i) {
TextraButton button = Controls.newTextButton("-");
button.addListener(new ClickListener() {
@Override
@@ -191,12 +131,9 @@ public class DeckSelectScene extends UIScene {
}
});
button.setText(Current.player().getDeck(i).getName());
Label label = Controls.newLabel(name);
layout.add(Controls.newLabel(name)).pad(2);
layout.add(button).fill(true, false).expand(true, false).align(Align.left).expandX().pad(2);
buttons.put(i, button);
labels.put(i, label);
addToSelectable(new Selectable(button));
layout.row();
return button;
@@ -221,7 +158,13 @@ public class DeckSelectScene extends UIScene {
@Override
public void enter() {
refreshDeckButtons();
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) {
if (buttons.containsKey(i)) {
buttons.get(i).setText(Current.player().getDeck(i).getName());
buttons.get(i).getTextraLabel().layout();
buttons.get(i).layout();
}
}
GameHUD.getInstance().switchAudio();
select(Current.player().getSelectedDeckIndex());
performTouch(scrollPane); //can use mouse wheel if available to scroll after selection
@@ -232,7 +175,9 @@ public class DeckSelectScene extends UIScene {
private void rename() {
String text = textInput.getText();
Current.player().renameDeck(text);
updateDeckButton(currentSlot);
buttons.get(currentSlot).setText(Current.player().getDeck(currentSlot).getName());
buttons.get(currentSlot).getTextraLabel().layout();
buttons.get(currentSlot).layout();
}
private void edit() {

View File

@@ -511,11 +511,13 @@ public class EventScene extends MenuScene implements IAfterMatch {
if (match.p1 instanceof AdventureEventData.AdventureEventHuman) {
humanMatch = match;
continue;
} else if (match.p2 instanceof AdventureEventData.AdventureEventHuman) {
AdventureEventData.AdventureEventParticipant placeholder = match.p1;
match.p1 = match.p2;
match.p2 = placeholder;
humanMatch = match;
continue;
} else {
//Todo: Actually run match simulation here
if (MyRandom.percentTrue(50)) {
@@ -528,6 +530,7 @@ public class EventScene extends MenuScene implements IAfterMatch {
match.winner = match.p2;
}
}
}
if (humanMatch != null && humanMatch.round != currentEvent.currentRound)
@@ -536,16 +539,14 @@ public class EventScene extends MenuScene implements IAfterMatch {
DuelScene duelScene = DuelScene.instance();
EnemySprite enemy = humanMatch.p2.getSprite();
currentEvent.nextOpponent = humanMatch.p2;
advance.setDisabled(true);
FThreads.invokeInEdtNowOrLater(() -> Forge.setTransitionScreen(new TransitionScreen(() -> {
duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, false, currentEvent);
advance.setDisabled(false);
Forge.switchScene(duelScene);
}, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.getName(), humanMatch.p1.getRecord(), humanMatch.p2.getRecord())));
} else {
finishRound();
advance.setDisabled(false);
}
advance.setDisabled(false);
}
AdventureEventData.AdventureEventMatch humanMatch = null;

View File

@@ -249,27 +249,6 @@ public class Controls {
return ret;
}
static public SelectBox<Integer> newComboBox(Integer[] text, int item, Function<Object, Void> func) {
SelectBox<Integer> ret = newComboBox();
ret.getStyle().listStyle.selection.setTopHeight(4);
ret.setItems(text);
ret.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
try {
func.apply(((SelectBox) actor).getSelected());
} catch (Exception e) {
e.printStackTrace();
}
}
});
func.apply(item);
ret.getList().setAlignment(Align.center);
ret.setSelected(item);
ret.setAlignment(Align.right);
return ret;
}
static public TextField newTextField(String text) {
return new TextField(text, getSkin());
}

View File

@@ -124,8 +124,6 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
if(reward.type.equals(Reward.Type.Card)) {
imageKey = reward.getCard().getImageKey(false);
PaperCard card = ImageUtil.getPaperCardFromImageKey(imageKey);
imageKey = card.getCardImageKey();
int count = 0;
@@ -248,8 +246,7 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
hasbackface = reward.getCard().hasBackFace();
if (ImageCache.getInstance().imageKeyFileExists(reward.getCard().getImageKey(false)) && !Forge.enableUIMask.equals("Art")) {
int count = 0;
PaperCard card = ImageUtil.getPaperCardFromImageKey(reward.getCard().getImageKey(false));
File frontFace = ImageKeys.getImageFile(card.getCardImageKey());
File frontFace = ImageKeys.getImageFile(reward.getCard().getImageKey(false));
if (frontFace != null) {
try {
Texture front = Forge.getAssets().manager().get(frontFace.getPath(), Texture.class, false);
@@ -275,8 +272,7 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
//preload card back for performance
if (hasbackface) {
if (ImageCache.getInstance().imageKeyFileExists(reward.getCard().getImageKey(true))) {
PaperCard cardBack = ImageUtil.getPaperCardFromImageKey(reward.getCard().getImageKey(true));
File backFace = ImageKeys.getImageFile(cardBack.getCardAltImageKey());
File backFace = ImageKeys.getImageFile(reward.getCard().getImageKey(true));
if (backFace != null) {
try {
Texture back = Forge.getAssets().manager().get(backFace.getPath(), Texture.class, false);
@@ -400,8 +396,7 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
if (imageKey != "") {
isBooster = true;
File file = new File(IMAGE_LIST_QUEST_BOOSTERS_FILE);
try {
Scanner scanner = new Scanner(file);
try (Scanner scanner = new Scanner(file)) {
String boosterPath = "";
while(scanner.hasNextLine())
{

View File

@@ -247,9 +247,6 @@ public class ImageCache {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
}
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
PaperCard card = ImageUtil.getPaperCardFromImageKey(imageKey);
if (card != null)
imageKey = altState ? card.getCardAltImageKey() : card.getCardImageKey();
if (StringUtils.isBlank(imageKey)) {
if (useDefaultIfNotFound)
return getDefaultImage();

View File

@@ -9,9 +9,9 @@ import java.util.List;
import forge.ImageKeys;
import forge.assets.*;
import forge.item.PaperCard;
import forge.util.ImageUtil;
import forge.util.TextBounds;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.badlogic.gdx.graphics.Color;
@@ -156,13 +156,7 @@ public class CardImageRenderer {
}
//space for artist
textBoxHeight -= 2 * PT_FONT.getCapHeight();
PaperCard paperCard = null;
try {
paperCard = ImageUtil.getPaperCardFromImageKey(state.getImageKey());
} catch (Exception e) {}
String artist = "WOTC";
if (paperCard != null && !paperCard.getArtist().isEmpty())
artist = paperCard.getArtist();
String artist = ObjectUtils.firstNonNull(state.getArtist(), "WOTC");
float minTextBoxHeight = 2 * headerHeight;
if (textBoxHeight < minTextBoxHeight) {
artHeight -= (minTextBoxHeight - textBoxHeight); //subtract from art height if text box not big enough otherwise

View File

@@ -103,27 +103,22 @@ public class CardZoom extends FOverlay {
if (pc != null) {
Card cardW = Card.fromPaperCard(pc, null);
cardW.setState(CardStateName.SpecializeW, true);
cardW.setImageKey(pc, CardStateName.SpecializeW);
list.add(cardW.getView());
Card cardU = Card.fromPaperCard(pc, null);
cardU.setState(CardStateName.SpecializeU, true);
cardU.setImageKey(pc, CardStateName.SpecializeU);
cardU.setState(CardStateName.SpecializeU, true);;
list.add(cardU.getView());
Card cardB = Card.fromPaperCard(pc, null);
cardB.setState(CardStateName.SpecializeB, true);
cardB.setImageKey(pc, CardStateName.SpecializeB);
list.add(cardB.getView());
Card cardR = Card.fromPaperCard(pc, null);
cardR.setState(CardStateName.SpecializeR, true);
cardR.setImageKey(pc, CardStateName.SpecializeR);
list.add(cardR.getView());
Card cardG = Card.fromPaperCard(pc, null);
cardG.setState(CardStateName.SpecializeG, true);
cardG.setImageKey(pc, CardStateName.SpecializeG);
list.add(cardG.getView());
}
if (!list.isEmpty())

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>2.0.04</version>
<version>${revision}</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -32,7 +32,7 @@
<object id="53" template="../../obj/treasure.tx" x="194" y="125"/>
<object id="54" template="../../obj/enemy.tx" x="201.253" y="161.473">
<properties>
<property name="effect" value="{ &quot;startBattleWithCard&quot;: [&quot;Dreadhorde Invasion&quot;]}"/>
<property name="effect" value="{ &quot;startBattleWithCard&quot;: [ &quot;Dreadhorde Invasion&quot; }"/>
<property name="enemy" value="Big Zombie"/>
<property name="speedModifier" type="float" value="3"/>
<property name="threatRange" type="int" value="60"/>

View File

@@ -1,4 +1,4 @@
deathknightidle.png
Deathknightidle.png
size: 80,16
format: RGBA8888
filter: Nearest,Nearest

View File

@@ -34,7 +34,7 @@
"width": 100,
"height": 30,
"x": 365,
"y": 50
"y": 60
},
{
"type": "TextButton",
@@ -44,7 +44,7 @@
"width": 100,
"height": 30,
"x": 365,
"y": 90
"y": 110
},
{
"type": "TextButton",
@@ -53,7 +53,7 @@
"width": 100,
"height": 30,
"x": 365,
"y": 130
"y": 160
},
{
"type": "TextButton",
@@ -62,15 +62,6 @@
"width": 100,
"height": 30,
"x": 365,
"y": 170
},
{
"type": "TextButton",
"name": "add",
"text": "tr(lblAddDeck)",
"width": 100,
"height": 30,
"x": 365,
"y": 210
}
]

View File

@@ -15,42 +15,33 @@
"x": 4,
"y": 4,
"width": 262,
"height": 384
},
{
"type": "TextButton",
"name": "add",
"text": "tr(lblAddDeck)",
"width": 130,
"height": 30,
"x": 4,
"y": 388
"height": 414
},
{
"type": "TextButton",
"name": "delete",
"text": "tr(lblDelete)",
"width": 130,
"height": 30,
"x": 136,
"y": 388
},
{
"type": "TextButton",
"name": "copy",
"text": "tr(lblCopy)",
"width": 130,
"width": 86,
"height": 30,
"x": 4,
"y": 418
},
{
"type": "TextButton",
"name": "copy",
"text": "tr(lblCopy)",
"width": 86,
"height": 30,
"x": 92,
"y": 418
},
{
"type": "TextButton",
"name": "rename",
"text": "tr(lblRename)",
"width": 130,
"width": 86,
"height": 30,
"x": 136,
"x": 180,
"y": 418
},
{

View File

@@ -141,4 +141,3 @@ Pioneer Masters, 3/6/PIO, PIO
Innistrad Remastered, 3/6/INR, INR
Aetherdrift, 3/6/DFT, DFT
Tarkir Dragonstorm, 3/6/TDM, TDM
Final Fantasy, 3/6/FIN, FIN

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Enchantment Aura
K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard
SVar:AttachAILogic:Reanimate
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay

View File

@@ -1,8 +1,8 @@
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
Name:Clackbridge Troll
ManaCost:3 B B
Types:Creature Troll
PT:8/8
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
K:Haste
K:Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TripleGoat | TriggerDescription$ When CARDNAME enters, target opponent creates three 0/1 white Goat creature tokens.

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Enchantment Aura
K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard
SVar:AttachAILogic:Reanimate
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | Tapped$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay

View File

@@ -1,4 +1,4 @@
Name:Monastery Messenger
Name: Monastery Messenger
ManaCost:2U 2R 2W
Types:Creature Bird Scout
PT:2/3

View File

@@ -2,7 +2,7 @@ Name:Necromancy
ManaCost:2 B
Types:Enchantment
K:MayFlashSac
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ RaiseDead | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with CARDNAME." Put target creature card from a graveyard onto the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ RaiseDead | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with CARDNAME." Put target creature card from a graveyard onto the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:RaiseDead:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | TgtPrompt$ Select target creature card in a graveyard | ValidTgts$ Creature | ChangeNum$ 1 | SubAbility$ Aurify
SVar:Aurify:DB$ Animate | Types$ Aura | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | Duration$ Permanent | SubAbility$ NecromAttach
SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it.

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Human Warrior
PT:3/4
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigStangg | TriggerDescription$ When CARDNAME enters, create Stangg Twin, a legendary 3/4 red and green Human Warrior creature token.
SVar:TrigStangg:DB$ Token | TokenAmount$ 1 | TokenScript$ stangg_twin | TokenOwner$ You | RememberTokens$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedLKI | Triggers$ TrigExile,TrigSac | Duration$ Permanent | ForgetOnMoved$ Battlefield | SubAbility$ DBCleanup
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedLKI | Triggers$ TrigExile,TrigSac | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:TrigExile:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.EffectSource | Execute$ ExileTwin | TriggerDescription$ When EFFECTSOURCE leaves the battlefield, exile that token.
SVar:ExileTwin:DB$ ChangeZone | Defined$ Remembered | Origin$ Battlefield | Destination$ Exile

View File

@@ -5,4 +5,4 @@ A:SP$ Charm | Choices$ DBTokens,DBTap | CharmNum$ 1
SVar:DBTokens:DB$ Token | TokenScript$ c_1_1_hero | TokenOwner$ You | TokenAmount$ 3 | SpellDescription$ Take the Elevator — Create three 1/1 colorless Hero creature tokens.
SVar:DBTap:DB$ Tap | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures | SubAbility$ DBPutCounter | SpellDescription$ Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them.
SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.targetedBy | CounterType$ Stun | CounterNum$ 1
Oracle:Choose one —\n• Take the Elevator — Create three 1/1 colorless Hero creature tokens.\n• Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)
Oracle:Choose one—\n• Take the Elevator — Create three 1/1 colorless Hero creature tokens.\n• Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)

View File

@@ -10,4 +10,4 @@ SVar:DBUntap:DB$ Untap
A:AB$ PumpAll | Cost$ R | ValidCards$ Creature.YouCtrl | KW$ Haste | Exhaust$ True | SpellDescription$ Creatures you control gain haste until end of turn.
DeckHas:Ability$Graveyard
DeckHints:Name$Audacious Knuckleblade
Oracle:Exhaust — {2}{G}: Seek a card named Audacious Knuckleblade and put it onto the battlefield tapped.\nExhaust — {1}{U}: Surveil 2, then untap this creature.\nExhaust — {R}: Creatures you control gain haste until end of turn.
Oracle:Exhaust — {2}{G}: Seek a card named Audacious Knuckleblade and put it onto the battlefield tapped.\nExhaust — {1}{U}: Surveil 2, then untap this creature./nExhaust — {R}: Creatures you control gain haste until end of turn.

View File

@@ -9,4 +9,4 @@ SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Count$DifferentCounterKinds_Ca
SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
SVar:X:Count$xPaid
Oracle:When this artifact enters, support X. (Put a +1/+1 counter on each of up to X target creatures.)\nGo for the Goal! — {3}, {T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.
Oracle:When this artifact enters, support X. (Put a +1/+1 counter on each of up to X target creatures.)\nGo for the Goal! — {3},{T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.

Some files were not shown because too many files have changed in this diff Show More