Tests: added many verify checks for missing cards, names, numbers, download settings:

* check wrong card numbers in sets;
* check missing cards from set;
* check wrong full art settings;
* check missing and unknown sets in scryfall download settings;
* check missing and unknown direct download links in scryfall download settings;
* improved ability text check results;
* removed unused tests for word checks;
This commit is contained in:
Oleg Agafonov 2020-08-22 15:16:30 +04:00
parent b083dd48e6
commit 7aac355f4a
7 changed files with 533 additions and 128 deletions

View file

@ -5,7 +5,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import mage.client.util.CardLanguage; import mage.client.util.CardLanguage;
import mage.util.CardUtil;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.DownloadServiceInfo; import org.mage.plugins.card.dl.DownloadServiceInfo;
import org.mage.plugins.card.images.CardDownloadData; 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 // double faced card
// the front face can be downloaded normally // the front face can be downloaded normally
// the back face is prepared beforehand // the back face is prepared beforehand

View file

@ -7,6 +7,8 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; 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 { public class ScryfallImageSupportCards {
private static final Map<String, String> xmageSetsToScryfall = ImmutableMap.<String, String>builder(). private static final Map<String, String> xmageSetsToScryfall = ImmutableMap.<String, String>builder()
//put("xmage", "scryfall"). .put("8EB", "8ED")
build(); .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<String> supportedSets = new ArraySet<String>() { private static final Set<String> supportedSets = new ArraySet<String>() {
{ {
@ -504,26 +509,214 @@ public class ScryfallImageSupportCards {
// set/card_name/card_number // set/card_name/card_number
// Cards with non-ASCII collector numbers must use direct download (cause xmage uses different 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 // J14
put("J14/Plains/1*", "https://api.scryfall.com/cards/j14/1★/"); put("J14/Plains/1*", "https://api.scryfall.com/cards/j14/1★/");
put("J14/Island/2*", "https://api.scryfall.com/cards/j14/2★/"); put("J14/Island/2*", "https://api.scryfall.com/cards/j14/2★/");
put("J14/Swamp/3*", "https://api.scryfall.com/cards/j14/3★/"); put("J14/Swamp/3*", "https://api.scryfall.com/cards/j14/3★/");
put("J14/Mountain/4*", "https://api.scryfall.com/cards/j14/4★/"); put("J14/Mountain/4*", "https://api.scryfall.com/cards/j14/4★/");
put("J14/Forest/5*", "https://api.scryfall.com/cards/j14/5★/"); 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 // 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/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/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 // PROE
put("PROE/Emrakul, the Aeons Torn/*4", "https://api.scryfall.com/cards/proe/★4/"); 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/"); 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 // PSOI
put("PSOI/Tamiyo's Journal/265s+", "https://api.scryfall.com/cards/psoi/265s†/"); 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 // PWAR
put("PWAR/Ajani, the Greathearted/184s*", "https://api.scryfall.com/cards/pwar/184s★/"); 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★/"); 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/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/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★/"); put("PWAR/Vraska, Swarm's Eminence/236s*", "https://api.scryfall.com/cards/pwar/236s★/");
// SHM
// 8th Edition box set and 9th Edition box set put("SHM/Reflecting Pool/278*", "https://api.scryfall.com/cards/shm/278★/");
// scryfall stores it with one set, by xmage split into two -- 8ED and 8EB, 9ED and 9EB // SOI
put("8EB/Eager Cadet", "https://api.scryfall.com/cards/8ed/S1/"); put("SOI/Tamiyo's Journal/265+", "https://api.scryfall.com/cards/soi/265†/");
put("8EB/Vengeance", "https://api.scryfall.com/cards/8ed/S2/"); put("SOI/Tamiyo's Journal/265+a", "https://api.scryfall.com/cards/soi/265†a/");
put("8EB/Giant Octopus", "https://api.scryfall.com/cards/8ed/S3/"); put("SOI/Tamiyo's Journal/265+b", "https://api.scryfall.com/cards/soi/265†b/");
put("8EB/Sea Eagle", "https://api.scryfall.com/cards/8ed/S4/"); put("SOI/Tamiyo's Journal/265+c", "https://api.scryfall.com/cards/soi/265†c/");
put("8EB/Vizzerdrix", "https://api.scryfall.com/cards/8ed/S5/"); put("SOI/Tamiyo's Journal/265+d", "https://api.scryfall.com/cards/soi/265†d/");
put("8EB/Enormous Baloth", "https://api.scryfall.com/cards/8ed/S6/"); // THB
put("8EB/Silverback Ape", "https://api.scryfall.com/cards/8ed/S7/"); put("THB/Temple of Abandon/347*", "https://api.scryfall.com/cards/thb/347★/");
put("9EB/Eager Cadet", "https://api.scryfall.com/cards/9ed/S1/"); // UNH
put("9EB/Vengeance", "https://api.scryfall.com/cards/9ed/S3/"); put("UNH/Aesthetic Consultation/48*", "https://api.scryfall.com/cards/unh/48★/");
put("9EB/Coral Eel", "https://api.scryfall.com/cards/9ed/S3/"); put("UNH/AWOL/2*", "https://api.scryfall.com/cards/unh/2★/");
put("9EB/Giant Octopus", "https://api.scryfall.com/cards/9ed/S4/"); put("UNH/Bad Ass/49*", "https://api.scryfall.com/cards/unh/49★/");
put("9EB/Index", "https://api.scryfall.com/cards/9ed/S5/"); put("UNH/Blast from the Past/72*", "https://api.scryfall.com/cards/unh/72★/");
put("9EB/Vizzerdrix", "https://api.scryfall.com/cards/9ed/S7/"); put("UNH/Cardpecker/4*", "https://api.scryfall.com/cards/unh/4★/");
put("9EB/Goblin Raider", "https://api.scryfall.com/cards/9ed/S8/"); put("UNH/Emcee/9*", "https://api.scryfall.com/cards/unh/9★/");
put("9EB/Enormous Baloth", "https://api.scryfall.com/cards/9ed/S9/"); put("UNH/Farewell to Arms/56*", "https://api.scryfall.com/cards/unh/56★/");
put("9EB/Spined Wurm", "https://api.scryfall.com/cards/9ed/S10/"); 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; 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 // set/card/number
String linkCode1 = setCode + "/" + cardName + "/" + cardNumber; String linkCode1 = setCode + "/" + cardName + "/" + cardNumber;
if (directDownloadLinks.containsKey(linkCode1)) { if (directDownloadLinks.containsKey(linkCode1)) {
return directDownloadLinks.get(linkCode1); return linkCode1;
} }
// set/card // set/card
String linkCode2 = setCode + "/" + cardName; String linkCode2 = setCode + "/" + cardName;
if (directDownloadLinks.containsKey(linkCode2)) { if (directDownloadLinks.containsKey(linkCode2)) {
return directDownloadLinks.get(linkCode2); return linkCode2;
} }
// default // default
return null; 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<String, String> getDirectDownloadLinks() {
return directDownloadLinks;
}
} }

View file

@ -84,7 +84,7 @@ class DawnEvangelAbility extends DiesCreatureTriggeredAbility {
@Override @Override
public String getRule() { 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."; "return target creature card with converted mana cost 2 or less from your graveyard to your hand.";
} }

View file

@ -8,6 +8,8 @@ public final class MtgJsonCard {
// contains only used fields, if you need more for tests then just add it here // contains only used fields, if you need more for tests then just add it here
public String name; 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 faceName;
public String side; public String side;
@ -28,5 +30,6 @@ public final class MtgJsonCard {
public Integer edhrecRank; public Integer edhrecRank;
public String layout; public String layout;
public boolean isFullArt;
public List<String> printings; // set codes with that card public List<String> printings; // set codes with that card
} }

View file

@ -24,6 +24,8 @@ public final class MtgJsonService {
for (Map.Entry<String, String> entry : mtgJsonToXMageCodes.entrySet()) { for (Map.Entry<String, String> entry : mtgJsonToXMageCodes.entrySet()) {
xMageToMtgJsonCodes.put(entry.getValue(), entry.getKey()); xMageToMtgJsonCodes.put(entry.getValue(), entry.getKey());
} }
xMageToMtgJsonCodes.put("8EB", "8ED");
xMageToMtgJsonCodes.put("9EB", "9ED");
} }
private static Map<String, MtgJsonCard> loadAllCards() throws IOException { private static Map<String, MtgJsonCard> loadAllCards() throws IOException {
@ -68,20 +70,58 @@ public final class MtgJsonService {
return findReference(CardHolder.cards, name); return findReference(CardHolder.cards, name);
} }
public static List<MtgJsonCard> 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<MtgJsonCard> list = cardsFromSet(jsonSetCode, name);
return list.stream()
.filter(c -> convertMtgJsonToXmageCardNumber(c.number).equals(number))
.findFirst().orElse(null);
}
private static <T> T findReference(Map<String, T> reference, String name) { private static <T> T findReference(Map<String, T> reference, String name) {
T ref = reference.get(name); T ref = reference.get(name);
if (ref == null) { if (ref == null) {
name = name.replaceFirst("\\bA[Ee]", "Æ"); //name = name.replaceFirst("\\bA[Ee]", "Æ");
ref = reference.get(name); //ref = reference.get(name);
} }
if (ref == null) { 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); ref = reference.get(name);
} }
return ref; 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 <T> void addAliases(Map<String, T> reference) { private static <T> void addAliases(Map<String, T> reference) {
Map<String, String> aliases = new HashMap<>(); Map<String, String> aliases = new HashMap<>();
for (String name : reference.keySet()) { for (String name : reference.keySet()) {

View file

@ -1,25 +1,12 @@
package mage.verify; package mage.verify;
import com.google.common.base.CharMatcher; 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.ObjectColor;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.MenaceAbility; import mage.abilities.keyword.MenaceAbility;
import mage.abilities.keyword.MultikickerAbility; import mage.abilities.keyword.MultikickerAbility;
import mage.cards.*; import mage.cards.*;
import mage.cards.basiclands.BasicLand;
import mage.cards.decks.DeckCardLists; import mage.cards.decks.DeckCardLists;
import mage.cards.decks.importer.DeckImporter; import mage.cards.decks.importer.DeckImporter;
import mage.cards.repository.CardInfo; 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.mage.plugins.card.images.DownloadPicturesService;
import org.reflections.Reflections; 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 * @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 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 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<String, Set<String>> skipCheckLists = new HashMap<>(); private static final HashMap<String, Set<String>> skipCheckLists = new HashMap<>();
private static final Set<String> subtypesToIgnore = new HashSet<>(); private static final Set<String> subtypesToIgnore = new HashSet<>();
private static final String SKIP_LIST_PT = "PT"; 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_DOUBLE_RARE = "DOUBLE_RARE";
private static final String SKIP_LIST_UNSUPPORTED_SETS = "UNSUPPORTED_SETS"; 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_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 String SKIP_LIST_SAMPLE_DECKS = "SAMPLE_DECKS";
private static final Pattern SHORT_JAVA_STRING = Pattern.compile("(?<=\")[A-Z][a-z]+(?=\")");
static { static {
// skip lists for checks (example: unstable cards with same name may have different stats) // 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, "AMH1"); // Modern Horizons Art Series
skipListAddName(SKIP_LIST_UNSUPPORTED_SETS, "PTG"); // Ponies: The Galloping 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) // scryfall download sets (missing from scryfall website)
skipListCreate(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS); skipListCreate(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS);
skipListAddName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, "SWS"); // Star Wars 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 // sample decks checking - some decks can contains unimplemented cards, so ignore it
// file name must be related to sample-decks folder // file name must be related to sample-decks folder
@ -246,12 +251,11 @@ public class VerifyCardDataTest {
int cardIndex = 0; int cardIndex = 0;
for (Card card : CardScanner.getAllCards()) { for (Card card : CardScanner.getAllCards()) {
cardIndex++; cardIndex++;
Set<String> tokens = findSourceTokens(card.getClass());
if (card.isSplitCard()) { if (card.isSplitCard()) {
check(((SplitCard) card).getLeftHalfCard(), null, cardIndex); check(((SplitCard) card).getLeftHalfCard(), cardIndex);
check(((SplitCard) card).getRightHalfCard(), null, cardIndex); check(((SplitCard) card).getRightHalfCard(), cardIndex);
} else { } else {
check(card, tokens, cardIndex); check(card, cardIndex);
} }
} }
@ -544,13 +548,87 @@ public class VerifyCardDataTest {
} }
@Test @Test
public void test_checkMissingScryfallSettings() { @Ignore // TODO: enable after all missing cards and settings fixes
public void test_checkWrongCardsDataInSets() {
Collection<String> errorsList = new ArrayList<>();
Collection<String> warningsList = new ArrayList<>();
Collection<ExpansionSet> xmageSets = Sets.getInstance().values();
Set<String> 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<String> errorsList = new ArrayList<>(); Collection<String> errorsList = new ArrayList<>();
Collection<ExpansionSet> xmageSets = Sets.getInstance().values(); Collection<ExpansionSet> xmageSets = Sets.getInstance().values();
Set<String> scryfallSets = ScryfallImageSupportCards.getSupportedSets(); Set<String> scryfallSets = ScryfallImageSupportCards.getSupportedSets();
// missing // CHECK: missing sets in supported list
for (ExpansionSet set : xmageSets) { for (ExpansionSet set : xmageSets) {
if (skipListHaveName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, set.getCode())) { if (skipListHaveName(SKIP_LIST_SCRYFALL_DOWNLOAD_SETS, set.getCode())) {
continue; continue;
@ -561,13 +639,64 @@ public class VerifyCardDataTest {
} }
} }
// unknown // CHECK: unknown sets in supported list
for (String scryfallCode : scryfallSets) { for (String scryfallCode : scryfallSets) {
if (xmageSets.stream().noneMatch(e -> e.getCode().equals(scryfallCode))) { if (xmageSets.stream().noneMatch(e -> e.getCode().equals(scryfallCode))) {
errorsList.add("Error: scryfall download unknown setting: " + 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<String> 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<String, String> 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); printMessages(errorsList);
if (errorsList.size() > 0) { if (errorsList.size() > 0) {
Assert.fail("Found scryfall download errors: " + errorsList.size()); 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) // CHECK: wrong basic lands settings (it's for lands search, not booster construct)
Map<String, Boolean> skipLandCheck = new HashMap<>();
for (ExpansionSet set : sets) { for (ExpansionSet set : sets) {
if (skipLandCheck.containsKey(set.getName())) {
continue;
}
Boolean needLand = set.hasBasicLands(); Boolean needLand = set.hasBasicLands();
Boolean foundedLand = false; Boolean foundedLand = false;
Map<String, Integer> foundLandsList = new HashMap<>(); Map<String, Integer> foundLandsList = new HashMap<>();
@ -983,53 +1107,13 @@ public class VerifyCardDataTest {
} }
} }
private Set<String> findSourceTokens(Class c) throws IOException { private void check(Card card, int cardIndex) {
if (!CHECK_SOURCE_TOKENS || BasicLand.class.isAssignableFrom(c)) { MtgJsonCard ref = MtgJsonService.cardFromSet(card.getExpansionSetCode(), card.getName(), card.getCardNumber());
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<String> 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<String> tokens, int cardIndex) {
MtgJsonCard ref = MtgJsonService.card(card.getName());
if (ref == null) { if (ref == null) {
warn(card, "Missing card reference"); warn(card, "Missing card reference");
return; return;
} }
checkAll(card, ref, cardIndex); 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<String> options, String value) { private boolean contains(Collection<String> options, String value) {
@ -1043,7 +1127,6 @@ public class VerifyCardDataTest {
checkSupertypes(card, ref); checkSupertypes(card, ref);
checkTypes(card, ref); checkTypes(card, ref);
checkColors(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); checkBasicLands(card, ref);
checkMissingAbilities(card, ref); checkMissingAbilities(card, ref);
checkWrongSymbolsInRules(card); checkWrongSymbolsInRules(card);
@ -1085,7 +1168,7 @@ public class VerifyCardDataTest {
// fix names (e.g. Urzas to Urza's) // fix names (e.g. Urzas to Urza's)
if (expected != null && expected.contains("Urzas")) { if (expected != null && expected.contains("Urzas")) {
expected = new ArrayList<>(expected); expected = new ArrayList<>(expected);
for (ListIterator<String> it = ((List<String>) expected).listIterator(); it.hasNext();) { for (ListIterator<String> it = ((List<String>) expected).listIterator(); it.hasNext(); ) {
if (it.next().equals("Urzas")) { if (it.next().equals("Urzas")) {
it.set("Urza's"); it.set("Urza's");
} }
@ -1333,9 +1416,11 @@ public class VerifyCardDataTest {
boolean isFine = true; boolean isFine = true;
for (int i = 0; i <= cardRules.length - 1; i++) { for (int i = 0; i <= cardRules.length - 1; i++) {
boolean isAbilityFounded = false; 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)) { if (cardRules[i].equals(refRule)) {
cardRules[i] = "+ " + cardRules[i]; cardRules[i] = "+ " + cardRules[i];
refRules[j] = "+ " + refRules[j];
isAbilityFounded = true; isAbilityFounded = true;
break; 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 // extra message for easy checks
if (!isFine) { if (!isFine) {
System.out.println(); System.out.println();
@ -1361,7 +1454,7 @@ public class VerifyCardDataTest {
System.out.println("ref:"); System.out.println("ref:");
Arrays.sort(refRules); Arrays.sort(refRules);
for (String s : refRules) { for (String s : refRules) {
System.out.println(" " + s); System.out.println(s);
} }
System.out.println(); System.out.println();

View file

@ -77,6 +77,12 @@ public abstract class ExpansionSet implements Serializable {
public CardGraphicInfo getGraphicInfo() { public CardGraphicInfo getGraphicInfo() {
return this.graphicInfo; return this.graphicInfo;
} }
public boolean isFullArt() {
return this.graphicInfo != null
&& this.graphicInfo.getFrameStyle() != null
&& this.graphicInfo.getFrameStyle().isFullArt();
}
} }
protected final List<SetCardInfo> cards = new ArrayList<>(); protected final List<SetCardInfo> cards = new ArrayList<>();