From 1d8e7a0cb31874d538f8640389bc2bda71d710b5 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 26 Jul 2019 18:39:38 +0200 Subject: [PATCH 01/10] Add Download HQ Card Pictures from Scryfall --- .../home/settings/CSubmenuDownloaders.java | 6 + .../home/settings/VSubmenuDownloaders.java | 5 + forge-gui/res/languages/de-DE.properties | 2 + forge-gui/res/languages/en-US.properties | 2 + forge-gui/res/languages/es-es.properties | 2 + .../forge/download/GuiDownloadPicturesHQ.java | 356 ++++++++++++++++++ .../forge/download/GuiDownloadService.java | 9 +- .../java/forge/properties/ForgeConstants.java | 2 + 8 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java index 79a7f188bb8..53a0814f8c3 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java @@ -28,6 +28,11 @@ public enum CSubmenuDownloaders implements ICDoc { new GuiDownloader(new GuiDownloadPicturesLQ()).show(); } }; + private final UiCommand cmdPicDownloadHQ = new UiCommand() { + @Override public void run() { + new GuiDownloader(new GuiDownloadPicturesHQ()).show(); + } + }; private final UiCommand cmdSetDownload = new UiCommand() { @Override public void run() { new GuiDownloader(new GuiDownloadSetPicturesLQ()).show(); @@ -80,6 +85,7 @@ public enum CSubmenuDownloaders implements ICDoc { public void initialize() { final VSubmenuDownloaders view = VSubmenuDownloaders.SINGLETON_INSTANCE; view.setDownloadPicsCommand(cmdPicDownload); + view.setDownloadPicsHQCommand(cmdPicDownloadHQ); view.setDownloadSetPicsCommand(cmdSetDownload); view.setDownloadQuestImagesCommand(cmdQuestImages); view.setDownloadAchievementImagesCommand(cmdAchievementImages); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java index 02cf1bfbb62..44c3e2230b9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java @@ -57,6 +57,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { private final FLabel btnDownloadSetPics = _makeButton(localizer.getMessage("btnDownloadSetPics")); private final FLabel btnDownloadPics = _makeButton(localizer.getMessage("btnDownloadPics")); + private final FLabel btnDownloadPicsHQ = _makeButton(localizer.getMessage("btnDownloadPicsHQ")); private final FLabel btnDownloadQuestImages = _makeButton(localizer.getMessage("btnDownloadQuestImages")); private final FLabel btnDownloadAchievementImages = _makeButton(localizer.getMessage("btnDownloadAchievementImages")); private final FLabel btnReportBug = _makeButton(localizer.getMessage("btnReportBug")); @@ -82,6 +83,9 @@ public enum VSubmenuDownloaders implements IVSubmenu { pnlContent.add(btnDownloadPics, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPics")), constraintsLBL); + pnlContent.add(btnDownloadPicsHQ, constraintsBTN); + pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPicsHQ")), constraintsLBL); + pnlContent.add(btnDownloadSetPics, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSetPics")), constraintsLBL); @@ -159,6 +163,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { } public void setDownloadPicsCommand(UiCommand command) { btnDownloadPics.setCommand(command); } + public void setDownloadPicsHQCommand(UiCommand command) { btnDownloadPicsHQ.setCommand(command); } public void setDownloadSetPicsCommand(UiCommand command) { btnDownloadSetPics.setCommand(command); } public void setDownloadQuestImagesCommand(UiCommand command) { btnDownloadQuestImages.setCommand(command); } public void setDownloadAchievementImagesCommand(UiCommand command) { btnDownloadAchievementImages.setCommand(command); } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 5cfecfde4be..66edc2f8d10 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -179,6 +179,7 @@ Achievements=Erfolge # VSubmenuDownloaders.java btnDownloadSetPics=Bilder(LQ) Sets herunterladen btnDownloadPics=Bilder(LQ) Karten herunterladen +btnDownloadPicsHQ=Bilder(HQ) Karten herunterladen (Sehr langsam!) btnDownloadQuestImages=Bilder für Quests herunterladen btnDownloadAchievementImages=Bilder für Erfolge herunterladen btnReportBug=Einen Fehler melden @@ -189,6 +190,7 @@ btnHowToPlay=Wie man spielt btnDownloadPrices=Kartenpreise herunterladen btnLicensing=Lizenzhinweis lblDownloadPics=Lädt ein Standardbild pro Karte. +lblDownloadPicsHQ=Lädt ein Standardbild (HQ) pro Karte. lblDownloadSetPics=Lädt alle Bilder pro Karte. Eines für jedes Set, in welchem die Karte auftauchte. lblDownloadQuestImages=Lädt die Bilder für den Quest-Modus. lblDownloadAchievementImages=Lädt die Bilder zu den möglichen Erfolgen. Verschönert die Trophäensammlung. diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index cedefb81c72..09e748829d7 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -178,6 +178,7 @@ KeyboardShortcuts=Keyboard Shortcuts Achievements=Achievements # VSubmenuDownloaders.java btnDownloadSetPics=Download LQ Set Pictures +btnDownloadPicsHQ=Download HQ Card Pictures (Very Slow!) btnDownloadPics=Download LQ Card Pictures btnDownloadQuestImages=Download Quest Images btnDownloadAchievementImages=Download Achievement Images @@ -189,6 +190,7 @@ btnHowToPlay=How To Play btnDownloadPrices=Download Card Prices btnLicensing=License Details lblDownloadPics=Download default card picture for each card. +lblDownloadPicsHQ=Download default card HQ picture for each card. lblDownloadSetPics=Download all pictures of each card (one for each set the card appeared in) lblDownloadQuestImages=Download tokens and icons used in Quest mode. lblDownloadAchievementImages=Download achievement images to really make your trophies stand out. diff --git a/forge-gui/res/languages/es-es.properties b/forge-gui/res/languages/es-es.properties index 793a7c577ee..dfeda5e8963 100644 --- a/forge-gui/res/languages/es-es.properties +++ b/forge-gui/res/languages/es-es.properties @@ -179,6 +179,7 @@ Achievements=Logros # VSubmenuDownloaders.java btnDownloadSetPics=Descargar todos los Sets de Cartas btnDownloadPics=Descargar todas las Cartas +btnDownloadPicsHQ=Descargar todas las Cartas en calidad alta (Muy lento!) btnDownloadQuestImages=Descargar Imágenes del modo Quest btnDownloadAchievementImages=Descagar Imágenes de los Logros btnReportBug=Reportar un error @@ -189,6 +190,7 @@ btnHowToPlay=Cómo jugar (Inglés) btnDownloadPrices=Descargar los precios de las cartas btnLicensing=Detalles de la licencia lblDownloadPics=Descargar la imagen de la carta por defecto para cada carta. +lblDownloadPicsHQ=Descargar la imagen en calidad alta de la carta por defecto para cada carta. lblDownloadSetPics=Descargue todas las imágenes de cada carta (una por cada set donde apareció la carta) lblDownloadQuestImages=Descarga fichas e íconos utilizados en el modo Quest. lblDownloadAchievementImages=Descarga imágenes de logros para que tus trofeos realmente destaquen. diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java new file mode 100644 index 00000000000..8e3e5b95802 --- /dev/null +++ b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesHQ.java @@ -0,0 +1,356 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.download; + +import forge.item.PaperCard; +import forge.model.FModel; +import forge.properties.ForgeConstants; +import forge.util.ImageUtil; + +import java.io.File; +import java.util.*; + +public class GuiDownloadPicturesHQ extends GuiDownloadService { + final Map downloads = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Set existingSets; + ArrayList existingImages; + + @Override + public String getTitle() { + return "Download HQ Card Pictures"; + } + + @Override + protected final Map getNeededFiles() { + File f = new File(ForgeConstants.CACHE_CARD_PICS_DIR); + existingImages = new ArrayList(Arrays.asList(f.list())); + existingSets = retrieveManifestDirectory(); + + for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) { + addDLObject(c, false); + if (ImageUtil.hasBackFacePicture(c)) { + addDLObject(c, true); + } + } + + for (final PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) { + addDLObject(c, false); + } + + // Add missing tokens to the list of things to download. + addMissingItems(downloads, ForgeConstants.IMAGE_LIST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR); + + return downloads; + } + + private void addDLObject(final PaperCard c, final boolean backFace) { + final String imageKey = ImageUtil.getImageKey(c, backFace, false); + final String destPath = ForgeConstants.CACHE_CARD_PICS_DIR + imageKey + ".jpg"; + + if (existingImages.contains(imageKey + ".jpg")) { + return; + } + + if (downloads.containsKey(destPath)) { + return; + } + + String setCode = c.getEdition(); + String cardname = imageKey.replace(".full", ""); + + switch(setCode) { + case "CFX": setCode="CON"; break; + case "COM": setCode="CMD"; break; + case "FVE": setCode="V09"; break; + case "FVL": setCode="V11"; break; + case "FVR": setCode="V10"; break; + case "MED": setCode="ME1"; break; + case "MPS_AKH": setCode="AKH"; break; + case "MPS_KLD": setCode="MPS"; break; + case "MPS_RNA": setCode="MED"; break; + case "PDS": setCode="H09"; break; + case "PO2": setCode="P02"; break; + case "UGF": setCode="UGIN"; break; + } + + setCode = getManualCode(cardname, setCode); + + cardname = cardname.replace(" ", "+"); + cardname = cardname.replace("'", ""); + String scryfallurl = ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + "named?fuzzy=" + cardname; + if(!setCode.equals("???")) scryfallurl += "&set=" + setCode.toLowerCase(); + scryfallurl += "&format=image"; + + downloads.put(destPath, scryfallurl); + } + + private String getManualCode(String cardname, String originalSetCode) { + String setCode=originalSetCode; + + switch(cardname) { + case "Daretti, Ingenious Iconoclast": + case "Elspeth, Knight-Errant": + case "Garruk, Apex Predator": + case "Jace, the Mind Sculptor": + case "Liliana, the Last Hope": + case "Nahiri, the Harbinger": + case "Nicol Bolas, Planeswalker": + case "Sarkhan Unbroken": + case "Teferi, Hero of Dominaria": + case "Tezzeret, Agent of Bolas": + case "Tezzeret the Seeker": + case "Ugin, the Spirit Dragon": setCode="MED"; break; + + case "Attrition": + case "Boil": + case "Capsize": + case "Counterbalance": + case "Daze": + case "Desolation Angel": + case "Divert": + case "Forbid": + case "Force of Will": + case "Loyal Retainers": + case "Mind Twist": + case "No Mercy": + case "Opposition": + case "Shatterstorm": + case "Slaughter Pact": + case "Stifle": + case "Sunder": + case "Worship": + case "Wrath of God": setCode="MP2"; break; + + case "Behold the Power of Destruction": + case "Choose Your Champion": + case "Dance, Pathetic Marionette": + case "Embrace My Diabolical Vision": + case "Every Hope Shall Vanish": + case "Every Last Vestige Shall Rot": + case "Evil Comes to Fruition": + case "Feed the Machine": + case "I Bask in Your Silent Awe": + case "I Call on the Ancient Magics": + case "I Delight in Your Convulsions": + case "I Know All, I See All": + case "Ignite the Cloneforge!": + case "Into the Earthen Maw": + case "Introductions Are in Order": + case "Know Naught but Fire": + case "Look Skyward and Despair": + case "May Civilization Collapse": + case "Mortal Flesh Is Weak": + case "My Crushing Masterstroke": + case "My Genius Knows No Bounds": + case "My Undead Horde Awakens": + case "My Wish Is Your Command": + case "Nature Demands an Offering": + case "Nature Shields Its Own": + case "Nothing Can Stop Me Now": + case "Only Blood Ends Your Nightmares": + case "Realms Befitting My Majesty": + case "Roots of All Evil": + case "Rotted Ones, Lay Siege": + case "Surrender Your Thoughts": + case "The Dead Shall Serve": + case "The Fate of the Flammable": + case "The Iron Guardian Stirs": + case "The Pieces Are Coming Together": + case "The Very Soil Shall Shake": + case "Tooth, Claw, and Tail": + case "Which of You Burns Brightest": + case "Your Fate Is Thrice Sealed": + case "Your Puny Minds Cannot Fathom": + case "Your Will Is Not Your Own": setCode="OARC"; break; + + case "Because I Have Willed It": + case "Behold My Grandeur": + case "Bow to My Command": + case "Choose Your Demise": + case "Delight in the Hunt": + case "Every Dream a Nightmare": + case "For Each of You, a Gift": + case "Know Evil": + case "Make Yourself Useful": + case "My Forces Are Innumerable": + case "My Laughter Echoes": + case "No One Will Hear Your Cries": + case "Pay Tribute to Me": + case "Power Without Equal": + case "The Mighty Will Fall": + case "There Is No Refuge": + case "This World Belongs to Me": + case "What's Yours Is Now Mine": + case "When Will You Learn": setCode="OE01"; break; + + case "Bloodhill Bastion": + case "Celestine Reef": + case "Chaotic Aether": + case "Choke": + case "Cliffside Market": + case "Edge of Malacol": + case "Eloren Wilds": + case "Feeding Grounds": + case "Fields of Summer": + case "Furnace Layer": + case "Glimmervoid Basin": + case "Grand Ossuary": + case "Grove of the Dreampods": + case "Hedron Fields of Agadeem": + case "Horizon Boughs": + case "Immersturm": + case "Interplanar Tunnel": + case "Isle of Vesuva": + case "Izzet Steam Maze": + case "Kharasha Foothills": + case "Kilnspire District": + case "Lair of the Ashen Idol": + case "Lethe Lake": + case "Mirrored Depths": + case "Morphic Tide": + case "Mount Keralia": + case "Mutual Epiphany": + case "Norn's Dominion": + case "Onakke Catacomb": + case "Orochi Colony": + case "Panopticon": + case "Planewide Disaster": + case "Pools of Becoming": + case "Quicksilver Sea": + case "Reality Shaping": + case "Sanctum of Serra": + case "Sea of Sand": + case "Selesnya Loft Gardens": + case "Skybreen": + case "Spatial Merging": + case "Stairs to Infinity": + case "Stronghold Furnace": + case "Talon Gates": + case "Tember City": + case "Windriddle Palaces": + case "The Aether Flues": + case "The Dark Barony": + case "The Eon Fog": + case "The Fourth Sphere": + case "The Great Forest": + case "The Hippodrome": + case "The Maelstrom": + case "The Zephyr Maze": + case "Time Distortion": + case "Trail of the Mage-Rings": + case "Truga Jungle": + case "Turri Island": + case "Undercity Reaches": setCode="OPCA"; break; + + case "Drench the Soil in Their Blood": + case "Imprison This Insolent Wretch": + case "Perhaps You've Met My Cohort": + case "Plots That Span Centuries": + case "Your Inescapable Doom": setCode="PARC"; break; + + case "Stoneforge Mystic": setCode="PGPX"; break; + + case "Birds of Paradise Avatar1": + case "Bosh, Iron Golem Avatar": + case "Braids, Conjurer Adept Avatar": + case "Chronatog Avatar": + case "Dakkon Blackblade Avatar": + case "Dauntless Escort Avatar": + case "Diamond Faerie Avatar": + case "Eight-and-a-Half-Tails Avatar": + case "Enigma Sphinx Avatar": + case "Eladamri, Lord of Leaves Avatar": + case "Elvish Champion Avatar": + case "Erhnam Djinn Avatar1": + case "Etched Oracle Avatar": + case "Fallen Angel Avatar": + case "Figure of Destiny Avatar": + case "Flametongue Kavu Avatar": + case "Frenetic Efreet Avatar": + case "Goblin Warchief Avatar1": + case "Grinning Demon Avatar1": + case "Haakon, Stromgald Scourge Avatar": + case "Heartwood Storyteller Avatar": + case "Hell's Caretaker Avatar": + case "Hermit Druid Avatar": + case "Higure, the Still Wind Avatar": + case "Ink-Eyes, Servant of Oni Avatar": + case "Jaya Ballard Avatar": + case "Jhoira of the Ghitu Avatar": + case "Karona, False God Avatar": + case "Kresh the Bloodbraided Avatar": + case "Loxodon Hierarch Avatar": + case "Lyzolda, the Blood Witch Avatar": + case "Malfegor Avatar": + case "Maralen of the Mornsong Avatar": + case "Maro Avatar": + case "Master of the Wild Hunt Avatar": + case "Mayael the Anima Avatar": + case "Mirri the Cursed Avatar": + case "Mirror Entity Avatar": + case "Momir Vig, Simic Visionary Avatar": + case "Morinfen Avatar": + case "Murderous Redcap Avatar": + case "Necropotence Avatar": + case "Nekrataal Avatar": + case "Oni of Wild Places Avatar": + case "Orcish Squatters Avatar": + case "Peacekeeper Avatar": + case "Phage the Untouchable Avatar": + case "Platinum Angel Avatar1": + case "Prodigal Sorcerer Avatar1": + case "Raksha Golden Cub Avatar": + case "Reaper King Avatar": + case "Rith, the Awakener Avatar1": + case "Royal Assassin Avatar1": + case "Rumbling Slum Avatar": + case "Sakashima the Impostor Avatar": + case "Serra Angel Avatar1": + case "Seshiro the Anointed Avatar": + case "Sisters of Stone Death Avatar": + case "Sliver Queen Avatar": + case "Squee, Goblin Nabob Avatar": + case "Stalking Tiger Avatar": + case "Stonehewer Giant Avatar": + case "Stuffy Doll Avatar": + case "Teysa, Orzhov Scion Avatar": + case "Tradewind Rider Avatar1": + case "Two-Headed Giant of Foriys Avatar": + case "Vampire Nocturnus Avatar": + case "Viridian Zealot Avatar": setCode="PMOA"; break; + + case "Nalathni Dragon": + case "Sewers of Estark": + case "Windseeker Centaur": setCode="PRM"; break; + + case "Lyna": + case "Sliver Queen, Brood Mother": + case "Takara": setCode="PVAN"; break; + + case "Goblin Hero": setCode="S99"; break; + + case "Pyrostatic Pillar": + case "Weathered Wayfarer": setCode="TD0"; break; + + case "Hero's Resolve": + case "Python": setCode="6ED"; break; + } + + return setCode; + } +} diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadService.java b/forge-gui/src/main/java/forge/download/GuiDownloadService.java index c0a22f27e85..04d31f77fd8 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadService.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadService.java @@ -32,6 +32,7 @@ import java.net.URLDecoder; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -272,7 +273,13 @@ public abstract class GuiDownloadService implements Runnable { URL imageUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection(p); // don't allow redirections here -- they indicate 'file not found' on the server - conn.setInstanceFollowRedirects(false); + // only allow redirections to consume Scryfall API + if(url.contains("api.scryfall.com")) { + conn.setInstanceFollowRedirects(true); + TimeUnit.MILLISECONDS.sleep(100); + } else { + conn.setInstanceFollowRedirects(false); + } conn.connect(); // if file is not found and this is a JPG, give PNG a shot... diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 193d2b6b6e1..44d5a6e0c5e 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -284,6 +284,8 @@ public final class ForgeConstants { public static final String URL_PIC_DOWNLOAD = URL_CARDFORGE + "/images/cards/"; public static final String URL_TOKEN_DOWNLOAD = URL_CARDFORGE + "/images/tokens/"; public static final String URL_PRICE_DOWNLOAD = URL_CARDFORGE + "/all-prices.txt"; + private static final String URL_SCRYFALL = "https://api.scryfall.com"; + public static final String URL_PIC_SCRYFALL_DOWNLOAD = URL_SCRYFALL + "/cards/"; // Constants for Display Card Identity game setting public static final String DISP_CURRENT_COLORS_ALWAYS = "Always"; From 198cb4f90ad4f91f6520727c3124d05cbc1075b2 Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 27 Jul 2019 10:17:30 +0200 Subject: [PATCH 02/10] Android: Add Language selector in Settings --- .../src/forge/assets/FLanguage.java | 42 +++++++++++++++++++ .../forge/screens/settings/SettingsPage.java | 9 ++++ 2 files changed, 51 insertions(+) create mode 100644 forge-gui-mobile/src/forge/assets/FLanguage.java diff --git a/forge-gui-mobile/src/forge/assets/FLanguage.java b/forge-gui-mobile/src/forge/assets/FLanguage.java new file mode 100644 index 00000000000..728ca8cf0be --- /dev/null +++ b/forge-gui-mobile/src/forge/assets/FLanguage.java @@ -0,0 +1,42 @@ +package forge.assets; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import forge.model.FModel; +import forge.properties.ForgeConstants; +import forge.properties.ForgePreferences; +import forge.properties.ForgePreferences.FPref; + +import java.util.ArrayList; +import java.util.List; + +public class FLanguage { + + public static void changeLanguage(final String languageName) { + final ForgePreferences prefs = FModel.getPreferences(); + if (languageName.equals(prefs.getPref(FPref.UI_LANGUAGE))) { return; } + + //save language preference + prefs.setPref(FPref.UI_LANGUAGE, languageName); + prefs.save(); + } + + /** + * Gets the languages. + * + * @return the languages + */ + public static Iterable getAllLanguages() { + final List allLanguages = new ArrayList(); + + final FileHandle dir = Gdx.files.absolute(ForgeConstants.LANG_DIR); + for (FileHandle languageFile : dir.list()) { + String languageName = languageFile.name(); + if (!languageName.endsWith(".properties")) { continue; } + allLanguages.add(languageName.replace(".properties", "")); + } + + return allLanguages; + } + +} diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index f13dafbc3b2..760b93ce9bc 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -6,6 +6,7 @@ import forge.Graphics; import forge.MulliganDefs; import forge.StaticData; import forge.ai.AiProfileUtil; +import forge.assets.FLanguage; import forge.assets.FSkin; import forge.assets.FSkinColor; import forge.assets.FSkinFont; @@ -48,6 +49,14 @@ public class SettingsPage extends TabPage { lstSettings.addGroup("Sound Options"); //General Settings + lstSettings.addItem(new CustomSelectSetting(FPref.UI_LANGUAGE, "Language", + "Select Language (Excluded Game part. Still a work in progress) (RESTART REQUIRED)", + FLanguage.getAllLanguages()) { + @Override + public void valueChanged(String newValue) { + FLanguage.changeLanguage(newValue); + } + }, 0); lstSettings.addItem(new CustomSelectSetting(FPref.UI_SKIN, "Theme", "Sets the theme that determines how display components are skinned.", FSkin.getAllSkins()) { From 37fe37d5d9bbe1edb62c5eff8ac515e9e6db893f Mon Sep 17 00:00:00 2001 From: Agetian Date: Mon, 29 Jul 2019 19:42:12 +0300 Subject: [PATCH 03/10] - Fix ChooseCardEffect crashing for e.g. Curse of the Pierced Heart on a planeswalker --- .../main/java/forge/game/ability/effects/ChooseCardEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 9a8df7b8343..5d7083bf5ab 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -52,7 +52,7 @@ public class ChooseCardEffect extends SpellAbilityEffect { } CardCollectionView choices = game.getCardsIn(choiceZone); if (sa.hasParam("Choices")) { - choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host); + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa); } if (sa.hasParam("TargetControls")) { choices = CardLists.filterControlledBy(choices, tgtPlayers.get(0)); From 60c2119f16b6db6bcf8e5cf4f467a092f6989d81 Mon Sep 17 00:00:00 2001 From: kms70847 Date: Mon, 29 Jul 2019 22:01:34 -0400 Subject: [PATCH 04/10] Show the number of cards you need to put back when London mulliganning --- forge-gui/res/languages/de-DE.properties | 2 +- forge-gui/res/languages/en-US.properties | 2 +- forge-gui/res/languages/es-es.properties | 2 +- .../src/main/java/forge/match/input/InputLondonMulligan.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 5cfecfde4be..f9701f6a74f 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -317,7 +317,7 @@ lblIsGoingFirst=beginnt. lblYouAreGoing=Du startest lblMulligan=Mulligan lblDoYouWantToKeepYourHand=Starthand behalten? -lblReturnForLondon=Lege %n Karten unter die Bibliothek +lblReturnForLondon=Lege %d Karten unter die Bibliothek lblOk=OK lblReset=Zurück lblAuto=Auto diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index cedefb81c72..0a65dfdb274 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -317,7 +317,7 @@ lblIsGoingFirst=is going first lblYouAreGoing=you are going lblMulligan=Mulligan lblDoYouWantToKeepYourHand=Do you want to keep your hand? -lblReturnForLondon=Return %n card(s) to bottom of library +lblReturnForLondon=Return %d card(s) to the bottom of your library lblOk=Ok lblReset=Reset lblAuto=Auto diff --git a/forge-gui/res/languages/es-es.properties b/forge-gui/res/languages/es-es.properties index 793a7c577ee..85f9fbd46d2 100644 --- a/forge-gui/res/languages/es-es.properties +++ b/forge-gui/res/languages/es-es.properties @@ -317,7 +317,7 @@ lblIsGoingFirst=va primero lblYouAreGoing=vas lblMulligan=Mulligan lblDoYouWantToKeepYourHand=¿Quieres quedarte tu mano? -lblReturnForLondon=Return %n card(s) to bottom of library +lblReturnForLondon=Return %d card(s) to the bottom of your library lblOk=Ok lblReset=Reset lblAuto=Auto diff --git a/forge-gui/src/main/java/forge/match/input/InputLondonMulligan.java b/forge-gui/src/main/java/forge/match/input/InputLondonMulligan.java index 6032c3d98ba..d227c20fce2 100644 --- a/forge-gui/src/main/java/forge/match/input/InputLondonMulligan.java +++ b/forge-gui/src/main/java/forge/match/input/InputLondonMulligan.java @@ -60,7 +60,7 @@ public class InputLondonMulligan extends InputSyncronizedBase { getController().getGui().updateButtons(getOwner(), localizer.getMessage("lblOk"), "", cardsLeft == 0, false, true); - sb.append(String.format(localizer.getMessage("lblReturnForLondon"), selected.size(), toReturn)); + sb.append(String.format(localizer.getMessage("lblReturnForLondon"), cardsLeft)); showMessage(sb.toString()); } From b9cf836c1a1c198914e6cb7eb7ae1659c6b0e73c Mon Sep 17 00:00:00 2001 From: kms70847 Date: Mon, 29 Jul 2019 22:48:54 -0400 Subject: [PATCH 05/10] Fix for https://git.cardforge.org/core-developers/forge/issues/619 --- forge-game/src/main/java/forge/game/card/Card.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 26c21399155..ab6660bbc2b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2697,6 +2697,13 @@ public class Card extends GameEntity implements Comparable { } public final Player getController() { + if ((currentZone == null) || ((currentZone.getZoneType() != ZoneType.Battlefield) && (currentZone.getZoneType() != ZoneType.Stack))){ + //only permanents and spells have controllers [108.4], + //so a card really only has a controller while it's on the stack or battlefield. + //everywhere else, just use the owner [108.4a]. + return owner; + } + Entry lastEntry = tempControllers.lastEntry(); if (lastEntry != null) { final long lastTimestamp = lastEntry.getKey(); From 89da830c6c29ab1ed38d7b829624e9884d11c7be Mon Sep 17 00:00:00 2001 From: kms70847 Date: Mon, 29 Jul 2019 23:03:58 -0400 Subject: [PATCH 06/10] Rebound: "it's manacost" -> "its mana cost" --- forge-game/src/main/java/forge/game/card/CardFactoryUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 5ed5219df8a..bf22da56bb6 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3508,7 +3508,7 @@ public class CardFactoryUtil { String abExile = "DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Exile"; String delTrig = "DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You " + " | OptionalDecider$ You | RememberObjects$ Self | TriggerDescription$" - + " At the beginning of your next upkeep, you may cast " + card.toString() + " without paying it's manacost."; + + " At the beginning of your next upkeep, you may cast " + card.toString() + " without paying its mana cost."; // TODO add check for still in exile String abPlay = "DB$ Play | Defined$ Self | WithoutManaCost$ True | Optional$ True"; From bbe324a7904400f86cf5fdefa1f0df2bc4211f98 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 30 Jul 2019 11:20:03 +0300 Subject: [PATCH 07/10] - Added SS2 edition (Signature Spellbook: Gideon) --- .../res/editions/Signature Spellbook Gideon.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 forge-gui/res/editions/Signature Spellbook Gideon.txt diff --git a/forge-gui/res/editions/Signature Spellbook Gideon.txt b/forge-gui/res/editions/Signature Spellbook Gideon.txt new file mode 100644 index 00000000000..1088da2b40d --- /dev/null +++ b/forge-gui/res/editions/Signature Spellbook Gideon.txt @@ -0,0 +1,16 @@ +[metadata] +Code=SS2 +Date=2019-06-28 +Name=Signature Spellbook: Gideon +MciCode=ss2 +Type=Reprint + +[cards] +1 M Gideon Jura +2 R Martyr's Bond +3 R Path to Exile +4 R Rest in Peace +5 R Shielded by Faith +6 R True Conviction +7 R Worship +8 R Blackblade Reforged From eab262aa7e15d3e3a76640eef1844e4126b06baf Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 30 Jul 2019 11:30:26 +0300 Subject: [PATCH 08/10] - Added puzzle PS_M203. --- forge-gui/res/puzzle/PS_M203.pzl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 forge-gui/res/puzzle/PS_M203.pzl diff --git a/forge-gui/res/puzzle/PS_M203.pzl b/forge-gui/res/puzzle/PS_M203.pzl new file mode 100644 index 00000000000..6eb2a228e72 --- /dev/null +++ b/forge-gui/res/puzzle/PS_M203.pzl @@ -0,0 +1,16 @@ +[metadata] +Name:Possibility Storm - Magic Core Set 2020 #03 +URL:http://www.possibilitystorm.com/wp-content/uploads/2019/07/122.m203.jpg +Goal:Win +Turns:1 +Difficulty:Mythic +Description:Win this turn. Assume your opponent has no mana available. +[state] +humanlife=20 +ailife=12 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Shatter;Teferi's Time Twist;Role Reversal;Trumpet Blast +humanbattlefield=Viashino Pyromancer;Retributive Wand;Captain's Hook;Saheeli, Sublime Artificer|Counters:LOYALTY=3;Island;Island;Mountain;Mountain +aibattlefield=Pardic Wanderer From 200e191f14c204ab3dbac9095c9d5628d1ba56a4 Mon Sep 17 00:00:00 2001 From: kms70847 Date: Sat, 27 Jul 2019 19:16:00 -0400 Subject: [PATCH 09/10] When adding cards to library, insert "closest to top" card last --- .../java/forge/game/ability/effects/ChangeZoneAllEffect.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 72ae0a53850..7292fcc8325 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -134,6 +134,9 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { && !sa.hasParam("Shuffle") && cards.size() >= 2 && !random) { Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0); cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination); + //the last card in this list will be the closest to the top, but we want the first card to be closest. + //so reverse it here before moving them to the library. + java.util.Collections.reverse(cards); } if (destination == ZoneType.Graveyard) { From 61c42275e6ae1f5d692504395c062f491d4c9f85 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 28 Jul 2019 12:21:01 +0000 Subject: [PATCH 10/10] Dash: reworked and better Haste part --- .../forge/game/ability/AbilityFactory.java | 7 ++++++- .../game/ability/SpellAbilityEffect.java | 2 +- .../ability/effects/ChangeZoneEffect.java | 4 ++-- .../forge/game/ability/effects/DigEffect.java | 13 +----------- .../game/ability/effects/PermanentEffect.java | 5 +++-- .../java/forge/game/card/CardFactoryUtil.java | 20 +++++-------------- .../java/forge/game/card/CardProperty.java | 5 +++++ 7 files changed, 23 insertions(+), 33 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 258d0a4e272..6fd213ea43c 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -204,7 +204,8 @@ public final class AbilityFactory { else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) { // If API is a permanent type, and creating AF Spell // Clear out the auto created SpellPemanent spell - if (type == AbilityRecordType.Spell && !mapParams.containsKey("SubAbility")) { + if (type == AbilityRecordType.Spell + && !mapParams.containsKey("SubAbility") && !mapParams.containsKey("NonBasicSpell")) { hostCard.clearFirstSpell(); } } @@ -389,6 +390,10 @@ public final class AbilityFactory { sa.setBasicSpell(false); } + if (mapParams.containsKey("Dash")) { + sa.setDash(true); + } + if (mapParams.containsKey("Outlast")) { sa.setOutlast(true); } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 6c468779186..a0d4e8b10b4 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -238,7 +238,7 @@ public abstract class SpellAbilityEffect { } sb.append(Lang.joinHomogenous(crds)); if (location.equals("Hand")) { - sb.append("to your hand").append(" "); + sb.append(" to your hand"); } sb.append(" at the "); if (combat) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 4397465f47c..3a053f5937a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -537,10 +537,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { tgtC.getController().getZone(destination), tgtC, sa, null); if (sa.hasParam("Unearth")) { movedCard.setUnearthed(true); - movedCard.addExtrinsicKeyword("Haste"); + movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false, false, + game.getNextTimestamp(), true); registerDelayedTrigger(sa, "Exile", Lists.newArrayList(movedCard)); addLeaveBattlefieldReplacement(movedCard, sa, "Exile"); - movedCard.updateStateForView(); } if (sa.hasParam("FaceDown")) { movedCard.turnFaceDown(true); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 7d8a8d4cab4..f3e66bad91b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -83,10 +83,6 @@ public class DigEffect extends SpellAbilityEffect { boolean changeAll = false; boolean allButOne = false; - final List keywords = new ArrayList(); - if (sa.hasParam("Keywords")) { - keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); - } if (sa.hasParam("ChangeNum")) { if (sa.getParam("ChangeNum").equalsIgnoreCase("All")) { @@ -307,9 +303,6 @@ public class DigEffect extends SpellAbilityEffect { else { c = game.getAction().moveTo(zone, c, sa); if (destZone1.equals(ZoneType.Battlefield)) { - for (final String kw : keywords) { - c.addExtrinsicKeyword(kw); - } if (sa.hasParam("Tapped")) { c.setTapped(true); } @@ -377,11 +370,7 @@ public class DigEffect extends SpellAbilityEffect { if (!origin.equals(c.getZone().getZoneType())) { table.put(origin, c.getZone().getZoneType(), c); } - if (destZone2 == ZoneType.Battlefield && !keywords.isEmpty()) { - for (final String kw : keywords) { - c.addExtrinsicKeyword(kw); - } - } else if (destZone2 == ZoneType.Exile) { + if (destZone2 == ZoneType.Exile) { if (sa.hasParam("ExileWithCounter")) { c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true, counterTable); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java index 3324a88a9a0..f916ee8b67e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java @@ -1,5 +1,7 @@ package forge.game.ability.effects; +import com.google.common.collect.Lists; + import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; @@ -25,9 +27,8 @@ public class PermanentEffect extends SpellAbilityEffect { // some extra for Dashing if (sa.isDash()) { - c.addExtrinsicKeyword("Haste"); c.setSVar("EndOfTurnLeavePlay", "Dash"); - c.updateKeywords(); + registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c)); } } } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index a958d18e9ae..5ed5219df8a 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3850,23 +3850,11 @@ public class CardFactoryUtil { inst.addSpellAbility(sa); } else if (keyword.startsWith("Dash")) { final String[] k = keyword.split(":"); - final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | SubAbility$" - + " DashDelayedTrigger"; - final String dbDelayTrigger = "DB$ DelayedTrigger | Mode$ Phase | Phase$" - + " End of Turn | Execute$ DashReturnSelf | RememberObjects$ Self" - + " | TriggerDescription$ Return CARDNAME from the battlefield to" + " its owner's hand."; - final String dbReturn = "DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand" - + " | Defined$ DelayTriggerRemembered"; - card.setSVar("DashDelayedTrigger", dbDelayTrigger); - card.setSVar("DashReturnSelf", dbReturn); + final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | StackDescription$ CARDNAME (Dash)" + + " | Dash$ True | NonBasicSpell$ True" + + " | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")"; final SpellAbility newSA = AbilityFactory.getAbility(dashString, card); - String desc = "Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() - + ")"; - newSA.setStackDescription(card.getName() + " (Dash)"); - newSA.setDescription(desc); - newSA.setBasicSpell(false); - newSA.setDash(true); newSA.setIntrinsic(intrinsic); newSA.setTemporary(!intrinsic); @@ -4527,6 +4515,8 @@ public class CardFactoryUtil { card.setSVar("CipherTrigger", trig); card.setSVar("PlayEncoded", ab); + } else if (keyword.startsWith("Dash")) { + effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste"; } else if (keyword.equals("Devoid")) { effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" + " | CharacteristicDefining$ True | SetColor$ Colorless | Secondary$ True" + diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 8828cc02790..6be6628b541 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1639,6 +1639,11 @@ public class CardProperty { return false; } return card.getCastSA().isSurged(); + } else if (property.startsWith("dashed")) { + if (card.getCastSA() == null) { + return false; + } + return card.getCastSA().isDash(); } else if (property.startsWith("evoked")) { if (card.getCastSA() == null) { return false;