Merge remote-tracking branch 'upstream/master'

This commit is contained in:
CCTV-1
2020-03-09 19:21:43 +08:00
44 changed files with 518 additions and 129 deletions

View File

@@ -981,14 +981,27 @@ public abstract class GameState {
spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim();
}
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
Card c = null;
if (pc == null) {
System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!");
return;
if (StringUtils.isNumeric(spellDef)) {
// Precast from a specific host
c = idToCard.get(Integer.parseInt(spellDef));
if (c == null) {
System.err.println("ERROR: Could not find a card with ID " + spellDef + " to precast!");
return;
}
} else {
// Precast from a card by name
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
if (pc == null) {
System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!");
return;
}
c = Card.fromPaperCard(pc, activator);
}
Card c = Card.fromPaperCard(pc, activator);
SpellAbility sa = null;
if (!scriptID.isEmpty()) {

View File

@@ -274,14 +274,24 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
it should also match the Un-set and older alternate art cards
like Merseine from FEM (should the editions files ever be updated)
*/
"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
/* Ideally we'd use the named group above, but Android 6 and
earlier don't appear to support named groups.
So, untill support for those devices is officially dropped,
we'll have to suffice with numbered groups.
We are looking for:
* cnum - grouping #2
* rarity - grouping #4
* name - grouping #5
*/
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
);
for(String line : contents.get("cards")) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
String collectorNumber = matcher.group("cnum");
CardRarity r = CardRarity.smartValueOf(matcher.group("rarity"));
String cardName = matcher.group("name");
String collectorNumber = matcher.group(2);
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
String cardName = matcher.group(5);
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
processedCards.add(cis);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#f0f0f0</color>
<color name="ic_launcher_background">#ffffff</color>
</resources>

View File

@@ -23,6 +23,13 @@ public enum CSubmenuDownloaders implements ICDoc {
VSubmenuDownloaders.SINGLETON_INSTANCE.showLicensing();
}
};
private final UiCommand cmdCheckForUpdates = new UiCommand() {
@Override
public void run() {
new AutoUpdater(false).attemptToUpdate();
}
};
private final UiCommand cmdPicDownload = new UiCommand() {
@Override public void run() {
new GuiDownloader(new GuiDownloadPicturesLQ()).show();
@@ -84,6 +91,7 @@ public enum CSubmenuDownloaders implements ICDoc {
@Override
public void initialize() {
final VSubmenuDownloaders view = VSubmenuDownloaders.SINGLETON_INSTANCE;
view.setCheckForUpdatesCommand(cmdCheckForUpdates);
view.setDownloadPicsCommand(cmdPicDownload);
view.setDownloadPicsHQCommand(cmdPicDownloadHQ);
view.setDownloadSetPicsCommand(cmdSetDownload);

View File

@@ -3,6 +3,7 @@ package forge.screens.home.settings;
import forge.*;
import forge.ai.AiProfileUtil;
import forge.control.FControl.CloseAction;
import forge.download.AutoUpdater;
import forge.game.GameLogEntryType;
import forge.gui.framework.FScreen;
import forge.gui.framework.ICDoc;
@@ -225,6 +226,7 @@ public enum CSubmenuPreferences implements ICDoc {
initializeGameLogVerbosityComboBox();
initializeCloseActionComboBox();
initializeDefaultFontSizeComboBox();
initializeAutoUpdaterComboBox();
initializeMulliganRuleComboBox();
initializeAiProfilesComboBox();
initializeStackAdditionsComboBox();
@@ -378,6 +380,16 @@ public enum CSubmenuPreferences implements ICDoc {
panel.setComboBox(comboBox, selectedItem);
}
private void initializeAutoUpdaterComboBox() {
// TODO: Ideally we would filter out update paths based on the type of Forge people have
final String[] updatePaths = AutoUpdater.updateChannels;
final FPref updatePreference = FPref.AUTO_UPDATE;
final FComboBoxPanel<String> panel = this.view.getCbpAutoUpdater();
final FComboBox<String> comboBox = createComboBox(updatePaths, updatePreference);
final String selectedItem = this.prefs.getPref(updatePreference);
panel.setComboBox(comboBox, selectedItem);
}
private void initializeMulliganRuleComboBox() {
final String [] choices = MulliganDefs.getMulliganRuleNames();
final FPref userSetting = FPref.MULLIGAN_RULE;

View File

@@ -55,6 +55,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
private final JPanel pnlContent = new JPanel(new MigLayout("insets 0, gap 0, wrap, ay center"));
private final FScrollPane scrContent = new FScrollPane(pnlContent, false);
private final FLabel btnCheckForUpdates = _makeButton(localizer.getMessage("btnCheckForUpdates"));
private final FLabel btnDownloadSetPics = _makeButton(localizer.getMessage("btnDownloadSetPics"));
private final FLabel btnDownloadPics = _makeButton(localizer.getMessage("btnDownloadPics"));
private final FLabel btnDownloadPicsHQ = _makeButton(localizer.getMessage("btnDownloadPicsHQ"));
@@ -80,6 +81,9 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
if (javaRecentEnough()) {
pnlContent.add(btnCheckForUpdates, constraintsBTN);
pnlContent.add(_makeLabel(localizer.getMessage("lblCheckForUpdates")), constraintsLBL);
pnlContent.add(btnDownloadPics, constraintsBTN);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPics")), constraintsLBL);
@@ -162,6 +166,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
return EMenuGroup.SETTINGS;
}
public void setCheckForUpdatesCommand(UiCommand command) { btnCheckForUpdates.setCommand(command); }
public void setDownloadPicsCommand(UiCommand command) { btnDownloadPics.setCommand(command); }
public void setDownloadPicsHQCommand(UiCommand command) { btnDownloadPicsHQ.setCommand(command); }
public void setDownloadSetPicsCommand(UiCommand command) { btnDownloadSetPics.setCommand(command); }

View File

@@ -25,8 +25,8 @@ import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;
import java.util.*;
/**
@@ -123,6 +123,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final FComboBoxPanel<String> cbpCounterDisplayLocation =new FComboBoxPanel<>(localizer.getMessage("cbpCounterDisplayLocation")+":");
private final FComboBoxPanel<String> cbpGraveyardOrdering = new FComboBoxPanel<>(localizer.getMessage("cbpGraveyardOrdering")+":");
private final FComboBoxPanel<String> cbpDefaultLanguage = new FComboBoxPanel<>(localizer.getMessage("cbpSelectLanguage")+":");
private final FComboBoxPanel<String> cbpAutoUpdater = new FComboBoxPanel<>(localizer.getMessage("cbpAutoUpdater")+":");
/**
* Constructor.
@@ -157,6 +158,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(new SectionLabel(localizer.getMessage("GeneralConfiguration")), sectionConstraints);
// language
pnlPrefs.add(cbpAutoUpdater, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAutoUpdater")), descriptionConstraints);
pnlPrefs.add(cbpDefaultLanguage, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSelectLanguage")), descriptionConstraints);
@@ -531,6 +536,10 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
}
}
public final FComboBoxPanel<String> getCbpAutoUpdater() {
return cbpAutoUpdater;
}
/** @return {@link javax.swing.JCheckBox} */
public final JCheckBox getCbCompactMainMenu() {
return cbCompactMainMenu;

View File

@@ -81,7 +81,7 @@ public final class Main {
break;
default:
System.out.println("Unknown mode.\nKnown mode is 'sim' ");
System.out.println("Unknown mode.\nKnown mode is 'sim', 'parse' ");
break;
}

View File

@@ -209,7 +209,7 @@ public class FSkin {
textures.put(f6.path(), textures.get(f3.path()));
}
if (f7.exists()){
Texture t = new Texture(f7, false);
Texture t = new Texture(f7, true);
//t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
textures.put(f7.path(), t);
}

View File

@@ -193,6 +193,15 @@ public enum FSkinImage implements FImage {
QUEST_BIG_SWORD (FSkinProp.ICO_QUEST_BIG_SWORD, SourceFile.ICONS),
QUEST_BIG_BAG (FSkinProp.ICO_QUEST_BIG_BAG, SourceFile.ICONS),
//menu icon
MENU_GALAXY (FSkinProp.ICO_MENU_GALAXY, SourceFile.ICONS),
MENU_STATS (FSkinProp.ICO_MENU_STATS, SourceFile.ICONS),
MENU_PUZZLE (FSkinProp.ICO_MENU_PUZZLE, SourceFile.ICONS),
MENU_GAUNTLET (FSkinProp.ICO_MENU_GAUNTLET, SourceFile.ICONS),
MENU_SEALED (FSkinProp.ICO_MENU_SEALED, SourceFile.ICONS),
MENU_DRAFT (FSkinProp.ICO_MENU_DRAFT, SourceFile.ICONS),
MENU_CONSTRUCTED (FSkinProp.ICO_MENU_CONSTRUCTED, SourceFile.ICONS),
//Interface icons
QUESTION (FSkinProp.ICO_QUESTION, SourceFile.ICONS),
INFORMATION (FSkinProp.ICO_INFORMATION, SourceFile.ICONS),

View File

@@ -20,11 +20,11 @@ import forge.util.Localizer;
public class LoadGameMenu extends FPopupMenu {
public enum LoadGameScreen {
BoosterDraft("lblBoosterDraft", FSkinImage.HAND, LoadDraftScreen.class),
SealedDeck("lblSealedDeck", FSkinImage.PACK, LoadSealedScreen.class),
BoosterDraft("lblBoosterDraft", FSkinImage.MENU_DRAFT, LoadDraftScreen.class),
SealedDeck("lblSealedDeck", FSkinImage.MENU_SEALED, LoadSealedScreen.class),
QuestMode("lblQuestMode", FSkinImage.QUEST_ZEP, LoadQuestScreen.class),
PlanarConquest("lblPlanarConquest", FSkinImage.MULTIVERSE, LoadConquestScreen.class),
Gauntlet("lblGauntlet", FSkinImage.ALPHASTRIKE, LoadGauntletScreen.class);
PlanarConquest("lblPlanarConquest", FSkinImage.MENU_GALAXY, LoadConquestScreen.class),
Gauntlet("lblGauntlet", FSkinImage.MENU_GAUNTLET, LoadGauntletScreen.class);
private final FMenuItem item;
private final Class<? extends FScreen> screenClass;

View File

@@ -24,13 +24,13 @@ public class NewGameMenu extends FPopupMenu {
final static Localizer localizer = Localizer.getInstance();
public enum NewGameScreen {
Constructed(localizer.getMessage("lblConstructed"), FSkinImage.DECKLIST, ConstructedScreen.class),
BoosterDraft(localizer.getMessage("lblBoosterDraft"), FSkinImage.HAND, NewDraftScreen.class),
SealedDeck(localizer.getMessage("lblSealedDeck"), FSkinImage.PACK, NewSealedScreen.class),
Constructed(localizer.getMessage("lblConstructed"), FSkinImage.MENU_CONSTRUCTED, ConstructedScreen.class),
BoosterDraft(localizer.getMessage("lblBoosterDraft"), FSkinImage.MENU_DRAFT, NewDraftScreen.class),
SealedDeck(localizer.getMessage("lblSealedDeck"), FSkinImage.MENU_SEALED, NewSealedScreen.class),
QuestMode(localizer.getMessage("lblQuestMode"), FSkinImage.QUEST_ZEP, NewQuestScreen.class),
PuzzleMode(localizer.getMessage("lblPuzzleMode"), FSkinImage.QUEST_BOOK, PuzzleScreen.class),
PlanarConquest(localizer.getMessage("lblPlanarConquest"), FSkinImage.MULTIVERSE, NewConquestScreen.class),
Gauntlet(localizer.getMessage("lblGauntlet"), FSkinImage.ALPHASTRIKE, NewGauntletScreen.class);
PuzzleMode(localizer.getMessage("lblPuzzleMode"), FSkinImage.MENU_PUZZLE, PuzzleScreen.class),
PlanarConquest(localizer.getMessage("lblPlanarConquest"), FSkinImage.MENU_GALAXY, NewConquestScreen.class),
Gauntlet(localizer.getMessage("lblGauntlet"), FSkinImage.MENU_GAUNTLET, NewGauntletScreen.class);
private final FMenuItem item;
private final Class<? extends FScreen> screenClass;

View File

@@ -54,43 +54,45 @@ public class PuzzleScreen extends LaunchScreen {
final ArrayList<Puzzle> puzzles = PuzzleIO.loadPuzzles();
Collections.sort(puzzles);
GuiChoose.one(Localizer.getInstance().getMessage("lblChooseAPuzzle"), puzzles, new Callback<Puzzle>() {
GuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblChooseAPuzzle"), puzzles, new Callback<Puzzle>() {
@Override
public void run(final Puzzle chosen) {
LoadingOverlay.show(Localizer.getInstance().getMessage("lblLoadingThePuzzle"), new Runnable() {
@Override
public void run() {
// Load selected puzzle
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();
hostedMatch.setStartGameHook(new Runnable() {
@Override
public final void run() {
chosen.applyToGame(hostedMatch.getGame());
}
});
if (chosen != null) {
LoadingOverlay.show(Localizer.getInstance().getMessage("lblLoadingThePuzzle"), new Runnable() {
@Override
public void run() {
// Load selected puzzle
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();
hostedMatch.setStartGameHook(new Runnable() {
@Override
public final void run() {
chosen.applyToGame(hostedMatch.getGame());
}
});
hostedMatch.setEndGameHook((new Runnable() {
@Override
public void run() {
chosen.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer()));
}
}));
hostedMatch.setEndGameHook((new Runnable() {
@Override
public void run() {
chosen.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer()));
}
}));
final List<RegisteredPlayer> players = new ArrayList<>();
final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer());
human.setStartingHand(0);
players.add(human);
final List<RegisteredPlayer> players = new ArrayList<>();
final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer());
human.setStartingHand(0);
players.add(human);
final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer());
ai.setStartingHand(0);
players.add(ai);
final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer());
ai.setStartingHand(0);
players.add(ai);
GameRules rules = new GameRules(GameType.Puzzle);
rules.setGamesPerMatch(1);
hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame());
FOptionPane.showMessageDialog(chosen.getGoalDescription(), chosen.getName());
}
});
GameRules rules = new GameRules(GameType.Puzzle);
rules.setGamesPerMatch(1);
hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame());
FOptionPane.showMessageDialog(chosen.getGoalDescription(), chosen.getName());
}
});
}
}
});

View File

@@ -56,7 +56,7 @@ public class ConquestMenu extends FPopupMenu {
setCurrentScreen(collectionScreen);
}
});
private static final FMenuItem statsItem = new FMenuItem(Localizer.getInstance().getMessage("lblStatistics"), FSkinImage.HDMULTI, new FEventHandler() {
private static final FMenuItem statsItem = new FMenuItem(Localizer.getInstance().getMessage("lblStatistics"), FSkinImage.MENU_STATS, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
setCurrentScreen(statsScreen);

View File

@@ -79,7 +79,7 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats {
setCurrentScreen(bazaarScreen);
}
});
private static final FMenuItem statsItem = new FMenuItem(Localizer.getInstance().getMessage("lblStatistics"), FSkinImage.HDMULTI, new FEventHandler() {
private static final FMenuItem statsItem = new FMenuItem(Localizer.getInstance().getMessage("lblStatistics"), FSkinImage.MENU_STATS, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
setCurrentScreen(statsScreen);

View File

@@ -1909,7 +1909,6 @@ Knight of Sorrows
Knight of the Skyward Eye
Knight of the Tusk
Kor Bladewhirl
Kor Chant
Kor Firewalker
Kor Hookmaster
Kor Sky Climber
@@ -2028,6 +2027,7 @@ Zealous Strike
[MB1 Blue CommonUncommon]
Academy Journeymage
Aethersnipe
Aether Spellbomb
Aether Tradewinds
Amass the Components
Amphin Pathmage
@@ -2739,7 +2739,6 @@ Tibalt's Rager
Torch Courier
Uncaged Fury
Undying Rage
Urza's Rage
Valakut Invoker
Valakut Predator
Valley Dasher
@@ -3615,3 +3614,21 @@ Minamo, School at Water's Edge+|FMB1
Mirrodin's Core+|FMB1
Shizo, Death's Storehouse+|FMB1
Stalking Stones+|FMB1
[CN2 Not In Normal Slots]
Adriana's Valor
#Assemble the Rank and Vile
Echoing Boon
#Emissary's Ploy
Hired Heist
#Hold the Permiter
Hymn of the Wilds
Incendiary Dissent
Natural Unity
#Sovereign's Realm
#Summoner's Bond
Weight Advantage
Kaya, Ghost Assassin|CN2|2
[CN2 Foil Kaya]
Kaya, Ghost Assassin|CN2|2

View File

@@ -6,7 +6,8 @@ Code2=CN2
MciCode=cn2
Type=Other
BoosterCovers=3
Booster=10 Common:!fromSheet("CN2 Draft Matters"), 3 Uncommon:!fromSheet("CN2 Draft Matters"), 1 RareMythic:!fromSheet("CN2 Draft Matters"), 1 fromSheet("CN2 Draft Matters")
Booster=10 Common:!fromSheet("CN2 Not In Normal Slots"), 3 Uncommon:!fromSheet("CN2 Not In Normal Slots"), 1 RareMythic:!fromSheet("CN2 Not In Normal Slots"), 1 fromSheet("CN2 Draft Matters")
AdditionalSheetForFoils=fromSheet("CN2 Foil Kaya")
[cards]
1 C Adriana's Valor
@@ -243,4 +244,4 @@ r_1_1_goblin_noblock
r_8_8_lizard
g_3_3_beast
g_1_1_insect
c_1_1_a_construct_defender
c_1_1_a_construct_defender

View File

@@ -52,6 +52,8 @@ btnResetJavaFutureCompatibilityWarnings=Reset Java Compatibility Warnings
btnClearImageCache=Clear Image Cache
btnTokenPreviewer=Token Previewer
btnCopyToClipboard=Copy to Clipboard
cbpAutoUpdater=Auto updater
nlAutoUpdater=Select the release channel to use for updating Forge
cbpSelectLanguage=Language
nlSelectLanguage=Select Language (Excluded Game part. Still a work in progress) (RESTART REQUIRED)
cbRemoveSmall=Remove Small Creatures
@@ -182,6 +184,7 @@ KeyboardShortcuts=Keyboard Shortcuts
#VSubmenuAchievements.java
lblAchievements=Achievements
#VSubmenuDownloaders.java
btnCheckForUpdates=Check for Updates
btnDownloadSetPics=Download LQ Set Pictures
btnDownloadPicsHQ=Download HQ Card Pictures (Very Slow!)
btnDownloadPics=Download LQ Card Pictures
@@ -194,6 +197,7 @@ btnImportPictures=Import Data
btnHowToPlay=How To Play
btnDownloadPrices=Download Card Prices
btnLicensing=License Details
lblCheckForUpdates=Check Forge server to see if there's a more recent release
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)

View File

@@ -0,0 +1,18 @@
[metadata]
Name:Possibility Storm - Theros Beyond Death #06
URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2020/02/149.-THB6-1-scaled.jpg
Goal:Win
Turns:1
Difficulty:Uncommon
Description:Win this turn. Assume any unknown cards drawn by either player are not relevant to solving the puzzle. Your opponent starts with 13 cards in their library. Your opponent has a Pollenbright Druid on top of their library, and twelve other unknown cards in it.
[state]
humanlife=20
ailife=18
turn=1
activeplayer=human
activephase=MAIN1
humanhand=Assassin's Trophy;Unmoored Ego;Applied Biomancy;Underworld Dreams;Tyrant's Scorn
humanlibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
humanbattlefield=Ob Nixilis, the Hate-Twisted|Counters:LOYALTY=2;Nessian Boar;Thief of Sanity;Thief of Sanity;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs;Breeding Pool|NoETBTrigs;Breeding Pool|NoETBTrigs;Breeding Pool|NoETBTrigs
ailibrary=Pollenbright Druid;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
aibattlefield=Silhana Wayfinder;Silhana Wayfinder;Blightbeetle;Wavebreak Hippocamp

View File

@@ -0,0 +1,17 @@
[metadata]
Name:Possibility Storm - Theros Beyond Death #07
URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2020/03/150.-THB7-scaled.jpg
Goal:Win
Turns:100
Difficulty:Mythic
Description:It's your OPPONENT'S turn (first main phase), and you need to win before you lose. Can you do it? Your opponent has no cards in hand and no available mana (assume they just tapped out to cast Goblin Assault Team). You control your opponent's Dreadhorde Butcher with The Akroan War's first chapter ability. Assume the puzzle starts with no cards in either player's graveyard.
[state]
humanlife=3
ailife=11
turn=1
activeplayer=ai
activephase=MAIN1
humanhand=Lazotep Plating;Slaying Fire;So Tiny;Shock;Gideon's Triumph;Aspect of Manticore
humanbattlefield=The Akroan War|Counters:LORE=2|Id:2;Blood Aspirant;Flux Channeler;Naiad of Hidden Coves;Temple of Enlightenment|NoETBTrigs;Temple of Enlightenment|NoETBTrigs;Sacred Foundry|NoETBTrigs;Sacred Foundry|NoETBTrigs
aibattlefield=Underworld Dreams;Underworld Dreams;Underworld Dreams;Ferocity of the Wilds;Goblin Assault Team;Temple Thief;Mire Triton;Dreadhorde Butcher|Id:1
humanprecast=2:DBGainControl->1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 533 KiB

View File

@@ -230,6 +230,15 @@ public enum FSkinProp {
ICO_QUEST_BIG_SWORD (new int[] {320, 1360, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_BAG (new int[] {480, 1360, 160, 160}, PropType.ICON),
//menu icon
ICO_MENU_GALAXY (new int[] {0, 1520, 80, 80}, PropType.ICON),
ICO_MENU_STATS (new int[] {80, 1520, 80, 80}, PropType.ICON),
ICO_MENU_PUZZLE (new int[] {160, 1520, 80, 80}, PropType.ICON),
ICO_MENU_GAUNTLET (new int[] {240, 1520, 80, 80}, PropType.ICON),
ICO_MENU_SEALED (new int[] {320, 1520, 80, 80}, PropType.ICON),
ICO_MENU_DRAFT (new int[] {400, 1520, 80, 80}, PropType.ICON),
ICO_MENU_CONSTRUCTED (new int[] {480, 1520, 80, 80}, PropType.ICON),
//interface icons
ICO_QUESTION (new int[] {560, 800, 32, 32}, PropType.ICON),
ICO_INFORMATION (new int[] {592, 800, 32, 32}, PropType.ICON),

View File

@@ -0,0 +1,247 @@
package forge.download;
import com.google.common.collect.ImmutableList;
import forge.GuiBase;
import forge.model.FModel;
import forge.properties.ForgePreferences;
import forge.util.BuildInfo;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SOptionPane;
import org.apache.commons.lang3.StringUtils;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AutoUpdater {
private final String SNAPSHOT_VERSION_INDEX = "https://snapshots.cardforge.org/";
private final String SNAPSHOT_VERSION_URL = "https://snapshots.cardforge.org/version.txt";
private final String SNAPSHOT_PACKAGE = "https://snapshots.cardforge.org/latest/";
private final String RELEASE_VERSION_URL = "https://releases.cardforge.org/forge/forge-gui-desktop/version.txt";
private final String RELEASE_PACKAGE = "https://releases.cardforge.org/latest/";
private final String RELEASE_MAVEN_METADATA = "https://releases.cardforge.org/forge/forge-gui-desktop/maven-metadata.xml";
private static final boolean VERSION_FROM_METADATA = true;
private static final String TMP_DIR = "tmp/";
public static String[] updateChannels = new String[]{ "none", "snapshot", "release"};
private boolean isLoading;
private String updateChannel;
private String version;
private String buildVersion;
private String versionUrlString;
private String packageUrl;
private String packagePath;
public AutoUpdater(boolean loading) {
// What do I need? Preferences? Splashscreen? UI? Skins?
isLoading = loading;
updateChannel = FModel.getPreferences().getPref(ForgePreferences.FPref.AUTO_UPDATE);
buildVersion = BuildInfo.getVersionString();
}
public boolean attemptToUpdate() {
if (!verifyUpdateable()) {
return false;
}
try {
if (downloadUpdate()) {
extractAndRestart();
}
} catch(IOException | URISyntaxException e) {
return false;
}
return true;
}
private void extractAndRestart() {
extractUpdate();
restartForge();
}
private boolean verifyUpdateable() {
if (buildVersion.contains("GIT")) {
//return false;
}
if (isLoading) {
// TODO This doesn't work yet, because FSkin isn't loaded at the time.
return false;
} else if (updateChannel.equals("none")) {
String message = "You haven't set an update channel. Do you want to check a channel now?";
List<String> options = ImmutableList.of("Cancel", "release", "snapshot");
int option = SOptionPane.showOptionDialog(message, "Manual Check", null, options, 0);
if (option == 0) {
return false;
} else {
updateChannel = options.get(option);
}
}
if (buildVersion.contains("SNAPSHOT")) {
if (!updateChannel.equals("snapshot")) {
System.out.println("Snapshot build versions must use snapshot update channel to work");
return false;
}
versionUrlString = SNAPSHOT_VERSION_URL;
packageUrl = SNAPSHOT_PACKAGE;
} else {
versionUrlString = RELEASE_VERSION_URL;
packageUrl = RELEASE_PACKAGE;
}
// Check the internet connection
if (!testNetConnection()) {
return false;
}
// Download appropriate version file
return compareBuildWithLatestChannelVersion();
}
private boolean testNetConnection() {
try (Socket socket = new Socket()) {
InetSocketAddress address = new InetSocketAddress("releases.cardforge.org", 443);
socket.connect(address, 1000);
return true;
} catch (IOException e) {
return false; // Either timeout or unreachable or failed DNS lookup.
}
}
private boolean compareBuildWithLatestChannelVersion() {
try {
retrieveVersion();
if (StringUtils.isEmpty(version) ) {
return false;
}
if (buildVersion.equals(version)) {
return false;
}
}
catch (Exception e) {
e.printStackTrace();
return false;
}
// If version doesn't match, it's assummably newer.
return true;
}
private void retrieveVersion() throws MalformedURLException {
if (VERSION_FROM_METADATA) {
if (updateChannel.equals("release")) {
extractVersionFromMavenRelease();
} else {
extractVersionFromSnapshotIndex();
}
} else {
URL versionUrl = new URL(versionUrlString);
version = FileUtil.readFileToString(versionUrl);
}
}
private void extractVersionFromSnapshotIndex() throws MalformedURLException {
URL metadataUrl = new URL(SNAPSHOT_VERSION_INDEX);
String index = FileUtil.readFileToString(metadataUrl);
System.out.println(index);
Pattern p = Pattern.compile(">forge-(.*SNAPSHOT)");
Matcher m = p.matcher(index);
while(m.find()){
version = m.group(1);
}
}
private void extractVersionFromMavenRelease() throws MalformedURLException {
URL metadataUrl = new URL(RELEASE_MAVEN_METADATA);
String xml = FileUtil.readFileToString(metadataUrl);
Pattern p = Pattern.compile("<release>(.*)</release>");
Matcher m = p.matcher(xml);
while(m.find()){
version = m.group(1);
}
}
private boolean downloadUpdate() throws URISyntaxException, IOException {
// TODO Change the "auto" to be more auto.
if (isLoading) {
// We need to preload enough of a Skins to show a dialog and a button if we're in loading
// splashScreen.prepareForDialogs();
return downloadFromBrowser();
}
String message = "A new version of Forge is available (" + version + ").\n" +
"You are currently on version (" + buildVersion + ").\n\n" +
"Would you like to update to the new version now?";
final List<String> options = ImmutableList.of("Update Now", "Update Later");
if (SOptionPane.showOptionDialog(message, "New Version Available", null, options, 0) == 0) {
return downloadFromForge();
}
return false;
}
private boolean downloadFromBrowser() throws URISyntaxException, IOException {
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
// Linking directly there will auto download, but won't auto-update
desktop.browse(new URI(packageUrl));
return true;
} else {
System.out.println("Download latest version: " + packageUrl);
return false;
}
}
private boolean downloadFromForge() {
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", "Download the new version..", packageUrl, "tmp/", null, null) {
@Override
public void downloadAndUnzip() {
packagePath = download(version + "-upgrade.tar.bz2");
if (packagePath != null) {
extractAndRestart();
}
}
}, this);
}
};
SwingUtilities.invokeLater(callback);
//
return false;
}
private void extractUpdate() {
// TODOD Something like https://stackoverflow.com/questions/315618/how-do-i-extract-a-tar-file-in-java
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null) {
try {
desktop.open(new File(packagePath).getParentFile());
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println(packagePath);
}
}
private void restartForge() {
if (isLoading || SOptionPane.showConfirmDialog("Forge has been downloaded. You should extract the package and restart Forge for the new version.", "Exit now?")) {
System.exit(0);
}
}
}

View File

@@ -73,72 +73,7 @@ public class GuiDownloadZipService extends GuiDownloadService {
String zipFilename = download("temp.zip");
if (zipFilename == null) { return; }
//if assets.zip downloaded successfully, unzip into destination folder
try {
GuiBase.getInterface().preventSystemSleep(true); //prevent system from going into sleep mode while unzipping
if (deleteFolder != null) {
final File deleteDir = new File(deleteFolder);
if (deleteDir.exists()) {
//attempt to delete previous res directory if to be rebuilt
progressBar.reset();
progressBar.setDescription("Deleting old " + desc + "...");
if (deleteFolder.equals(destFolder)) { //move zip file to prevent deleting it
final String oldZipFilename = zipFilename;
zipFilename = deleteDir.getParentFile().getAbsolutePath() + File.separator + "temp.zip";
Files.move(new File(oldZipFilename), new File(zipFilename));
}
FileUtil.deleteDirectory(deleteDir);
}
}
final ZipFile zipFile = new ZipFile(zipFilename);
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
progressBar.reset();
progressBar.setPercentMode(true);
progressBar.setDescription("Extracting " + desc);
progressBar.setMaximum(zipFile.size());
FileUtil.ensureDirectoryExists(destFolder);
int count = 0;
int failedCount = 0;
while (entries.hasMoreElements()) {
if (cancel) { break; }
try {
final ZipEntry entry = entries.nextElement();
final String path = destFolder + File.separator + entry.getName();
if (entry.isDirectory()) {
new File(path).mkdir();
progressBar.setValue(++count);
continue;
}
copyInputStream(zipFile.getInputStream(entry), path);
progressBar.setValue(++count);
filesExtracted++;
}
catch (final Exception e) { //don't quit out completely if an entry is not UTF-8
progressBar.setValue(++count);
failedCount++;
}
}
if (failedCount > 0) {
Log.error("Downloading " + desc, failedCount + " " + desc + " could not be extracted");
}
zipFile.close();
new File(zipFilename).delete();
}
catch (final Exception e) {
e.printStackTrace();
}
finally {
GuiBase.getInterface().preventSystemSleep(false);
}
extract(zipFilename);
}
public String download(final String filename) {
@@ -211,6 +146,75 @@ public class GuiDownloadZipService extends GuiDownloadService {
}
}
public void extract(String zipFilename) {
//if assets.zip downloaded successfully, unzip into destination folder
try {
GuiBase.getInterface().preventSystemSleep(true); //prevent system from going into sleep mode while unzipping
if (deleteFolder != null) {
final File deleteDir = new File(deleteFolder);
if (deleteDir.exists()) {
//attempt to delete previous res directory if to be rebuilt
progressBar.reset();
progressBar.setDescription("Deleting old " + desc + "...");
if (deleteFolder.equals(destFolder)) { //move zip file to prevent deleting it
final String oldZipFilename = zipFilename;
zipFilename = deleteDir.getParentFile().getAbsolutePath() + File.separator + "temp.zip";
Files.move(new File(oldZipFilename), new File(zipFilename));
}
FileUtil.deleteDirectory(deleteDir);
}
}
final ZipFile zipFile = new ZipFile(zipFilename);
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
progressBar.reset();
progressBar.setPercentMode(true);
progressBar.setDescription("Extracting " + desc);
progressBar.setMaximum(zipFile.size());
FileUtil.ensureDirectoryExists(destFolder);
int count = 0;
int failedCount = 0;
while (entries.hasMoreElements()) {
if (cancel) { break; }
try {
final ZipEntry entry = entries.nextElement();
final String path = destFolder + File.separator + entry.getName();
if (entry.isDirectory()) {
new File(path).mkdir();
progressBar.setValue(++count);
continue;
}
copyInputStream(zipFile.getInputStream(entry), path);
progressBar.setValue(++count);
filesExtracted++;
}
catch (final Exception e) { //don't quit out completely if an entry is not UTF-8
progressBar.setValue(++count);
failedCount++;
}
}
if (failedCount > 0) {
Log.error("Downloading " + desc, failedCount + " " + desc + " could not be extracted");
}
zipFile.close();
new File(zipFilename).delete();
}
catch (final Exception e) {
e.printStackTrace();
}
finally {
GuiBase.getInterface().preventSystemSleep(false);
}
}
protected void copyInputStream(final InputStream in, final String outPath) throws IOException {
final byte[] buffer = new byte[1024];
int len;

View File

@@ -28,6 +28,7 @@ import forge.card.CardType;
import forge.deck.CardArchetypeLDAGenerator;
import forge.deck.CardRelationMatrixGenerator;
import forge.deck.io.DeckPreferences;
import forge.download.AutoUpdater;
import forge.game.GameFormat;
import forge.game.GameType;
import forge.game.card.CardUtil;
@@ -117,7 +118,6 @@ public final class FModel {
Localizer.getInstance().initialize(FModel.getPreferences().getPref(FPref.UI_LANGUAGE), ForgeConstants.LANG_DIR);
//load card database
final ProgressObserver progressBarBridge = (progressBar == null) ?
ProgressObserver.emptyObserver : new ProgressObserver() {
@Override
@@ -143,6 +143,11 @@ public final class FModel {
}
};
if (new AutoUpdater(true).attemptToUpdate()) {
//
}
//load card database
final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge,
FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
final CardStorageReader tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge,
@@ -220,8 +225,6 @@ public final class FModel {
achievements.put(GameType.Quest, new QuestAchievements());
achievements.put(GameType.PlanarConquest, new PlanarConquestAchievements());
achievements.put(GameType.Puzzle, new PuzzleAchievements());
//preload AI profiles
AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR);

View File

@@ -153,6 +153,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
//TODO This should be removed after the update that requires Java 8.
DISABLE_DISPLAY_JAVA_8_UPDATE_WARNING("false"),
AUTO_UPDATE("none"),
USE_SENTRY("false"), // this controls whether automated bug reporting is done or not
MATCH_HOT_SEAT_MODE("false"), //this only applies to mobile game