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 04d9659d5f..0e4fb2d0d9 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 @@ -5,7 +5,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import mage.client.util.CardLanguage; -import mage.util.CardUtil; import org.apache.log4j.Logger; import org.mage.plugins.card.dl.DownloadServiceInfo; import org.mage.plugins.card.images.CardDownloadData; @@ -87,19 +86,6 @@ public enum ScryfallImageSource implements CardImageSource { } } - // ARN and POR uses † notation for art variation cards - if (baseUrl == null && card.getUsesVariousArt() && card.getSet().matches("ARN|POR")) { - String collectorId = card.getCollectorId(); - if (collectorId.endsWith("b")) - collectorId = collectorId.replace("b", "†"); - - final String scryfallCollectorId = CardUtil.urlEncode(collectorId); - baseUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet(), isToken) + "/" - + scryfallCollectorId + "/" + localizedCode + "?format=image"; - alternativeUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet(), isToken) + "/" - + scryfallCollectorId + "/" + defaultCode + "?format=image"; - } - // double faced card // the front face can be downloaded normally // the back face is prepared beforehand 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 ece971106e..dd2d409ee1 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 @@ -7,6 +7,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** @@ -14,9 +16,12 @@ import java.util.Set; */ public class ScryfallImageSupportCards { - private static final Map xmageSetsToScryfall = ImmutableMap.builder(). - //put("xmage", "scryfall"). - build(); + private static final Map xmageSetsToScryfall = ImmutableMap.builder() + .put("8EB", "8ED") + .put("9EB", "9ED") + .build(); + static final Pattern REGEXP_DIRECT_KEY_SET_CODE_PATTERN = Pattern.compile("(\\w+)\\/", Pattern.MULTILINE); + static final Pattern REGEXP_DIRECT_KEY_CARD_NAME_PATTERN = Pattern.compile("\\/(.+?)\\/", Pattern.MULTILINE); private static final Set supportedSets = new ArraySet() { { @@ -504,26 +509,214 @@ public class ScryfallImageSupportCards { // set/card_name/card_number // 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 + // 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★/"); + put("10E/Ancestor's Chosen/1*", "https://api.scryfall.com/cards/10e/1★/"); + put("10E/Angel of Mercy/2*", "https://api.scryfall.com/cards/10e/2★/"); + put("10E/Angelic Blessing/3*", "https://api.scryfall.com/cards/10e/3★/"); + put("10E/Angelic Wall/5*", "https://api.scryfall.com/cards/10e/5★/"); + put("10E/Arcane Teachings/188*", "https://api.scryfall.com/cards/10e/188★/"); + put("10E/Ascendant Evincar/127*", "https://api.scryfall.com/cards/10e/127★/"); + put("10E/Avatar of Might/251*", "https://api.scryfall.com/cards/10e/251★/"); + put("10E/Aven Cloudchaser/7*", "https://api.scryfall.com/cards/10e/7★/"); + put("10E/Aven Fisher/68*", "https://api.scryfall.com/cards/10e/68★/"); + put("10E/Aven Windreader/69*", "https://api.scryfall.com/cards/10e/69★/"); + put("10E/Benalish Knight/11*", "https://api.scryfall.com/cards/10e/11★/"); + put("10E/Birds of Paradise/252*", "https://api.scryfall.com/cards/10e/252★/"); + put("10E/Blanchwood Armor/253*", "https://api.scryfall.com/cards/10e/253★/"); + put("10E/Bog Wraith/130*", "https://api.scryfall.com/cards/10e/130★/"); + put("10E/Canopy Spider/254*", "https://api.scryfall.com/cards/10e/254★/"); + put("10E/Cloud Elemental/74*", "https://api.scryfall.com/cards/10e/74★/"); + put("10E/Cloud Sprite/75*", "https://api.scryfall.com/cards/10e/75★/"); + put("10E/Coat of Arms/316*", "https://api.scryfall.com/cards/10e/316★/"); + put("10E/Colossus of Sardia/317*", "https://api.scryfall.com/cards/10e/317★/"); + put("10E/Contaminated Bond/132*", "https://api.scryfall.com/cards/10e/132★/"); + put("10E/Dehydration/78*", "https://api.scryfall.com/cards/10e/78★/"); + put("10E/Dragon Roost/197*", "https://api.scryfall.com/cards/10e/197★/"); + put("10E/Drudge Skeletons/139*", "https://api.scryfall.com/cards/10e/139★/"); + put("10E/Dusk Imp/140*", "https://api.scryfall.com/cards/10e/140★/"); + put("10E/Elvish Champion/261*", "https://api.scryfall.com/cards/10e/261★/"); + put("10E/Faerie Conclave/351*", "https://api.scryfall.com/cards/10e/351★/"); + put("10E/Fear/142*", "https://api.scryfall.com/cards/10e/142★/"); + put("10E/Field Marshal/15*", "https://api.scryfall.com/cards/10e/15★/"); + put("10E/Firebreathing/200*", "https://api.scryfall.com/cards/10e/200★/"); + put("10E/Fog Elemental/85*", "https://api.scryfall.com/cards/10e/85★/"); + put("10E/Furnace Whelp/205*", "https://api.scryfall.com/cards/10e/205★/"); + put("10E/Ghitu Encampment/353*", "https://api.scryfall.com/cards/10e/353★/"); + put("10E/Giant Spider/267*", "https://api.scryfall.com/cards/10e/267★/"); + put("10E/Goblin King/207*", "https://api.scryfall.com/cards/10e/207★/"); + put("10E/Goblin Sky Raider/210*", "https://api.scryfall.com/cards/10e/210★/"); + put("10E/Heart of Light/19*", "https://api.scryfall.com/cards/10e/19★/"); + put("10E/Holy Strength/22*", "https://api.scryfall.com/cards/10e/22★/"); + put("10E/Hypnotic Specter/151*", "https://api.scryfall.com/cards/10e/151★/"); + put("10E/Kamahl, Pit Fighter/214*", "https://api.scryfall.com/cards/10e/214★/"); + put("10E/Leonin Scimitar/331*", "https://api.scryfall.com/cards/10e/331★/"); + put("10E/Lightning Elemental/217*", "https://api.scryfall.com/cards/10e/217★/"); + put("10E/Lord of the Pit/154*", "https://api.scryfall.com/cards/10e/154★/"); + put("10E/Loxodon Warhammer/332*", "https://api.scryfall.com/cards/10e/332★/"); + put("10E/Lure/276*", "https://api.scryfall.com/cards/10e/276★/"); + put("10E/Mahamoti Djinn/90*", "https://api.scryfall.com/cards/10e/90★/"); + put("10E/Mantis Engine/333*", "https://api.scryfall.com/cards/10e/333★/"); + put("10E/March of the Machines/91*", "https://api.scryfall.com/cards/10e/91★/"); + put("10E/Might Weaver/278*", "https://api.scryfall.com/cards/10e/278★/"); + //put("10E/Mind Bend/93*", "https://api.scryfall.com/cards/10e/93★/"); // not implemented + put("10E/Mirri, Cat Warrior/279*", "https://api.scryfall.com/cards/10e/279★/"); + put("10E/Mobilization/29*", "https://api.scryfall.com/cards/10e/29★/"); + put("10E/Molimo, Maro-Sorcerer/280*", "https://api.scryfall.com/cards/10e/280★/"); + put("10E/Mortivore/161*", "https://api.scryfall.com/cards/10e/161★/"); + put("10E/Nekrataal/163*", "https://api.scryfall.com/cards/10e/163★/"); + put("10E/Nightmare/164*", "https://api.scryfall.com/cards/10e/164★/"); + put("10E/Nomad Mythmaker/30*", "https://api.scryfall.com/cards/10e/30★/"); + put("10E/Ornithopter/336*", "https://api.scryfall.com/cards/10e/336★/"); + put("10E/Overgrowth/283*", "https://api.scryfall.com/cards/10e/283★/"); + put("10E/Overrun/284*", "https://api.scryfall.com/cards/10e/284★/"); + put("10E/Pacifism/31*", "https://api.scryfall.com/cards/10e/31★/"); + put("10E/Paladin en-Vec/32*", "https://api.scryfall.com/cards/10e/32★/"); + put("10E/Pariah/33*", "https://api.scryfall.com/cards/10e/33★/"); + put("10E/Persuasion/95*", "https://api.scryfall.com/cards/10e/95★/"); + put("10E/Pincher Beetles/285*", "https://api.scryfall.com/cards/10e/285★/"); + put("10E/Plague Beetle/168*", "https://api.scryfall.com/cards/10e/168★/"); + put("10E/Platinum Angel/339*", "https://api.scryfall.com/cards/10e/339★/"); + put("10E/Primal Rage/286*", "https://api.scryfall.com/cards/10e/286★/"); + put("10E/Rage Weaver/223*", "https://api.scryfall.com/cards/10e/223★/"); + put("10E/Raging Goblin/224*", "https://api.scryfall.com/cards/10e/224★/"); + put("10E/Razormane Masticore/340*", "https://api.scryfall.com/cards/10e/340★/"); + put("10E/Regeneration/290*", "https://api.scryfall.com/cards/10e/290★/"); + put("10E/Reya Dawnbringer/35*", "https://api.scryfall.com/cards/10e/35★/"); + put("10E/Rhox/291*", "https://api.scryfall.com/cards/10e/291★/"); + put("10E/Robe of Mirrors/101*", "https://api.scryfall.com/cards/10e/101★/"); + put("10E/Rock Badger/226*", "https://api.scryfall.com/cards/10e/226★/"); + put("10E/Rootwater Commando/102*", "https://api.scryfall.com/cards/10e/102★/"); + put("10E/Rushwood Dryad/294*", "https://api.scryfall.com/cards/10e/294★/"); + put("10E/Sage Owl/104*", "https://api.scryfall.com/cards/10e/104★/"); + put("10E/Scalpelexis/105*", "https://api.scryfall.com/cards/10e/105★/"); + put("10E/Sengir Vampire/176*", "https://api.scryfall.com/cards/10e/176★/"); + put("10E/Serra Angel/39*", "https://api.scryfall.com/cards/10e/39★/"); + put("10E/Serra's Embrace/40*", "https://api.scryfall.com/cards/10e/40★/"); + put("10E/Severed Legion/177*", "https://api.scryfall.com/cards/10e/177★/"); + put("10E/Shimmering Wings/107*", "https://api.scryfall.com/cards/10e/107★/"); + put("10E/Shivan Dragon/230*", "https://api.scryfall.com/cards/10e/230★/"); + put("10E/Shivan Hellkite/231*", "https://api.scryfall.com/cards/10e/231★/"); + put("10E/Sky Weaver/109*", "https://api.scryfall.com/cards/10e/109★/"); + put("10E/Skyhunter Patrol/41*", "https://api.scryfall.com/cards/10e/41★/"); + put("10E/Skyhunter Prowler/42*", "https://api.scryfall.com/cards/10e/42★/"); + put("10E/Skyhunter Skirmisher/43*", "https://api.scryfall.com/cards/10e/43★/"); + put("10E/Snapping Drake/110*", "https://api.scryfall.com/cards/10e/110★/"); + put("10E/Spark Elemental/237*", "https://api.scryfall.com/cards/10e/237★/"); + put("10E/Spawning Pool/358*", "https://api.scryfall.com/cards/10e/358★/"); + put("10E/Spiketail Hatchling/111*", "https://api.scryfall.com/cards/10e/111★/"); + put("10E/Spirit Link/45*", "https://api.scryfall.com/cards/10e/45★/"); + put("10E/Stampeding Wildebeests/300*", "https://api.scryfall.com/cards/10e/300★/"); + put("10E/Steadfast Guard/48*", "https://api.scryfall.com/cards/10e/48★/"); + put("10E/Suntail Hawk/50*", "https://api.scryfall.com/cards/10e/50★/"); + put("10E/Tangle Spider/303*", "https://api.scryfall.com/cards/10e/303★/"); + put("10E/The Hive/324*", "https://api.scryfall.com/cards/10e/324★/"); + put("10E/Thieving Magpie/115*", "https://api.scryfall.com/cards/10e/115★/"); + put("10E/Threaten/242*", "https://api.scryfall.com/cards/10e/242★/"); + put("10E/Thundering Giant/243*", "https://api.scryfall.com/cards/10e/243★/"); + put("10E/Time Stop/117*", "https://api.scryfall.com/cards/10e/117★/"); + put("10E/Treetop Bracers/304*", "https://api.scryfall.com/cards/10e/304★/"); + put("10E/Treetop Village/361*", "https://api.scryfall.com/cards/10e/361★/"); + put("10E/Troll Ascetic/305*", "https://api.scryfall.com/cards/10e/305★/"); + put("10E/True Believer/53*", "https://api.scryfall.com/cards/10e/53★/"); + put("10E/Tundra Wolves/54*", "https://api.scryfall.com/cards/10e/54★/"); + put("10E/Uncontrollable Anger/244*", "https://api.scryfall.com/cards/10e/244★/"); + put("10E/Unholy Strength/185*", "https://api.scryfall.com/cards/10e/185★/"); + put("10E/Upwelling/306*", "https://api.scryfall.com/cards/10e/306★/"); + put("10E/Vampire Bats/186*", "https://api.scryfall.com/cards/10e/186★/"); + put("10E/Viashino Sandscout/246*", "https://api.scryfall.com/cards/10e/246★/"); + put("10E/Voice of All/56*", "https://api.scryfall.com/cards/10e/56★/"); + put("10E/Wall of Air/124*", "https://api.scryfall.com/cards/10e/124★/"); + put("10E/Wall of Fire/247*", "https://api.scryfall.com/cards/10e/247★/"); + put("10E/Wall of Swords/57*", "https://api.scryfall.com/cards/10e/57★/"); + put("10E/Wall of Wood/309*", "https://api.scryfall.com/cards/10e/309★/"); + put("10E/Whispersilk Cloak/345*", "https://api.scryfall.com/cards/10e/345★/"); + put("10E/Wild Griffin/59*", "https://api.scryfall.com/cards/10e/59★/"); + put("10E/Windborn Muse/60*", "https://api.scryfall.com/cards/10e/60★/"); + put("10E/Youthful Knight/62*", "https://api.scryfall.com/cards/10e/62★/"); + // 4ED + put("4ED/El-Hajjaj/134+", "https://api.scryfall.com/cards/4ed/134†/"); + // 5ED + put("5ED/Game of Chaos/232+", "https://api.scryfall.com/cards/5ed/232†/"); + put("5ED/Inferno/243+", "https://api.scryfall.com/cards/5ed/243†/"); + put("5ED/Ironclaw Curse/244+", "https://api.scryfall.com/cards/5ed/244†/"); + put("5ED/Manabarbs/250+", "https://api.scryfall.com/cards/5ed/250†/"); + put("5ED/Shivan Dragon/267+", "https://api.scryfall.com/cards/5ed/267†/"); + // AER + put("AER/Alley Strangler/52+", "https://api.scryfall.com/cards/aer/52†/"); + put("AER/Dawnfeather Eagle/14+", "https://api.scryfall.com/cards/aer/14†/"); + put("AER/Wrangle/101+", "https://api.scryfall.com/cards/aer/101†/"); + // ARN + put("ARN/Army of Allah/2+", "https://api.scryfall.com/cards/arn/2†/"); + put("ARN/Bird Maiden/37+", "https://api.scryfall.com/cards/arn/37†/"); + put("ARN/Erg Raiders/25+", "https://api.scryfall.com/cards/arn/25†/"); + put("ARN/Fishliver Oil/13+", "https://api.scryfall.com/cards/arn/13†/"); + put("ARN/Giant Tortoise/15+", "https://api.scryfall.com/cards/arn/15†/"); + put("ARN/Hasran Ogress/27+", "https://api.scryfall.com/cards/arn/27†/"); + put("ARN/Moorish Cavalry/7+", "https://api.scryfall.com/cards/arn/7†/"); + put("ARN/Nafs Asp/52+", "https://api.scryfall.com/cards/arn/52†/"); + put("ARN/Oubliette/31+", "https://api.scryfall.com/cards/arn/31†/"); + put("ARN/Piety/8+", "https://api.scryfall.com/cards/arn/8†/"); + put("ARN/Rukh Egg/43+", "https://api.scryfall.com/cards/arn/43†/"); + put("ARN/Stone-Throwing Devils/33+", "https://api.scryfall.com/cards/arn/33†/"); + put("ARN/War Elephant/11+", "https://api.scryfall.com/cards/arn/11†/"); + put("ARN/Wyluli Wolf/55+", "https://api.scryfall.com/cards/arn/55†/"); + // ATQ + put("ATQ/Tawnos's Weaponry/70+", "https://api.scryfall.com/cards/atq/70†/"); + // DD2 + put("DD2/Chandra Nalaar/34*", "https://api.scryfall.com/cards/dd2/34★/"); + put("DD2/Jace Beleren/1*", "https://api.scryfall.com/cards/dd2/1★/"); + // DKM + put("DKM/Icy Manipulator/36*", "https://api.scryfall.com/cards/dkm/36★/"); + put("DKM/Incinerate/14*", "https://api.scryfall.com/cards/dkm/14★/"); + put("DKM/Icy Manipulator/36s", "https://api.scryfall.com/cards/dkm/36★/"); + put("DKM/Incinerate/14s", "https://api.scryfall.com/cards/dkm/14★/"); + // DRK + put("DRK/Fountain of Youth/103+", "https://api.scryfall.com/cards/drk/103†/"); + put("DRK/Gaea's Touch/77+", "https://api.scryfall.com/cards/drk/77†/"); + put("DRK/Runesword/107+", "https://api.scryfall.com/cards/drk/107†/"); // J14 put("J14/Plains/1*", "https://api.scryfall.com/cards/j14/1★/"); put("J14/Island/2*", "https://api.scryfall.com/cards/j14/2★/"); put("J14/Swamp/3*", "https://api.scryfall.com/cards/j14/3★/"); put("J14/Mountain/4*", "https://api.scryfall.com/cards/j14/4★/"); put("J14/Forest/5*", "https://api.scryfall.com/cards/j14/5★/"); + // KLD + put("KLD/Arborback Stomper/142+", "https://api.scryfall.com/cards/kld/142†/"); + put("KLD/Brazen Scourge/107+", "https://api.scryfall.com/cards/kld/107†/"); + put("KLD/Terrain Elemental/272+", "https://api.scryfall.com/cards/kld/272†/"); + put("KLD/Wind Drake/70+", "https://api.scryfall.com/cards/kld/70†/"); + // M20 + put("M20/Corpse Knight/206+", "https://api.scryfall.com/cards/m20/206†/"); + // MIR + put("MIR/Reality Ripple/87+", "https://api.scryfall.com/cards/mir/87†/"); + // ODY + put("ODY/Cephalid Looter/72+", "https://api.scryfall.com/cards/ody/72†/"); + put("ODY/Seafloor Debris/325+", "https://api.scryfall.com/cards/ody/325†/"); + // PAL99 + put("PAL99/Island/3+", "https://api.scryfall.com/cards/pal99/3†/"); // PLS - put("PLS/Tahngarth, Talruum Hero/74*", "https://api.scryfall.com/cards/pls/74★/"); put("PLS/Ertai, the Corrupted/107*", "https://api.scryfall.com/cards/pls/107★/"); put("PLS/Skyship Weatherlight/133*", "https://api.scryfall.com/cards/pls/133★/"); + put("PLS/Tahngarth, Talruum Hero/74*", "https://api.scryfall.com/cards/pls/74★/"); + // POR + put("POR/Anaconda/158+", "https://api.scryfall.com/cards/por/158†/"); + put("POR/Blaze/118+", "https://api.scryfall.com/cards/por/118†/"); + put("POR/Elite Cat Warrior/163+", "https://api.scryfall.com/cards/por/163†/"); + put("POR/Hand of Death/96+", "https://api.scryfall.com/cards/por/96†/"); + put("POR/Monstrous Growth/173+", "https://api.scryfall.com/cards/por/173†/"); + put("POR/Raging Goblin/145+", "https://api.scryfall.com/cards/por/145†/"); + put("POR/Warrior's Charge/38+", "https://api.scryfall.com/cards/por/38†/"); // 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/"); - // PAL99 - put("PAL99/Island/3+", "https://api.scryfall.com/cards/pal99/3†/"); + // + // PPRE + put("PPRE/Beast of Burden/5+", "https://api.scryfall.com/cards/ppre/5†/"); // PSOI put("PSOI/Tamiyo's Journal/265s+", "https://api.scryfall.com/cards/psoi/265s†/"); - // DKM - put("DKM/Icy Manipulator/36s", "https://api.scryfall.com/cards/dkm/36★/"); - put("DKM/Incinerate/14s", "https://api.scryfall.com/cards/dkm/14★/"); // PWAR put("PWAR/Ajani, the Greathearted/184s*", "https://api.scryfall.com/cards/pwar/184s★/"); put("PWAR/Angrath, Captain of Chaos/227s*", "https://api.scryfall.com/cards/pwar/227s★/"); @@ -561,25 +754,81 @@ public class ScryfallImageSupportCards { put("PWAR/Ugin, the Ineffable/2s*", "https://api.scryfall.com/cards/pwar/2s★/"); put("PWAR/Vivien, Champion of the Wilds/180s*", "https://api.scryfall.com/cards/pwar/180s★/"); put("PWAR/Vraska, Swarm's Eminence/236s*", "https://api.scryfall.com/cards/pwar/236s★/"); - - // 8th Edition box set and 9th Edition box set - // scryfall stores it with one set, by xmage split into two -- 8ED and 8EB, 9ED and 9EB - put("8EB/Eager Cadet", "https://api.scryfall.com/cards/8ed/S1/"); - put("8EB/Vengeance", "https://api.scryfall.com/cards/8ed/S2/"); - put("8EB/Giant Octopus", "https://api.scryfall.com/cards/8ed/S3/"); - put("8EB/Sea Eagle", "https://api.scryfall.com/cards/8ed/S4/"); - put("8EB/Vizzerdrix", "https://api.scryfall.com/cards/8ed/S5/"); - put("8EB/Enormous Baloth", "https://api.scryfall.com/cards/8ed/S6/"); - put("8EB/Silverback Ape", "https://api.scryfall.com/cards/8ed/S7/"); - put("9EB/Eager Cadet", "https://api.scryfall.com/cards/9ed/S1/"); - put("9EB/Vengeance", "https://api.scryfall.com/cards/9ed/S3/"); - put("9EB/Coral Eel", "https://api.scryfall.com/cards/9ed/S3/"); - put("9EB/Giant Octopus", "https://api.scryfall.com/cards/9ed/S4/"); - put("9EB/Index", "https://api.scryfall.com/cards/9ed/S5/"); - put("9EB/Vizzerdrix", "https://api.scryfall.com/cards/9ed/S7/"); - put("9EB/Goblin Raider", "https://api.scryfall.com/cards/9ed/S8/"); - put("9EB/Enormous Baloth", "https://api.scryfall.com/cards/9ed/S9/"); - put("9EB/Spined Wurm", "https://api.scryfall.com/cards/9ed/S10/"); + // SHM + put("SHM/Reflecting Pool/278*", "https://api.scryfall.com/cards/shm/278★/"); + // SOI + put("SOI/Tamiyo's Journal/265+", "https://api.scryfall.com/cards/soi/265†/"); + put("SOI/Tamiyo's Journal/265+a", "https://api.scryfall.com/cards/soi/265†a/"); + put("SOI/Tamiyo's Journal/265+b", "https://api.scryfall.com/cards/soi/265†b/"); + put("SOI/Tamiyo's Journal/265+c", "https://api.scryfall.com/cards/soi/265†c/"); + put("SOI/Tamiyo's Journal/265+d", "https://api.scryfall.com/cards/soi/265†d/"); + // THB + put("THB/Temple of Abandon/347*", "https://api.scryfall.com/cards/thb/347★/"); + // UNH + put("UNH/Aesthetic Consultation/48*", "https://api.scryfall.com/cards/unh/48★/"); + put("UNH/AWOL/2*", "https://api.scryfall.com/cards/unh/2★/"); + put("UNH/Bad Ass/49*", "https://api.scryfall.com/cards/unh/49★/"); + put("UNH/Blast from the Past/72*", "https://api.scryfall.com/cards/unh/72★/"); + put("UNH/Cardpecker/4*", "https://api.scryfall.com/cards/unh/4★/"); + put("UNH/Emcee/9*", "https://api.scryfall.com/cards/unh/9★/"); + put("UNH/Farewell to Arms/56*", "https://api.scryfall.com/cards/unh/56★/"); + put("UNH/Frazzled Editor/77*", "https://api.scryfall.com/cards/unh/77★/"); + put("UNH/Gleemax/121*", "https://api.scryfall.com/cards/unh/121★/"); + put("UNH/Goblin Mime/78*", "https://api.scryfall.com/cards/unh/78★/"); + put("UNH/Greater Morphling/34*", "https://api.scryfall.com/cards/unh/34★/"); + put("UNH/Keeper of the Sacred Word/101*", "https://api.scryfall.com/cards/unh/101★/"); + put("UNH/Laughing Hyena/103*", "https://api.scryfall.com/cards/unh/103★/"); + put("UNH/Letter Bomb/122*", "https://api.scryfall.com/cards/unh/122★/"); + put("UNH/Mana Screw/123*", "https://api.scryfall.com/cards/unh/123★/"); + put("UNH/Monkey Monkey Monkey/104*", "https://api.scryfall.com/cards/unh/104★/"); + put("UNH/Mons's Goblin Waiters/82*", "https://api.scryfall.com/cards/unh/82★/"); + put("UNH/Mox Lotus/124*", "https://api.scryfall.com/cards/unh/124★/"); + put("UNH/My First Tome/125*", "https://api.scryfall.com/cards/unh/125★/"); + put("UNH/Old Fogey/106*", "https://api.scryfall.com/cards/unh/106★/"); + put("UNH/Question Elemental?/43*", "https://api.scryfall.com/cards/unh/43★/"); + put("UNH/Richard Garfield, Ph.D./44*", "https://api.scryfall.com/cards/unh/44★/"); + put("UNH/Shoe Tree/109*", "https://api.scryfall.com/cards/unh/109★/"); + put("UNH/Standing Army/20*", "https://api.scryfall.com/cards/unh/20★/"); + put("UNH/Time Machine/128*", "https://api.scryfall.com/cards/unh/128★/"); + put("UNH/Touch and Go/90*", "https://api.scryfall.com/cards/unh/90★/"); + put("UNH/When Fluffy Bunnies Attack/67*", "https://api.scryfall.com/cards/unh/67★/"); + // WAR + put("WAR/Ajani, the Greathearted/184*", "https://api.scryfall.com/cards/war/184★/"); + put("WAR/Angrath, Captain of Chaos/227*", "https://api.scryfall.com/cards/war/227★/"); + put("WAR/Arlinn, Voice of the Pack/150*", "https://api.scryfall.com/cards/war/150★/"); + put("WAR/Ashiok, Dream Render/228*", "https://api.scryfall.com/cards/war/228★/"); + put("WAR/Chandra, Fire Artisan/119*", "https://api.scryfall.com/cards/war/119★/"); + put("WAR/Davriel, Rogue Shadowmage/83*", "https://api.scryfall.com/cards/war/83★/"); + put("WAR/Domri, Anarch of Bolas/191*", "https://api.scryfall.com/cards/war/191★/"); + put("WAR/Dovin, Hand of Control/229*", "https://api.scryfall.com/cards/war/229★/"); + put("WAR/Gideon Blackblade/13*", "https://api.scryfall.com/cards/war/13★/"); + put("WAR/Huatli, the Sun's Heart/230*", "https://api.scryfall.com/cards/war/230★/"); + put("WAR/Jace, Wielder of Mysteries/54*", "https://api.scryfall.com/cards/war/54★/"); + put("WAR/Jaya, Venerated Firemage/135*", "https://api.scryfall.com/cards/war/135★/"); + put("WAR/Jiang Yanggu, Wildcrafter/164*", "https://api.scryfall.com/cards/war/164★/"); + put("WAR/Karn, the Great Creator/1*", "https://api.scryfall.com/cards/war/1★/"); + put("WAR/Kasmina, Enigmatic Mentor/56*", "https://api.scryfall.com/cards/war/56★/"); + put("WAR/Kaya, Bane of the Dead/231*", "https://api.scryfall.com/cards/war/231★/"); + put("WAR/Kiora, Behemoth Beckoner/232*", "https://api.scryfall.com/cards/war/232★/"); + put("WAR/Liliana, Dreadhorde General/97*", "https://api.scryfall.com/cards/war/97★/"); + put("WAR/Nahiri, Storm of Stone/233*", "https://api.scryfall.com/cards/war/233★/"); + put("WAR/Narset, Parter of Veils/61*", "https://api.scryfall.com/cards/war/61★/"); + put("WAR/Nicol Bolas, Dragon-God/207*", "https://api.scryfall.com/cards/war/207★/"); + put("WAR/Nissa, Who Shakes the World/169*", "https://api.scryfall.com/cards/war/169★/"); + put("WAR/Ob Nixilis, the Hate-Twisted/100*", "https://api.scryfall.com/cards/war/100★/"); + put("WAR/Ral, Storm Conduit/211*", "https://api.scryfall.com/cards/war/211★/"); + put("WAR/Saheeli, Sublime Artificer/234*", "https://api.scryfall.com/cards/war/234★/"); + put("WAR/Samut, Tyrant Smasher/235*", "https://api.scryfall.com/cards/war/235★/"); + put("WAR/Sarkhan the Masterless/143*", "https://api.scryfall.com/cards/war/143★/"); + put("WAR/Sorin, Vengeful Bloodlord/217*", "https://api.scryfall.com/cards/war/217★/"); + put("WAR/Tamiyo, Collector of Tales/220*", "https://api.scryfall.com/cards/war/220★/"); + put("WAR/Teferi, Time Raveler/221*", "https://api.scryfall.com/cards/war/221★/"); + put("WAR/Teyo, the Shieldmage/32*", "https://api.scryfall.com/cards/war/32★/"); + put("WAR/The Wanderer/37*", "https://api.scryfall.com/cards/war/37★/"); + put("WAR/Tibalt, Rakish Instigator/146*", "https://api.scryfall.com/cards/war/146★/"); + 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★/"); } }; @@ -591,21 +840,49 @@ public class ScryfallImageSupportCards { return supportedSets; } - public static String findDirectDownloadLink(String setCode, String cardName, String cardNumber) { - + public static String findDirectDownloadKey(String setCode, String cardName, String cardNumber) { // set/card/number String linkCode1 = setCode + "/" + cardName + "/" + cardNumber; if (directDownloadLinks.containsKey(linkCode1)) { - return directDownloadLinks.get(linkCode1); + return linkCode1; } // set/card String linkCode2 = setCode + "/" + cardName; if (directDownloadLinks.containsKey(linkCode2)) { - return directDownloadLinks.get(linkCode2); + return linkCode2; } // default return null; } + + public static String findDirectDownloadLink(String setCode, String cardName, String cardNumber) { + String key = findDirectDownloadKey(setCode, cardName, cardNumber); + return directDownloadLinks.get(key); + } + + public static String extractSetCodeFromDirectKey(String key) { + // from: 8EB/Giant Octopus + // to: 8EB + Matcher matcher = REGEXP_DIRECT_KEY_SET_CODE_PATTERN.matcher(key); + if (matcher.find()) { + return matcher.group(1); + } + return ""; + } + + public static String extractCardNameFromDirectKey(String key) { + // from: 8EB/Giant Octopus/ + // to: Giant Octopus + Matcher matcher = REGEXP_DIRECT_KEY_CARD_NAME_PATTERN.matcher(key + "/"); // add / for regexp workaround + if (matcher.find()) { + return matcher.group(1); + } + return ""; + } + + public static Map getDirectDownloadLinks() { + return directDownloadLinks; + } } diff --git a/Mage.Sets/src/mage/cards/d/DawnEvangel.java b/Mage.Sets/src/mage/cards/d/DawnEvangel.java index 6edc5213fb..2b17d77d61 100644 --- a/Mage.Sets/src/mage/cards/d/DawnEvangel.java +++ b/Mage.Sets/src/mage/cards/d/DawnEvangel.java @@ -84,7 +84,7 @@ class DawnEvangelAbility extends DiesCreatureTriggeredAbility { @Override public String getRule() { - return "Whenever a creature dies, if an Aura you control was attached to it, " + + return "Whenever a creature dies, if an Aura you controlled was attached to it, " + "return target creature card with converted mana cost 2 or less from your graveyard to your hand."; } 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 49f7d0756f..5efe55a622 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java @@ -8,6 +8,8 @@ public final class MtgJsonCard { // contains only used fields, if you need more for tests then just add it here public String name; + public String asciiName; // mtgjson uses it for some cards like El-Hajjaj + public String number; // from sets source only, see https://mtgjson.com/data-models/card/ public String faceName; public String side; @@ -28,5 +30,6 @@ public final class MtgJsonCard { public Integer edhrecRank; public String layout; + public boolean isFullArt; public List printings; // set codes with that card } diff --git a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonService.java b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonService.java index 5cd6438498..2476850ce2 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonService.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonService.java @@ -24,6 +24,8 @@ public final class MtgJsonService { for (Map.Entry entry : mtgJsonToXMageCodes.entrySet()) { xMageToMtgJsonCodes.put(entry.getValue(), entry.getKey()); } + xMageToMtgJsonCodes.put("8EB", "8ED"); + xMageToMtgJsonCodes.put("9EB", "9ED"); } private static Map loadAllCards() throws IOException { @@ -68,20 +70,58 @@ public final class MtgJsonService { return findReference(CardHolder.cards, name); } + public static List cardsFromSet(String setCode, String name) { + MtgJsonSet set = findReference(SetHolder.sets, setCode); + if (set == null) { + return new ArrayList<>(); + } + + String needName = convertXmageToMtgJsonCardName(name); + return set.cards.stream() + .filter(c -> needName.equals(c.name) || needName.equals(c.asciiName) || needName.equals(c.faceName)) + .collect(Collectors.toList()); + } + + public static MtgJsonCard cardFromSet(String setCode, String name, String number) { + String jsonSetCode = xMageToMtgJsonCodes.getOrDefault(setCode, setCode); + List list = cardsFromSet(jsonSetCode, name); + return list.stream() + .filter(c -> convertMtgJsonToXmageCardNumber(c.number).equals(number)) + .findFirst().orElse(null); + } + private static T findReference(Map reference, String name) { T ref = reference.get(name); if (ref == null) { - name = name.replaceFirst("\\bA[Ee]", "Æ"); - ref = reference.get(name); + //name = name.replaceFirst("\\bA[Ee]", "Æ"); + //ref = reference.get(name); } if (ref == null) { - name = name.replace("'", "\""); // for Kongming, "Sleeping Dragon" & Pang Tong, "Young Phoenix" + //name = name.replace("'", "\""); // for Kongming, "Sleeping Dragon" & Pang Tong, "Young Phoenix" + //ref = reference.get(name); + } + if (ref == null) { + name = convertXmageToMtgJsonCardName(name); ref = reference.get(name); } return ref; } + private static String convertXmageToMtgJsonCardName(String cardName) { + return cardName; + //.replaceFirst("Aether", "Æther") + //.replace("'", "\""); // for Kongming, "Sleeping Dragon" & Pang Tong, "Young Phoenix" + } + + private static String convertMtgJsonToXmageCardNumber(String number) { + // card number notation must be same for all sets (replace non-ascii symbols) + // so your set generation tools must use same replaces + return number + .replace("★", "*") + .replace("†", "+"); + } + private static void addAliases(Map reference) { Map aliases = new HashMap<>(); for (String name : reference.keySet()) { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index e18961237f..b87f45a159 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1,25 +1,12 @@ package mage.verify; import com.google.common.base.CharMatcher; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.keyword.MenaceAbility; import mage.abilities.keyword.MultikickerAbility; import mage.cards.*; -import mage.cards.basiclands.BasicLand; import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; import mage.cards.repository.CardInfo; @@ -47,6 +34,16 @@ import org.mage.plugins.card.images.CardDownloadData; import org.mage.plugins.card.images.DownloadPicturesService; import org.reflections.Reflections; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.stream.Collectors; + /** * @author JayDi85 */ @@ -57,9 +54,6 @@ public class VerifyCardDataTest { private static final String FULL_ABILITIES_CHECK_SET_CODE = "THB"; // check all abilities and output cards with wrong abilities texts; private static final boolean AUTO_FIX_SAMPLE_DECKS = false; // debug only: fix sample decks if it contains errors like wrong card numbers - // right now this is very noisy, and not useful enough to make any assertions on - private static final boolean CHECK_SOURCE_TOKENS = false; - private static final HashMap> skipCheckLists = new HashMap<>(); private static final Set subtypesToIgnore = new HashSet<>(); private static final String SKIP_LIST_PT = "PT"; @@ -73,8 +67,8 @@ public class VerifyCardDataTest { private static final String SKIP_LIST_DOUBLE_RARE = "DOUBLE_RARE"; private static final String SKIP_LIST_UNSUPPORTED_SETS = "UNSUPPORTED_SETS"; private static final String SKIP_LIST_SCRYFALL_DOWNLOAD_SETS = "SCRYFALL_DOWNLOAD_SETS"; + private static final String SKIP_LIST_WRONG_CARD_NUMBERS = "WRONG_CARD_NUMBERS"; private static final String SKIP_LIST_SAMPLE_DECKS = "SAMPLE_DECKS"; - private static final Pattern SHORT_JAVA_STRING = Pattern.compile("(?<=\")[A-Z][a-z]+(?=\")"); static { // skip lists for checks (example: unstable cards with same name may have different stats) @@ -190,9 +184,20 @@ public class VerifyCardDataTest { skipListAddName(SKIP_LIST_UNSUPPORTED_SETS, "AMH1"); // Modern Horizons Art Series skipListAddName(SKIP_LIST_UNSUPPORTED_SETS, "PTG"); // Ponies: The Galloping + // wrond card numbers skip list + skipListCreate(SKIP_LIST_WRONG_CARD_NUMBERS); + skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "SWS"); // Star Wars + skipListAddName(SKIP_LIST_WRONG_CARD_NUMBERS, "POR"); // Portal, TODO: remove after bug fixed https://github.com/mtgjson/mtgjson/issues/660 + 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 + + // scryfall download sets (missing from scryfall website) skipListCreate(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS); skipListAddName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, "SWS"); // Star Wars + //skipListAddName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, "8EB"); // Eighth Edition Box - inner xmage set, split from 8ED + //skipListAddName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, "9EB"); // Ninth Edition Box - inner xmage set, split from 9ED // sample decks checking - some decks can contains unimplemented cards, so ignore it // file name must be related to sample-decks folder @@ -246,12 +251,11 @@ public class VerifyCardDataTest { int cardIndex = 0; for (Card card : CardScanner.getAllCards()) { cardIndex++; - Set tokens = findSourceTokens(card.getClass()); if (card.isSplitCard()) { - check(((SplitCard) card).getLeftHalfCard(), null, cardIndex); - check(((SplitCard) card).getRightHalfCard(), null, cardIndex); + check(((SplitCard) card).getLeftHalfCard(), cardIndex); + check(((SplitCard) card).getRightHalfCard(), cardIndex); } else { - check(card, tokens, cardIndex); + check(card, cardIndex); } } @@ -544,13 +548,87 @@ public class VerifyCardDataTest { } @Test - public void test_checkMissingScryfallSettings() { + @Ignore // TODO: enable after all missing cards and settings fixes + public void test_checkWrongCardsDataInSets() { + Collection errorsList = new ArrayList<>(); + Collection warningsList = new ArrayList<>(); + Collection xmageSets = Sets.getInstance().values(); + Set foundedJsonCards = new HashSet<>(); + + // CHECK: wrong card numbers + for (ExpansionSet set : xmageSets) { + if (skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, set.getCode())) { + continue; + } + + for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + MtgJsonCard jsonCard = MtgJsonService.cardFromSet(set.getCode(), card.getName(), card.getCardNumber()); + if (jsonCard == null) { + // see convertMtgJsonToXmageCardNumber for card number convert notation + errorsList.add("Error: unknown card number, use standard number notations: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + continue; + } + + // index for missing cards + String code = MtgJsonService.xMageToMtgJsonCodes.getOrDefault(set.getCode(), set.getCode()) + " - " + jsonCard.name + " - " + jsonCard.number; + foundedJsonCards.add(code); + + // CHECK: must use full art setting + if (jsonCard.isFullArt && !card.isFullArt()) { + errorsList.add("Error: card must use full art setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + + // CHECK: must not use full art setting + if (!jsonCard.isFullArt && card.isFullArt()) { + errorsList.add("Error: card must NOT use full art setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + } + } + + // CHECK: missing cards from set + for (MtgJsonSet jsonSet : MtgJsonService.sets().values()) { + if (skipListHaveName(SKIP_LIST_UNSUPPORTED_SETS, jsonSet.code) + || skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, jsonSet.code)) { + continue; + } + + ExpansionSet xmageSet = Sets.findSet(jsonSet.code); + if (xmageSet == null) { + warningsList.add("Warning: found un-implemented set from mtgjson database: " + jsonSet.code + " - " + jsonSet.name + " - " + jsonSet.releaseDate); + continue; + } + + for (MtgJsonCard jsonCard : jsonSet.cards) { + String code = jsonSet.code + " - " + jsonCard.name + " - " + jsonCard.number; + if (!foundedJsonCards.contains(code)) { + if (CardRepository.instance.findCard(jsonCard.name) == null) { + // ignore non-implemented cards + continue; + } + errorsList.add("Error: missing card from xmage's set: " + jsonSet.code + " - " + jsonCard.name + " - " + jsonCard.number); + } + } + } + + printMessages(warningsList); + printMessages(errorsList); + if (errorsList.size() > 0) { + Assert.fail("Found wrong cards data in sets, errors: " + errorsList.size()); + } + } + + @Test + @Ignore // TODO: enable after all missing cards and settings fixes + public void test_checkMissingScryfallSettingsAndCardNumbers() { Collection errorsList = new ArrayList<>(); Collection xmageSets = Sets.getInstance().values(); Set scryfallSets = ScryfallImageSupportCards.getSupportedSets(); - // missing + // CHECK: missing sets in supported list for (ExpansionSet set : xmageSets) { if (skipListHaveName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, set.getCode())) { continue; @@ -561,13 +639,64 @@ public class VerifyCardDataTest { } } - // unknown + // CHECK: unknown sets in supported list for (String scryfallCode : scryfallSets) { if (xmageSets.stream().noneMatch(e -> e.getCode().equals(scryfallCode))) { errorsList.add("Error: scryfall download unknown setting: " + scryfallCode); } } + // card numbers + // all cards with non-ascii numbers must be downloaded by direct links (api) + Set foundedDirectDownloadKeys = new HashSet<>(); + for (ExpansionSet set : xmageSets) { + if (skipListHaveName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, set.getCode()) + || skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, set.getCode())) { + continue; + } + + for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + 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()); + continue; + } + + // CHECK: non-ascii numbers and direct download list + if (!CharMatcher.ascii().matchesAllOf(jsonCard.number)) { + // non-ascii numbers + // xmage card numbers can't have non-ascii numbers (it checked by test_checkMissingCardData) + 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 non-ascii card link in direct download list " + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + jsonCard.number); + } + } + } + } + + // CHECK: unknown direct download links + for (Map.Entry direct : ScryfallImageSupportCards.getDirectDownloadLinks().entrySet()) { + // skip custom sets + String setCode = ScryfallImageSupportCards.extractSetCodeFromDirectKey(direct.getKey()); + String cardName = ScryfallImageSupportCards.extractCardNameFromDirectKey(direct.getKey()); + if (skipListHaveName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, setCode) + || skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, setCode)) { + continue; + } + + // skip non implemented cards list + if (CardRepository.instance.findCard(cardName) == null) { + continue; + } + + if (!foundedDirectDownloadKeys.contains(direct.getKey())) { + errorsList.add("Error: scryfall download found unknown direct download link " + direct.getKey() + " - " + direct.getValue()); + } + } + printMessages(errorsList); if (errorsList.size() > 0) { Assert.fail("Found scryfall download errors: " + errorsList.size()); @@ -646,12 +775,7 @@ public class VerifyCardDataTest { } // CHECK: wrong basic lands settings (it's for lands search, not booster construct) - Map skipLandCheck = new HashMap<>(); for (ExpansionSet set : sets) { - if (skipLandCheck.containsKey(set.getName())) { - continue; - } - Boolean needLand = set.hasBasicLands(); Boolean foundedLand = false; Map foundLandsList = new HashMap<>(); @@ -983,53 +1107,13 @@ public class VerifyCardDataTest { } } - private Set findSourceTokens(Class c) throws IOException { - if (!CHECK_SOURCE_TOKENS || BasicLand.class.isAssignableFrom(c)) { - return null; - } - String path = "../Mage.Sets/src/" + c.getName().replace(".", "/") + ".java"; - try { - String source = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8); - Matcher matcher = SHORT_JAVA_STRING.matcher(source); - Set tokens = new HashSet<>(); - while (matcher.find()) { - tokens.add(matcher.group()); - } - return tokens; - } catch (NoSuchFileException e) { - System.out.println("failed to read " + path); - return null; - } - } - - private void check(Card card, Set tokens, int cardIndex) { - MtgJsonCard ref = MtgJsonService.card(card.getName()); + private void check(Card card, int cardIndex) { + MtgJsonCard ref = MtgJsonService.cardFromSet(card.getExpansionSetCode(), card.getName(), card.getCardNumber()); if (ref == null) { warn(card, "Missing card reference"); return; } checkAll(card, ref, cardIndex); - if (tokens != null) { - MtgJsonCard ref2 = null; - if (card.isFlipCard()) { - ref2 = MtgJsonService.card(card.getFlipCardName()); - } - for (String token : tokens) { - if (!(token.equals(card.getName()) - || containsInTypesOrText(ref, token) - || containsInTypesOrText(ref, token.toLowerCase(Locale.ENGLISH)) - || (ref2 != null && (containsInTypesOrText(ref2, token) || containsInTypesOrText(ref2, token.toLowerCase(Locale.ENGLISH)))))) { - System.out.println("unexpected token " + token + " in " + card); - } - } - } - } - - private boolean containsInTypesOrText(MtgJsonCard ref, String token) { - return contains(ref.types, token) - || contains(ref.subtypes, token) - || contains(ref.supertypes, token) - || ref.text.contains(token); } private boolean contains(Collection options, String value) { @@ -1043,7 +1127,6 @@ public class VerifyCardDataTest { checkSupertypes(card, ref); checkTypes(card, ref); checkColors(card, ref); - //checkNumbers(card, ref); // TODO: load data from AllPrintings.json and check it (allcards.json do not have card numbers) checkBasicLands(card, ref); checkMissingAbilities(card, ref); checkWrongSymbolsInRules(card); @@ -1085,7 +1168,7 @@ public class VerifyCardDataTest { // fix names (e.g. Urza’s to Urza's) if (expected != null && expected.contains("Urza’s")) { expected = new ArrayList<>(expected); - for (ListIterator it = ((List) expected).listIterator(); it.hasNext();) { + for (ListIterator it = ((List) expected).listIterator(); it.hasNext(); ) { if (it.next().equals("Urza’s")) { it.set("Urza's"); } @@ -1144,9 +1227,9 @@ public class VerifyCardDataTest { // ability/effect must have description or not boolean mustCheck = card.getAbilities().containsClass(objectClass) || card.getAbilities().stream() - .map(Ability::getAllEffects) - .flatMap(Collection::stream) - .anyMatch(effect -> effect.getClass().isAssignableFrom(objectClass)); + .map(Ability::getAllEffects) + .flatMap(Collection::stream) + .anyMatch(effect -> effect.getClass().isAssignableFrom(objectClass)); mustCheck = false; // TODO: enable and fix all problems with effect and ability hints if (mustCheck) { boolean needHint = ref.text.contains(objectHint); @@ -1333,9 +1416,11 @@ public class VerifyCardDataTest { boolean isFine = true; for (int i = 0; i <= cardRules.length - 1; i++) { boolean isAbilityFounded = false; - for (String refRule : refRules) { + for (int j = 0; j <= refRules.length - 1; j++) { + String refRule = refRules[j]; if (cardRules[i].equals(refRule)) { cardRules[i] = "+ " + cardRules[i]; + refRules[j] = "+ " + refRules[j]; isAbilityFounded = true; break; } @@ -1348,6 +1433,14 @@ public class VerifyCardDataTest { } } + // mark ref rules as unknown + for (int j = 0; j <= refRules.length - 1; j++) { + String refRule = refRules[j]; + if (!refRule.startsWith("+ ")) { + refRules[j] = "- " + refRules[j]; + } + } + // extra message for easy checks if (!isFine) { System.out.println(); @@ -1361,7 +1454,7 @@ public class VerifyCardDataTest { System.out.println("ref:"); Arrays.sort(refRules); for (String s : refRules) { - System.out.println(" " + s); + System.out.println(s); } System.out.println(); diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 1f26c9a428..56b8bd0951 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -77,6 +77,12 @@ public abstract class ExpansionSet implements Serializable { public CardGraphicInfo getGraphicInfo() { return this.graphicInfo; } + + public boolean isFullArt() { + return this.graphicInfo != null + && this.graphicInfo.getFrameStyle() != null + && this.graphicInfo.getFrameStyle().isFullArt(); + } } protected final List cards = new ArrayList<>();