diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index 31688a61c3..519fa48360 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -74,16 +74,22 @@ public enum ScryfallImageSource implements CardImageSource { // CARDS TRY - // direct links to images via hardcoded API path. Used for cards with non-ASCII collector numbers + // direct links to images via hardcoded API path + // used for cards with non-ASCII collector numbers or another use cases if (baseUrl == null) { - String apiUrl = ScryfallImageSupportCards.findDirectDownloadLink(card.getSet(), card.getName(), card.getCollectorId()); - if (apiUrl != null) { - baseUrl = apiUrl + localizedCode + "?format=image"; - alternativeUrl = apiUrl + defaultCode + "?format=image"; - - // workaround to use cards without english images (some promos or special cards) - if (Objects.equals(baseUrl, alternativeUrl) && baseUrl.endsWith("/en?format=image")) { - alternativeUrl = alternativeUrl.replace("/en?format=image", "/?format=image"); + String link = ScryfallImageSupportCards.findDirectDownloadLink(card.getSet(), card.getName(), card.getCollectorId()); + if (link != null) { + if (ScryfallImageSupportCards.isApiLink(link)) { + // api + baseUrl = link + localizedCode + "?format=image"; + alternativeUrl = link + defaultCode + "?format=image"; + // workaround to use cards without english images (some promos or special cards) + if (Objects.equals(baseUrl, alternativeUrl) && baseUrl.endsWith("/en?format=image")) { + alternativeUrl = alternativeUrl.replace("/en?format=image", "/?format=image"); + } + } else { + // image + baseUrl = link; } } } @@ -103,18 +109,22 @@ public enum ScryfallImageSource implements CardImageSource { // basic cards by api call (redirect to img link) // example: https://api.scryfall.com/cards/xln/121/en?format=image if (baseUrl == null) { - baseUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet(), isToken) + "/" - + card.getCollectorId() + "/" + localizedCode + "?format=image"; - alternativeUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet(), isToken) + "/" - + card.getCollectorId() + "/" + defaultCode + "?format=image"; - + baseUrl = String.format("https://api.scryfall.com/cards/%s/%s/%s?format=image", + formatSetName(card.getSet(), isToken), + card.getCollectorId(), + localizedCode); + alternativeUrl = String.format("https://api.scryfall.com/cards/%s/%s/%s?format=image", + formatSetName(card.getSet(), isToken), + card.getCollectorId(), + defaultCode); // workaround to use cards without english images (some promos or special cards) // bug: https://github.com/magefree/mage/issues/6829 // example: Mysterious Egg from IKO https://api.scryfall.com/cards/iko/385/?format=image if (Objects.equals(baseUrl, alternativeUrl)) { // without loc code scryfall must return first available image - alternativeUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet(), isToken) + "/" - + card.getCollectorId() + "/?format=image"; + alternativeUrl = String.format("https://api.scryfall.com/cards/%s/%s/?format=image", + formatSetName(card.getSet(), isToken), + card.getCollectorId()); } } @@ -126,6 +136,7 @@ public enum ScryfallImageSource implements CardImageSource { final String localizedCode = languageAliases.getOrDefault(this.getCurrentLanguage(), defaultCode); List needUrls = new ArrayList<>(); + int needFaceIndex = card.isSecondSide() ? 1 : 0; String apiUrl = ScryfallImageSupportCards.findDirectDownloadLink(card.getSet(), card.getName(), card.getCollectorId()); if (apiUrl != null) { @@ -143,11 +154,15 @@ public enum ScryfallImageSource implements CardImageSource { } else { // BY CARD NUMBER // localized and default - needUrls.add("https://api.scryfall.com/cards/" - + formatSetName(card.getSet(), isToken) + "/" + card.getCollectorId() + "/" + localizedCode); + needUrls.add(String.format("https://api.scryfall.com/cards/%s/%s/%s", + formatSetName(card.getSet(), isToken), + card.getCollectorId(), + localizedCode)); if (!localizedCode.equals(defaultCode)) { - needUrls.add("https://api.scryfall.com/cards/" - + formatSetName(card.getSet(), isToken) + "/" + card.getCollectorId() + "/" + defaultCode); + needUrls.add(String.format("https://api.scryfall.com/cards/%s/%s/%s", + formatSetName(card.getSet(), isToken), + card.getCollectorId(), + defaultCode)); } } @@ -178,8 +193,11 @@ public enum ScryfallImageSource implements CardImageSource { if (jsonFaces == null) { throw new MageException("Couldn't find card_faces in card's JSON data: " + jsonUrl); } + if (jsonFaces.size() < needFaceIndex + 1) { + throw new MageException("card_faces doesn't contains face index in card's JSON data: " + jsonUrl); + } - JsonObject jsonFace = jsonFaces.get(card.isSecondSide() ? 1 : 0).getAsJsonObject(); + JsonObject jsonFace = jsonFaces.get(needFaceIndex).getAsJsonObject(); JsonObject jsonImages = JsonUtil.getAsObject(jsonFace, "image_uris"); if (jsonImages == null) { throw new MageException("Couldn't find image_uris in card's JSON data: " + jsonUrl); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index 333aa04e84..e89d19462c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -508,21 +508,23 @@ public class ScryfallImageSupportCards { private static final Map directDownloadLinks = new HashMap() { { - // xmage card -> api link: - // examples: - // api example: https://api.scryfall.com/cards/trix/6/ - // api format is primary + // xmage card -> api or image link + // WARNING, try use api links as much as possible (it supports build-in translation) // - // code form for one card: + // example: + // api link: https://api.scryfall.com/cards/trix/6/ + // image link: https://c1.scryfall.com/file/scryfall-cards/large/back/d/5/d5dfd236-b1da-4552-b94f-ebf6bb9dafdf.jpg + // + // key for one card: // set/card_name // - // code form for same name cards (alternative images): - // set/card_name/card_number - // set/card_name/card_number - + // key for same name cards (alternative images): + // set/card_name/card_number_1 + // set/card_name/card_number_2 + // // Cards with non-ASCII collector numbers must use direct download (cause xmage uses different card number) // Verify checks must check and show missing data from that list - // WARNING, must use as API link, not image + // 10E put("10E/Air Elemental/64*", "https://api.scryfall.com/cards/10e/64★/"); put("10E/Anaba Bodyguard/187*", "https://api.scryfall.com/cards/10e/187★/"); @@ -786,10 +788,6 @@ public class ScryfallImageSupportCards { put("PM14/Hive Stirrings/21*", "https://api.scryfall.com/cards/pm14/21★/"); put("PM14/Megantic Sliver/185*", "https://api.scryfall.com/cards/pm14/185★/"); put("PM14/Ratchet Bomb/215*", "https://api.scryfall.com/cards/pm14/215★/"); - // PROE - put("PROE/Emrakul, the Aeons Torn/4*", "https://api.scryfall.com/cards/proe/4★/"); - put("PROE/Lord of Shatterskull Pass/156*", "https://api.scryfall.com/cards/proe/156★/"); - // // PMBS put("PMBS/Glissa, the Traitor/96*", "https://api.scryfall.com/cards/pmbs/96★/"); put("PMBS/Hero of Bladehold/8*", "https://api.scryfall.com/cards/pmbs/8★/"); @@ -948,6 +946,18 @@ public class ScryfallImageSupportCards { put("WAR/Ugin, the Ineffable/2*", "https://api.scryfall.com/cards/war/2★/"); put("WAR/Vivien, Champion of the Wilds/180*", "https://api.scryfall.com/cards/war/180★/"); put("WAR/Vraska, Swarm's Eminence/236*", "https://api.scryfall.com/cards/war/236★/"); + // SLD + // TODO: update direct image links in 2022 for HQ images + put("SLD/Zndrsplt, Eye of Wisdom/379", "https://api.scryfall.com/cards/sld/379/"); + put("SLD/Zndrsplt, Eye of Wisdom/379b", "https://c1.scryfall.com/file/scryfall-cards/large/back/d/5/d5dfd236-b1da-4552-b94f-ebf6bb9dafdf.jpg"); + put("SLD/Krark's Thumb/383", "https://api.scryfall.com/cards/sld/383/"); + put("SLD/Krark's Thumb/383b", "https://c1.scryfall.com/file/scryfall-cards/large/back/9/f/9f63277b-e139-46c8-b9e3-0cfb647f44cc.jpg"); + put("SLD/Okaun, Eye of Chaos/380", "https://api.scryfall.com/cards/sld/380/"); + put("SLD/Okaun, Eye of Chaos/380b", "https://c1.scryfall.com/file/scryfall-cards/large/back/9/4/94eea6e3-20bc-4dab-90ba-3113c120fb90.jpg"); + put("SLD/Propaganda/381", "https://api.scryfall.com/cards/sld/381/"); + put("SLD/Propaganda/381b", "https://c1.scryfall.com/file/scryfall-cards/large/back/3/e/3e3f0bcd-0796-494d-bf51-94b33c1671e9.jpg"); + put("SLD/Stitch in Time/382", "https://api.scryfall.com/cards/sld/382/"); + put("SLD/Stitch in Time/382b", "https://c1.scryfall.com/file/scryfall-cards/large/back/0/8/087c3a0d-c710-4451-989e-596b55352184.jpg"); } }; @@ -1001,6 +1011,10 @@ public class ScryfallImageSupportCards { return ""; } + public static boolean isApiLink(String link) { + return !link.endsWith(".jpg") && !link.endsWith(".png"); + } + public static Map getDirectDownloadLinks() { return directDownloadLinks; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 7da3a0eb09..18f6c63d02 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -436,6 +436,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements String cardName = card.getName(); boolean isType2 = type2SetsFilter.contains(card.getSetCode()); CardDownloadData url = new CardDownloadData(cardName, card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), card.isNightCard()); + + // variations must have diff file names with additional postfix if (url.getUsesVariousArt()) { url.setDownloadName(createDownloadName(card)); } @@ -444,7 +446,10 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements url.setSplitCard(card.isSplitCard()); url.setType2(isType2); + // main side allCardsUrls.add(url); + + // second side (xmage's set doesn't have info about it, so generate it here) if (card.isDoubleFaced()) { if (card.getSecondSideName() == null || card.getSecondSideName().trim().isEmpty()) { throw new IllegalStateException("Second side card can't have empty name."); diff --git a/Mage.Sets/src/mage/cards/e/EmpoweredAutogenerator.java b/Mage.Sets/src/mage/cards/e/EmpoweredAutogenerator.java index 2ac91e2883..bbb1a078c7 100644 --- a/Mage.Sets/src/mage/cards/e/EmpoweredAutogenerator.java +++ b/Mage.Sets/src/mage/cards/e/EmpoweredAutogenerator.java @@ -71,7 +71,7 @@ class EmpoweredAutogeneratorManaEffect extends ManaEffect { public List getNetMana(Game game, Ability source) { List netMana = new ArrayList<>(); if (game != null) { - Permanent sourcePermanent = game.getState().getPermanent(source.getSourceId()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent != null) { int counters = sourcePermanent.getCounters(game).getCount(CounterType.CHARGE) + 1; // one counter will be added on real mana call if (counters > 0) { @@ -89,7 +89,7 @@ class EmpoweredAutogeneratorManaEffect extends ManaEffect { return mana; } game.getState().processAction(game); - Permanent sourcePermanent = game.getState().getPermanent(source.getSourceId()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent == null) { return mana; } diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index 9fb6f67a31..7563a3c49d 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -357,11 +357,16 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Craterhoof Behemoth", 376, Rarity.MYTHIC, mage.cards.c.CraterhoofBehemoth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Metalwork Colossus", 377, Rarity.RARE, mage.cards.m.MetalworkColossus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Metalwork Colossus", 378, Rarity.RARE, mage.cards.m.MetalworkColossus.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Zndrsplt, Eye of Wisdom", 379, Rarity.RARE, mage.cards.z.ZndrspltEyeOfWisdom.class)); - cards.add(new SetCardInfo("Okaun, Eye of Chaos", 380, Rarity.RARE, mage.cards.o.OkaunEyeOfChaos.class)); - cards.add(new SetCardInfo("Propaganda", 381, Rarity.RARE, mage.cards.p.Propaganda.class)); - cards.add(new SetCardInfo("Stitch in Time", 382, Rarity.RARE, mage.cards.s.StitchInTime.class)); - cards.add(new SetCardInfo("Krark's Thumb", 383, Rarity.RARE, mage.cards.k.KrarksThumb.class)); + cards.add(new SetCardInfo("Zndrsplt, Eye of Wisdom", 379, Rarity.RARE, mage.cards.z.ZndrspltEyeOfWisdom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zndrsplt, Eye of Wisdom", "379b", Rarity.RARE, mage.cards.z.ZndrspltEyeOfWisdom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Okaun, Eye of Chaos", 380, Rarity.RARE, mage.cards.o.OkaunEyeOfChaos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Okaun, Eye of Chaos", "380b", Rarity.RARE, mage.cards.o.OkaunEyeOfChaos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Propaganda", 381, Rarity.RARE, mage.cards.p.Propaganda.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Propaganda", "381b", Rarity.RARE, mage.cards.p.Propaganda.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stitch in Time", 382, Rarity.RARE, mage.cards.s.StitchInTime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stitch in Time", "382b", Rarity.RARE, mage.cards.s.StitchInTime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krark's Thumb", 383, Rarity.RARE, mage.cards.k.KrarksThumb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krark's Thumb", "383b", Rarity.RARE, mage.cards.k.KrarksThumb.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Path to Exile", 477, Rarity.RARE, mage.cards.p.PathToExile.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rhystic Study", 478, Rarity.RARE, mage.cards.r.RhysticStudy.class)); cards.add(new SetCardInfo("Counterflux", 482, Rarity.RARE, mage.cards.c.Counterflux.class)); diff --git a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java index bc5da1dee5..865f505da5 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java @@ -40,6 +40,7 @@ public final class MtgJsonCard { || "flip".equals(layout) || "adventure".equals(layout) || "modal_dfc".equals(layout) + || "reversible_card".equals(layout) // example: Zndrsplt, Eye of Wisdom || "split".equals(layout) || "aftermath".equals(layout) || "meld".equals(layout)) { // mtgjson uses composite names for meld cards, but scryfall uses simple face names diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 03e7a210b6..53da7942e8 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -196,6 +196,7 @@ public class VerifyCardDataTest { skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "UND"); // un-sets don't have full implementation of card variations skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "UST"); // un-sets don't have full implementation of card variations skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "SOI", "Tamiyo's Journal"); // not all variations implemented + skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "SLD", "Zndrsplt, Eye of Wisdom"); // xmage adds additional card for alternative image (second side) // scryfall download sets (missing from scryfall website) @@ -700,10 +701,16 @@ public class VerifyCardDataTest { } for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + if (skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, set.getCode(), card.getName())) { + continue; + } + MtgJsonCard jsonCard = MtgJsonService.cardFromSet(set.getCode(), card.getName(), card.getCardNumber()); if (jsonCard == null) { // see convertMtgJsonToXmageCardNumber for card number convert notation - errorsList.add("Error: scryfall download can't find card from mtgjson " + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + if (!skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, set.getCode(), card.getName())) { + errorsList.add("Error: scryfall download can't find card from mtgjson " + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } continue; } @@ -718,6 +725,16 @@ public class VerifyCardDataTest { errorsList.add("Error: scryfall download can't find non-ascii card link in direct download list " + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + jsonCard.number); } } + + // CHECK: reversible_card must be in direct download list (xmage must have 2 cards with diff image face) + if (jsonCard.layout.equals("reversible_card")) { + String key = ScryfallImageSupportCards.findDirectDownloadKey(set.getCode(), card.getName(), card.getCardNumber()); + if (key != null) { + foundedDirectDownloadKeys.add(key); + } else { + errorsList.add("Error: scryfall download can't find face image of reversible_card in direct download list " + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + jsonCard.number); + } + } } } @@ -731,7 +748,7 @@ public class VerifyCardDataTest { continue; } - // skip non implemented cards list + // skip non-implemented cards list if (CardRepository.instance.findCard(cardName) == null) { continue; } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index bca6a28b90..750a3be431 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -504,7 +504,7 @@ public enum CardRepository { } /** - * Warning, don't use db functions in card's code - it generate heavy db loading in AI simulations. If you + * Warning, don't use db functions in card's code - it generates heavy db loading in AI simulations. If you * need that feature then check for simulation mode. See https://github.com/magefree/mage/issues/7014 * * @param criteria