Merge remote-tracking branch 'upstream/master' into donot_translation_token

This commit is contained in:
CCTV-1
2021-03-03 12:31:55 +08:00
7889 changed files with 141750 additions and 40318 deletions

0
forge-gui/src/main/config/create-dmg Normal file → Executable file
View File

View File

@@ -1,3 +0,0 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$

View File

@@ -1,3 +0,0 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$

View File

@@ -6,23 +6,36 @@ public class GuiBase {
private static IGuiBase guiInterface;
private static boolean propertyConfig = true;
private static boolean networkplay = false;
private static boolean isAndroidport = false;
private static boolean interrupted = false;
private static String deviceName = "";
private static String androidRelease = "";
private static int androidAPI = 0;
private static int deviceRAM = 0;
public static IGuiBase getInterface() {
return guiInterface;
}
public static void setInterface(IGuiBase i0) {
guiInterface = i0;
}
public static void enablePropertyConfig(boolean value) {
propertyConfig = value;
}
public static boolean isNetworkplay() {
return networkplay;
}
public static void setNetworkplay(boolean value) {
networkplay = value;
}
public static boolean hasPropertyConfig() {
return propertyConfig;
public static IGuiBase getInterface() { return guiInterface; }
public static void setInterface(IGuiBase i0) { guiInterface = i0; }
public static void setIsAndroid(boolean value) { isAndroidport = value; }
public static boolean isAndroid() { return isAndroidport; }
public static void setDeviceInfo(String DeviceName, String AndroidName, int AndroidAPI, int RAM) {
deviceName = DeviceName;
androidRelease = AndroidName;
androidAPI = AndroidAPI;
deviceRAM = RAM;
}
public static String getDeviceName() { return deviceName; }
public static String getAndroidRelease() { return androidRelease; }
public static int getAndroidAPILevel() { return androidAPI; }
public static int getDeviceRAM() { return deviceRAM; }
public static boolean isNetworkplay() { return networkplay; }
public static void setNetworkplay(boolean value) { networkplay = value; }
public static boolean hasPropertyConfig() { return propertyConfig; }
public static void enablePropertyConfig(boolean value) { propertyConfig = value; }
public static void setInterrupted(boolean value) { interrupted = value; }
public static boolean isInterrupted() { return interrupted; }
}

View File

@@ -18,8 +18,8 @@ import org.w3c.dom.NodeList;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameType;
import forge.game.Match;
import forge.game.player.Player;
import forge.interfaces.IComboBox;
import forge.model.FModel;
@@ -28,6 +28,7 @@ import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.ThreadUtil;
import forge.util.XmlUtil;
import forge.util.Localizer;
public abstract class AchievementCollection implements Iterable<Achievement> {
protected final Map<String, Achievement> achievements = Maps.newLinkedHashMap();
@@ -44,7 +45,7 @@ public abstract class AchievementCollection implements Iterable<Achievement> {
return;
}
final Game game = controller.getGame();
final Match match = controller.getMatch();
final Player player = controller.getPlayer();
//update all achievements for GUI player after game finished
@@ -53,16 +54,16 @@ public abstract class AchievementCollection implements Iterable<Achievement> {
ThreadUtil.invokeInGameThread(new Runnable() {
@Override
public void run() {
doUpdateAllAchievements(game, player);
doUpdateAllAchievements(match, player);
}
});
} else {
doUpdateAllAchievements(game, player);
doUpdateAllAchievements(match, player);
}
}
private static void doUpdateAllAchievements(final Game game, final Player player) {
FModel.getAchievements(game.getRules().getGameType()).updateAll(player);
private static void doUpdateAllAchievements(final Match match, final Player player) {
FModel.getAchievements(match.getRules().getGameType()).updateAll(player);
AltWinAchievements.instance.updateAll(player);
PlaneswalkerAchievements.instance.updateAll(player);
ChallengeAchievements.instance.updateAll(player);
@@ -85,7 +86,7 @@ public abstract class AchievementCollection implements Iterable<Achievement> {
}
protected AchievementCollection(String name0, String filename0, boolean isLimitedFormat0, String path0) {
name = name0;
name = Localizer.getInstance().getMessage(name0);
filename = filename0;
isLimitedFormat = isLimitedFormat0;
path = path0;

View File

@@ -7,12 +7,14 @@ import forge.game.player.Player;
import forge.item.IPaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.util.Localizer;
import forge.util.CardTranslation;
public class AltWinAchievements extends AchievementCollection {
public static final AltWinAchievements instance = new AltWinAchievements();
private AltWinAchievements() {
super("Alternate Win Conditions", ForgeConstants.ACHIEVEMENTS_DIR + "altwin.xml", false, ForgeConstants.ALTWIN_ACHIEVEMENT_LIST_FILE);
super("lblAlternateWinConditions", ForgeConstants.ACHIEVEMENTS_DIR + "altwin.xml", false, ForgeConstants.ALTWIN_ACHIEVEMENT_LIST_FILE);
}
@Override
@@ -40,6 +42,9 @@ public class AltWinAchievements extends AchievementCollection {
}
Achievement achievement = achievements.get(altWinCondition);
if (achievement == null) {
achievement = achievements.get("Emblem - " + altWinCondition); // indirectly winning through an emblem
}
if (achievement != null) {
achievement.update(player);
save();
@@ -49,7 +54,7 @@ public class AltWinAchievements extends AchievementCollection {
private class AltWinAchievement extends ProgressiveAchievement {
private AltWinAchievement(String cardName0, String displayName0, String flavorText0) {
super(cardName0, displayName0, "Win a game with " + cardName0, flavorText0);
super(CardTranslation.getTranslatedName(cardName0), displayName0, Localizer.getInstance().getMessage("lblWinGameWithCard", CardTranslation.getTranslatedName(cardName0)), flavorText0);
}
@Override
@@ -64,7 +69,7 @@ public class AltWinAchievements extends AchievementCollection {
@Override
public String getNoun() {
return "Win";
return Localizer.getInstance().getMessage("lblWin");
}
}
}

View File

@@ -3,14 +3,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.GameType;
import forge.game.player.Player;
import forge.util.Localizer;
public class ArcaneMaster extends Achievement {
public ArcaneMaster() {
super("ArcaneMaster", "Arcane Master", "Win a game without casting", Integer.MAX_VALUE,
"more than 3 spells", 3,
"more than 2 spells", 2,
"more than 1 spell", 1,
"any spells", 0);
super("ArcaneMaster", Localizer.getInstance().getMessage("lblArcaneMaster"),
Localizer.getInstance().getMessage("lblWinGameWithOutCasting"), Integer.MAX_VALUE,
Localizer.getInstance().getMessage("lblMore3Spells"), 3,
Localizer.getInstance().getMessage("lblMore2Spells"), 2,
Localizer.getInstance().getMessage("lblMore1Spell"), 1,
Localizer.getInstance().getMessage("lblAnySpells"), 0
);
}
@Override
@@ -26,6 +29,6 @@ public class ArcaneMaster extends Achievement {
@Override
protected String getNoun() {
return "Spell";
return Localizer.getInstance().getMessage("lblSpell");
}
}

View File

@@ -4,16 +4,19 @@ import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.GameLossReason;
import forge.game.player.Player;
import forge.util.Localizer;
public class Blackjack extends Achievement {
private static final int THRESHOLD = 21;
public Blackjack(int silver0, int gold0, int mythic0) {
super("Blackjack", "Blackjack", "Win a game from your commander dealing", 0,
String.format("%d combat damage", THRESHOLD), THRESHOLD,
String.format("%d combat damage", silver0), silver0,
String.format("%d combat damage", gold0), gold0,
String.format("%d combat damage", mythic0), mythic0);
super("Blackjack", Localizer.getInstance().getMessage("lblBlackjack"),
Localizer.getInstance().getMessage("lblWinGameFromYourCommanderDealing"), 0,
Localizer.getInstance().getMessage("lblNCombatDamage", String.valueOf(THRESHOLD)), THRESHOLD,
Localizer.getInstance().getMessage("lblNCombatDamage", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblNCombatDamage", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblNCombatDamage", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -40,7 +43,7 @@ public class Blackjack extends Achievement {
@Override
public String getNoun() {
return "Damage";
return Localizer.getInstance().getMessage("lblDamage");
}
@Override

View File

@@ -5,12 +5,13 @@ import forge.game.Game;
import forge.game.GameType;
import forge.game.player.Player;
import forge.properties.ForgeConstants;
import forge.util.Localizer;
public class ChallengeAchievements extends AchievementCollection {
public static final ChallengeAchievements instance = new ChallengeAchievements();
private ChallengeAchievements() {
super("Challenges", ForgeConstants.ACHIEVEMENTS_DIR + "challenges.xml", false);
super("lblChallenges", ForgeConstants.ACHIEVEMENTS_DIR + "challenges.xml", false);
}
@Override
@@ -24,8 +25,14 @@ public class ChallengeAchievements extends AchievementCollection {
add(new NoSpells());
add(new NoLands());
add(new Domain());
add("Chromatic", "Chromatic", "Win a game after casting a 5 color spell", "With great color requirements comes great power.");
add("Epic", "Epic", "Win a game after resolving a spell with the Epic keyword", "When it's the last spell you ever cast, you better make it count!");
add("Chromatic", Localizer.getInstance().getMessage("lblChromatic"),
Localizer.getInstance().getMessage("lblWinGameAfterCasting5CSpell"),
Localizer.getInstance().getMessage("lblGreatColorComesPower")
);
add("Epic", Localizer.getInstance().getMessage("lblEpic"),
Localizer.getInstance().getMessage("lblWinGameAfterResolvingWithEpicSpell"),
Localizer.getInstance().getMessage("lblWhenItsYouLastSpellBetterMakeCount")
);
}
private void add(String key0, String displayName0, String description0, String flavorText0) {
@@ -39,7 +46,7 @@ public class ChallengeAchievements extends AchievementCollection {
@Override
protected final String getNoun() {
return "Win";
return Localizer.getInstance().getMessage("lblWin");
}
@Override
@@ -54,7 +61,7 @@ public class ChallengeAchievements extends AchievementCollection {
public static abstract class DeckChallengeAchievement extends ChallengeAchievement {
protected DeckChallengeAchievement(String key0, String displayName0, String condition0, String flavorText0) {
super(key0, displayName0, "Win a game using a deck " + condition0, flavorText0);
super(key0, displayName0, Localizer.getInstance().getMessage("lblWinGameUsingTargetDeck", condition0), flavorText0);
}
@Override

View File

@@ -5,7 +5,7 @@ import forge.properties.ForgeConstants;
public class ConstructedAchievements extends AchievementCollection {
public ConstructedAchievements() {
super("Constructed", ForgeConstants.ACHIEVEMENTS_DIR + "constructed.xml", false);
super("lblConstructed", ForgeConstants.ACHIEVEMENTS_DIR + "constructed.xml", false);
}
//add achievements that should appear at the bottom below core achievements for each game mode

View File

@@ -3,14 +3,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.GameLossReason;
import forge.game.player.Player;
import forge.util.Localizer;
public class DeckedOut extends Achievement {
public DeckedOut(int silver0, int gold0, int mythic0) {
super("DeckedOut", "Decked Out", "Win a game from opponent", Integer.MAX_VALUE,
"drawing into an empty library", Integer.MAX_VALUE - 1,
String.format("drawing into an empty library by turn %d", silver0), silver0,
String.format("drawing into an empty library by turn %d", gold0), gold0,
String.format("drawing into an empty library by turn %d", mythic0), mythic0);
super("DeckedOut", Localizer.getInstance().getMessage("lblDeckedOut"),
Localizer.getInstance().getMessage("lblWinGameFromOpponent"), Integer.MAX_VALUE,
Localizer.getInstance().getMessage("lblDrawingEmptyLibrary"), Integer.MAX_VALUE - 1,
Localizer.getInstance().getMessage("lblDrawingEmptyLibraryByNTurn", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblDrawingEmptyLibraryByNTurn", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblDrawingEmptyLibraryByNTurn", String.valueOf(mythic0)), mythic0
);
}
@Override

View File

@@ -9,10 +9,14 @@ import forge.game.zone.ZoneType;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import forge.util.Localizer;
public class Domain extends ProgressiveAchievement {
public Domain() {
super("Domain", "Domain", "Win a game with one of each basic land on the battlefield", "It's nice being able to cast anything you want.");
super("Domain", Localizer.getInstance().getMessage("lblDomain"),
Localizer.getInstance().getMessage("lblWinGameWithOneOfEachBasicLandOnTheBattlefield"),
Localizer.getInstance().getMessage("lblAbleToCastAnytingNiceBeing")
);
}
private HashMap<String, String> basicLandMap = new HashMap<String, String>() {
@@ -51,6 +55,6 @@ public class Domain extends ProgressiveAchievement {
@Override
protected String getNoun() {
return "Win";
return Localizer.getInstance().getMessage("lblWin");
}
}

View File

@@ -4,7 +4,7 @@ import forge.properties.ForgeConstants;
public class DraftAchievements extends AchievementCollection {
public DraftAchievements() {
super("Booster Draft", ForgeConstants.ACHIEVEMENTS_DIR + "draft.xml", true);
super("lblBoosterDraft", ForgeConstants.ACHIEVEMENTS_DIR + "draft.xml", true);
}
//add achievements that should appear at the bottom below core achievements for each game mode

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class GameWinStreak extends StreakAchievement {
public GameWinStreak(int bronze0, int silver0, int gold0, int mythic0) {
super("GameWinStreak", "Game Win Streak", null,
String.format("Win %d games in a row", bronze0), bronze0,
String.format("Win %d games in a row", silver0), silver0,
String.format("Win %d games in a row", gold0), gold0,
String.format("Win %d games in a row", mythic0), mythic0);
super("GameWinStreak",
Localizer.getInstance().getMessage("lblGameWinStreak"), null,
Localizer.getInstance().getMessage("lblWinNGamesInARow", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblWinNGamesInARow", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblWinNGamesInARow", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblWinNGamesInARow", String.valueOf(mythic0)), mythic0
);
}
@Override

View File

@@ -3,14 +3,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
public class Hellbent extends Achievement {
public Hellbent() {
super("Hellbent", "Hellbent", "Win a game with no cards in your", 0,
"hand", 1,
"hand or library", 2,
"hand, library, or graveyard", 3,
"hand, library, graveyard, or battlefield", 4);
super("Hellbent", Localizer.getInstance().getMessage("lblHellbent"),
Localizer.getInstance().getMessage("lblWinGameWithNoCardsInYour"), 0,
Localizer.getInstance().getMessage("lblHandZone"), 1,
Localizer.getInstance().getMessage("lblhandOrlibrary"), 2,
Localizer.getInstance().getMessage("lblHandOrLibraryOrGraveyard"), 3,
Localizer.getInstance().getMessage("lblHandOrLibraryOrGraveyardOrBattlefield"), 4
);
}
@Override

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class LifeToSpare extends Achievement {
public LifeToSpare(int bronze0, int silver0, int gold0, int mythic0) {
super("LifeToSpare", "Life to Spare", "Win a game with", 0,
String.format("%d life more than you started with", bronze0), bronze0,
String.format("%d life more than you started with", silver0), silver0,
String.format("%d life more than you started with", gold0), gold0,
String.format("%d life more than you started with", mythic0), mythic0);
super("LifeToSpare", Localizer.getInstance().getMessage("lblLifeToSpare"),
Localizer.getInstance().getMessage("lblWinGameWith"), 0,
Localizer.getInstance().getMessage("lblMoreThanStartedLifeN", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblMoreThanStartedLifeN", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblMoreThanStartedLifeN", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblMoreThanStartedLifeN", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -25,7 +28,7 @@ public class LifeToSpare extends Achievement {
@Override
protected String getNoun() {
return "Life";
return Localizer.getInstance().getMessage("lblLife");
}
@Override
protected boolean pluralizeNoun() {

View File

@@ -5,14 +5,17 @@ import forge.game.GameType;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
public class ManaFlooded extends Achievement {
public ManaFlooded(int bronze0, int silver0, int gold0, int mythic0) {
super("ManaFlooded", "Mana Flooded", "Win a game with at least", 0,
String.format("%d lands on the battlefield", bronze0), bronze0,
String.format("%d lands on the battlefield", silver0), silver0,
String.format("%d lands on the battlefield", gold0), gold0,
String.format("%d lands on the battlefield", mythic0), mythic0);
super("ManaFlooded", Localizer.getInstance().getMessage("lblManaFlooded"),
Localizer.getInstance().getMessage("lblWinGameWithLeast"), 0,
Localizer.getInstance().getMessage("lblNLandOnTheBattlefield", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblNLandOnTheBattlefield", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblNLandOnTheBattlefield", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblNLandOnTheBattlefield", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -34,6 +37,6 @@ public class ManaFlooded extends Achievement {
@Override
protected String getNoun() {
return "Land";
return Localizer.getInstance().getMessage("lblLand");
}
}

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class ManaScrewed extends Achievement {
public ManaScrewed() {
super("ManaScrewed", "Mana Screwed", "Win a game despite playing only", Integer.MAX_VALUE,
"3 lands", 3,
"2 lands", 2,
"1 land", 1,
"0 lands", 0);
super("ManaScrewed", Localizer.getInstance().getMessage("lblManaScrewed"),
Localizer.getInstance().getMessage("lblWinGameOnlyPlaing"), Integer.MAX_VALUE,
Localizer.getInstance().getMessage("lblNLands", String.valueOf(3)), 3,
Localizer.getInstance().getMessage("lblNLands", String.valueOf(2)), 2,
Localizer.getInstance().getMessage("lblNLands", String.valueOf(1)), 1,
Localizer.getInstance().getMessage("lblNLands", String.valueOf(0)), 0
);
}
@Override
@@ -22,7 +25,7 @@ public class ManaScrewed extends Achievement {
@Override
protected String getNoun() {
return "Land";
return Localizer.getInstance().getMessage("lblLand");
}
@Override

View File

@@ -2,14 +2,16 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class MatchWinStreak extends StreakAchievement {
public MatchWinStreak(int bronze0, int silver0, int gold0, int mythic0) {
super("MatchWinStreak", "Match Win Streak", null,
String.format("Win %d matches in a row", bronze0), bronze0,
String.format("Win %d matches in a row", silver0), silver0,
String.format("Win %d matches in a row", gold0), gold0,
String.format("Win %d matches in a row", mythic0), mythic0);
super("MatchWinStreak",Localizer.getInstance().getMessage("lblMatchWinStreak"), null,
Localizer.getInstance().getMessage("lblWinNMatchesInARow", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblWinNMatchesInARow", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblWinNMatchesInARow", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblWinNMatchesInARow", String.valueOf(mythic0)), mythic0
);
}
@Override

View File

@@ -2,14 +2,16 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class NeedForSpeed extends Achievement {
public NeedForSpeed(int bronze0, int silver0, int gold0, int mythic0) {
super("NeedForSpeed", "Need for Speed", null, Integer.MAX_VALUE,
String.format("Win a game by turn %d", bronze0), bronze0,
String.format("Win a game by turn %d", silver0), silver0,
String.format("Win a game by turn %d", gold0), gold0,
String.format("Win a game by turn %d", mythic0), mythic0);
super("NeedForSpeed", Localizer.getInstance().getMessage("lblNeedForSpeed"), null, Integer.MAX_VALUE,
Localizer.getInstance().getMessage("lblWinGameByNTurn", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblWinGameByNTurn", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblWinGameByNTurn", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblWinGameByNTurn", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -22,7 +24,7 @@ public class NeedForSpeed extends Achievement {
@Override
protected String getNoun() {
return "Turn";
return Localizer.getInstance().getMessage("lblTurn");
}
@Override

View File

@@ -5,10 +5,13 @@ import java.util.Map.Entry;
import forge.achievement.ChallengeAchievements.DeckChallengeAchievement;
import forge.deck.Deck;
import forge.item.PaperCard;
import forge.util.Localizer;
public class NoCreatures extends DeckChallengeAchievement {
public NoCreatures() {
super("NoCreatures", "No Creatures", "with no creatures", "I'm not really an animal person.");
super("NoCreatures", Localizer.getInstance().getMessage("lblNoCreatures"),
Localizer.getInstance().getMessage("lblWithNoCreatures"), Localizer.getInstance().getMessage("lblIMNotReallyAnimalPerson")
);
}
@Override

View File

@@ -5,10 +5,14 @@ import java.util.Map.Entry;
import forge.achievement.ChallengeAchievements.DeckChallengeAchievement;
import forge.deck.Deck;
import forge.item.PaperCard;
import forge.util.Localizer;
public class NoLands extends DeckChallengeAchievement {
public NoLands() {
super("NoLands", "No Lands", "with no lands", "I prefer mana from more artificial sources.");
super("NoLands", Localizer.getInstance().getMessage("lblNoLands"),
Localizer.getInstance().getMessage("lblWithNoLands"),
Localizer.getInstance().getMessage("lblIMorePreferManaFromArtificial")
);
}
@Override

View File

@@ -6,10 +6,14 @@ import forge.achievement.ChallengeAchievements.DeckChallengeAchievement;
import forge.card.CardType;
import forge.deck.Deck;
import forge.item.PaperCard;
import forge.util.Localizer;
public class NoSpells extends DeckChallengeAchievement {
public NoSpells() {
super("NoSpells", "No Spells", "with only creatures and lands", "I let my army do the talking.");
super("NoSpells", Localizer.getInstance().getMessage("lblNoSpells"),
Localizer.getInstance().getMessage("lblWithOnlyCreaturesAndLands"),
Localizer.getInstance().getMessage("lblILetMyArmyTalking")
);
}
@Override

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class Overkill extends Achievement {
public Overkill(int bronze0, int silver0, int gold0, int mythic0) {
super("Overkill", "Overkill", "Win a game with opponent at", 0,
String.format("%d life", bronze0), bronze0,
String.format("%d life", silver0), silver0,
String.format("%d life", gold0), gold0,
String.format("%d life", mythic0), mythic0);
super("Overkill", Localizer.getInstance().getMessage("lblOverkill"),
Localizer.getInstance().getMessage("lblWinGameWithOppentAt"), 0,
Localizer.getInstance().getMessage("lblNLife", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblNLife", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblNLife", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblNLife", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -25,7 +28,7 @@ public class Overkill extends Achievement {
@Override
protected String getNoun() {
return "Life";
return Localizer.getInstance().getMessage("lblLife");
}
@Override
protected boolean pluralizeNoun() {

View File

@@ -5,7 +5,7 @@ import forge.properties.ForgeConstants;
public class PlanarConquestAchievements extends AchievementCollection {
public PlanarConquestAchievements() {
super("Planar Conquest", ForgeConstants.ACHIEVEMENTS_DIR + "planar_conquest.xml", true);
super("lblPlanarConquest", ForgeConstants.ACHIEVEMENTS_DIR + "planar_conquest.xml", true);
}
//add achievements that should appear at the bottom below core achievements for each game mode

View File

@@ -8,6 +8,8 @@ import forge.game.player.Player;
import forge.item.IPaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.util.Localizer;
import forge.util.CardTranslation;
public class PlaneswalkerAchievements extends AchievementCollection {
public static final PlaneswalkerAchievements instance = new PlaneswalkerAchievements();
@@ -17,7 +19,7 @@ public class PlaneswalkerAchievements extends AchievementCollection {
}
private PlaneswalkerAchievements() {
super("Planeswalker Ultimates", ForgeConstants.ACHIEVEMENTS_DIR + "planeswalkers.xml", false, ForgeConstants.PLANESWALKER_ACHIEVEMENT_LIST_FILE);
super("lblPlaneswalkerUltimates", ForgeConstants.ACHIEVEMENTS_DIR + "planeswalkers.xml", false, ForgeConstants.PLANESWALKER_ACHIEVEMENT_LIST_FILE);
}
@Override
@@ -49,7 +51,7 @@ public class PlaneswalkerAchievements extends AchievementCollection {
private class PlaneswalkerUltimate extends ProgressiveAchievement {
private PlaneswalkerUltimate(String cardName0, String displayName0, String flavorText0) {
super(cardName0, displayName0, "Win a game after activating " + cardName0 + "'s ultimate", flavorText0);
super(cardName0, displayName0, Localizer.getInstance().getMessage("lblWinGameAfterActivatingCardUltimate", CardTranslation.getTranslatedName(cardName0)), flavorText0);
}
@Override
@@ -64,7 +66,7 @@ public class PlaneswalkerAchievements extends AchievementCollection {
@Override
protected String getNoun() {
return "Win";
return Localizer.getInstance().getMessage("lblWin");
}
}
}

View File

@@ -3,16 +3,19 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.GameLossReason;
import forge.game.player.Player;
import forge.util.Localizer;
public class Poisoned extends Achievement {
private static final int THRESHOLD = 10;
public Poisoned(int silver0, int gold0, int mythic0) {
super("Poisoned", "Poisoned", "Win a game by giving opponent", 0,
String.format("%d poison counters", THRESHOLD), THRESHOLD,
String.format("%d poison counters", silver0), silver0,
String.format("%d poison counters", gold0), gold0,
String.format("%d poison counters", mythic0), mythic0);
super("Poisoned", Localizer.getInstance().getMessage("lblPoisoned"),
Localizer.getInstance().getMessage("lblWinGameByGivingOppoent"), 0,
Localizer.getInstance().getMessage("lblNPoisonCounters", String.valueOf(THRESHOLD)), THRESHOLD,
Localizer.getInstance().getMessage("lblNPoisonCounters", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblNPoisonCounters", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblNPoisonCounters", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -28,6 +31,6 @@ public class Poisoned extends Achievement {
@Override
protected String getNoun() {
return "Counter";
return Localizer.getInstance().getMessage("lblCounter");
}
}

View File

@@ -4,7 +4,7 @@ import forge.properties.ForgeConstants;
public class PuzzleAchievements extends AchievementCollection {
public PuzzleAchievements() {
super("Puzzle Mode", ForgeConstants.ACHIEVEMENTS_DIR + "puzzle.xml", false);
super("lblPuzzleMode", ForgeConstants.ACHIEVEMENTS_DIR + "puzzle.xml", false);
}
@Override

View File

@@ -4,7 +4,7 @@ import forge.properties.ForgeConstants;
public class QuestAchievements extends AchievementCollection {
public QuestAchievements() {
super("Quest Mode", ForgeConstants.ACHIEVEMENTS_DIR + "quest.xml", false);
super("lblQuestMode", ForgeConstants.ACHIEVEMENTS_DIR + "quest.xml", false);
}
//add achievements that should appear at the bottom below core achievements for each game mode

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class RagsToRiches extends Achievement {
public RagsToRiches() {
super("RagsToRiches", "Rags to Riches", "Win a game after mulliganing to", 7,
"4 cards", 4,
"3 cards", 3,
"2 cards", 2,
"1 card", 1);
super("RagsToRiches", Localizer.getInstance().getMessage("lblRagsToRiches"),
Localizer.getInstance().getMessage("lblWinGameAfterMulliganingTo"), 7,
Localizer.getInstance().getMessage("lblNCards", String.valueOf(4)), 4,
Localizer.getInstance().getMessage("lblNCards", String.valueOf(3)), 3,
Localizer.getInstance().getMessage("lblNCards", String.valueOf(2)), 2,
Localizer.getInstance().getMessage("lblNCards", String.valueOf(1)), 1
);
}
@Override
@@ -22,6 +25,6 @@ public class RagsToRiches extends Achievement {
@Override
protected String getNoun() {
return "Card";
return Localizer.getInstance().getMessage("lblCard");
}
}

View File

@@ -4,7 +4,7 @@ import forge.properties.ForgeConstants;
public class SealedAchievements extends AchievementCollection {
public SealedAchievements() {
super("Sealed Deck", ForgeConstants.ACHIEVEMENTS_DIR + "sealed.xml", true);
super("lblSealedDeck", ForgeConstants.ACHIEVEMENTS_DIR + "sealed.xml", true);
}
//add achievements that should appear at the bottom below core achievements for each game mode

View File

@@ -2,14 +2,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class StormChaser extends Achievement {
public StormChaser(int bronze0, int silver0, int gold0, int mythic0) {
super("StormChaser", "Storm Chaser", "Win a game after casting", 0,
String.format("%d spells in a single turn", bronze0), bronze0,
String.format("%d spells in a single turn", silver0), silver0,
String.format("%d spells in a single turn", gold0), gold0,
String.format("%d spells in a single turn", mythic0), mythic0);
super("StormChaser", Localizer.getInstance().getMessage("lblStormChaser"),
Localizer.getInstance().getMessage("lblWinGameAfterCasting"), 0,
Localizer.getInstance().getMessage("lblNSpellInSingleTurn", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblNSpellInSingleTurn", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblNSpellInSingleTurn", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblNSpellInSingleTurn", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -22,6 +25,6 @@ public class StormChaser extends Achievement {
@Override
protected String getNoun() {
return "Spell";
return Localizer.getInstance().getMessage("lblSpell");
}
}

View File

@@ -2,14 +2,16 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class TotalGameWins extends ProgressiveAchievement {
public TotalGameWins(int bronze0, int silver0, int gold0, int mythic0) {
super("TotalGameWins", "Total Game Wins", null,
String.format("Win %d games", bronze0), bronze0,
String.format("Win %d games", silver0), silver0,
String.format("Win %d games", gold0), gold0,
String.format("Win %d games", mythic0), mythic0);
super("TotalGameWins", Localizer.getInstance().getMessage("lblTotalGameWins"), null,
Localizer.getInstance().getMessage("lblWinNGames", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblWinNGames", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblWinNGames", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblWinNGames", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -19,6 +21,6 @@ public class TotalGameWins extends ProgressiveAchievement {
@Override
protected String getNoun() {
return "Game";
return Localizer.getInstance().getMessage("lblGame");
}
}

View File

@@ -2,14 +2,16 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class TotalMatchWins extends ProgressiveAchievement {
public TotalMatchWins(int bronze0, int silver0, int gold0, int mythic0) {
super("TotalMatchWins", "Total Match Wins", null,
String.format("Win %d matches", bronze0), bronze0,
String.format("Win %d matches", silver0), silver0,
String.format("Win %d matches", gold0), gold0,
String.format("Win %d matches", mythic0), mythic0);
super("TotalMatchWins", Localizer.getInstance().getMessage("lblTotalMatchWins"), null,
Localizer.getInstance().getMessage("lblWinNMatches", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblWinNMatches", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblWinNMatches", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblWinNMatches", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -19,6 +21,6 @@ public class TotalMatchWins extends ProgressiveAchievement {
@Override
protected String getNoun() {
return "Match";
return Localizer.getInstance().getMessage("lblMatch");
}
}

View File

@@ -2,14 +2,16 @@ package forge.achievement;
import forge.game.Game;
import forge.game.player.Player;
import forge.util.Localizer;
public class TotalPuzzlesSolved extends ProgressiveAchievement {
public TotalPuzzlesSolved(int bronze0, int silver0, int gold0, int mythic0) {
super("TotalPuzzlesSolved", "Total Puzzles Solved", null,
String.format(bronze0 == 1 ? "Solve a puzzle" : "Solve %d puzzles", bronze0), bronze0,
String.format("Solve %d puzzles", silver0), silver0,
String.format("Solve %d puzzles", gold0), gold0,
String.format("Solve %d puzzles", mythic0), mythic0);
super("TotalPuzzlesSolved", Localizer.getInstance().getMessage("lblTotalPuzzlesSolved"), null,
Localizer.getInstance().getMessage("lblSolveNPuzzles", String.valueOf(bronze0)), bronze0,
Localizer.getInstance().getMessage("lblSolveNPuzzles", String.valueOf(silver0)), silver0,
Localizer.getInstance().getMessage("lblSolveNPuzzles", String.valueOf(gold0)), gold0,
Localizer.getInstance().getMessage("lblSolveNPuzzles", String.valueOf(mythic0)), mythic0
);
}
@Override
@@ -19,7 +21,7 @@ public class TotalPuzzlesSolved extends ProgressiveAchievement {
@Override
protected String getNoun() {
return "Puzzle";
return Localizer.getInstance().getMessage("lblPuzzle");
}
}

View File

@@ -3,17 +3,17 @@ package forge.achievement;
import forge.game.Game;
import forge.game.GameType;
import forge.game.player.Player;
import forge.util.Lang;
import forge.util.Localizer;
public class VariantWins extends ProgressiveAchievement {
private GameType variant;
public VariantWins(GameType variant0, int silver0, int gold0, int mythic0) {
super(variant0.name(), variant0.toString(), null,
"Win " + Lang.nounWithAmount(1, variant0.toString() + " game"), 1,
"Win " + Lang.nounWithAmount(silver0, variant0.toString() + " game"), silver0,
"Win " + Lang.nounWithAmount(gold0, variant0.toString() + " game"), gold0,
"Win " + Lang.nounWithAmount(mythic0, variant0.toString() + " game"), mythic0);
Localizer.getInstance().getMessage("lblWinNVariantGame", 1, variant0.toString()), 1,
Localizer.getInstance().getMessage("lblWinNVariantGame", silver0, variant0.toString()), silver0,
Localizer.getInstance().getMessage("lblWinNVariantGame", gold0, variant0.toString()), gold0,
Localizer.getInstance().getMessage("lblWinNVariantGame", mythic0, variant0.toString()), mythic0);
variant = variant0;
}
@@ -32,6 +32,6 @@ public class VariantWins extends ProgressiveAchievement {
@Override
protected String getNoun() {
return "Win";
return Localizer.getInstance().getMessage("lblWin");
}
}

View File

@@ -48,10 +48,22 @@ public enum FSkinProp {
//zone images
IMG_ZONE_HAND (new int[] {280, 40, 40, 40}, PropType.IMAGE),
IMG_HDZONE_HAND (new int[] {2, 136, 128, 128}, PropType.BUTTONS),
IMG_ZONE_LIBRARY (new int[] {280, 0, 40, 40}, PropType.IMAGE),
IMG_HDZONE_LIBRARY (new int[] {132, 136, 128, 128}, PropType.BUTTONS),
IMG_ZONE_EXILE (new int[] {320, 40, 40, 40}, PropType.IMAGE),
IMG_HDZONE_EXILE (new int[] {262, 136, 128, 128}, PropType.BUTTONS),
IMG_ZONE_FLASHBACK (new int[] {280, 80, 40, 40}, PropType.IMAGE),
IMG_HDZONE_FLASHBACK (new int[] {262, 6, 128, 128}, PropType.BUTTONS),
IMG_ZONE_GRAVEYARD (new int[] {320, 0, 40, 40}, PropType.IMAGE),
IMG_HDZONE_GRAVEYARD (new int[] {132, 6, 128, 128}, PropType.BUTTONS),
IMG_HDZONE_MANAPOOL (new int[] {2, 6, 128, 128}, PropType.BUTTONS),
IMG_ZONE_POISON (new int[] {320, 80, 40, 40}, PropType.IMAGE),
//mana images
@@ -170,6 +182,16 @@ public enum FSkinProp {
ICO_ARCSON (new int[] {320, 800, 80, 80}, PropType.ICON),
ICO_ARCSHOVER (new int[] {400, 800, 80, 80}, PropType.ICON),
//choice-search-misc
ICO_HDCHOICE (new int[] {2, 1792, 128, 128}, PropType.BUTTONS),
ICO_HDSIDEBOARD (new int[] {132, 1792, 128, 128}, PropType.BUTTONS),
ICO_HDPREFERENCE (new int[] {262, 1792, 128, 128}, PropType.BUTTONS),
ICO_HDIMPORT (new int[] {2, 1922, 128, 128}, PropType.BUTTONS),
ICO_HDEXPORT (new int[] {132, 1922, 128, 128}, PropType.BUTTONS),
ICO_HDYIELD (new int[] {262, 1922, 128, 128}, PropType.BUTTONS),
ICO_BLANK (new int[] {2, 2, 2, 2}, PropType.ICON), //safe coords, lower than 2 will cause crash on desktop
IMG_LANDLOGO (new int[] {84, 822, 80, 80}, PropType.MANAICONS),
//quest icons
ICO_QUEST_ZEP (new int[] {0, 480, 80, 80}, PropType.ICON),
ICO_QUEST_GEAR (new int[] {80, 480, 80, 80}, PropType.ICON),
@@ -192,31 +214,82 @@ public enum FSkinProp {
ICO_QUEST_MINUS (new int[] {560, 640, 80, 80}, PropType.ICON),
ICO_QUEST_PLUS (new int[] {480, 640, 80, 80}, PropType.ICON),
ICO_QUEST_PLUSPLUS (new int[] {480, 720, 80, 80}, PropType.ICON),
ICO_QUEST_BIG_ELIXIR (new int[] {0, 880, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_BREW (new int[] {160, 880, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_BM (new int[] {320, 880, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_STAKES (new int[] {480, 880, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_HOUSE (new int[] {0, 1040, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_COIN (new int[] {160, 1040, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_BOOK (new int[] {320, 1040, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_MAP (new int[] {480, 1040, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_ZEP (new int[] {0, 1200, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_CHARM (new int[] {160, 1200, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_BOOTS (new int[] {320, 1200, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_SHIELD (new int[] {480, 1200, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_ARMOR (new int[] {0, 1360, 160, 160}, PropType.ICON),
ICO_QUEST_BIG_AXE (new int[] {160, 1360, 160, 160}, PropType.ICON),
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),
ICO_WARNING (new int[] {560, 832, 32, 32}, PropType.ICON),
ICO_ERROR (new int[] {592, 832, 32, 32}, PropType.ICON),
ICO_DELETE (new int[] {640, 480, 20, 20}, PropType.ICON),
ICO_HDDELETE (new int[] {392, 134, 64, 64}, PropType.BUTTONS),
ICO_DELETE_OVER (new int[] {660, 480, 20, 20}, PropType.ICON),
ICO_EDIT (new int[] {640, 500, 20, 20}, PropType.ICON),
ICO_HDEDIT (new int[] {392, 200, 64, 64}, PropType.BUTTONS),
ICO_EDIT_OVER (new int[] {660, 500, 20, 20}, PropType.ICON),
ICO_OPEN (new int[] {660, 520, 20, 20}, PropType.ICON),
ICO_HDOPEN (new int[] {392, 68, 64, 64}, PropType.BUTTONS),
ICO_MINUS (new int[] {660, 620, 20, 20}, PropType.ICON),
ICO_HDMINUS (new int[] {391, 1506, 64, 64}, PropType.BUTTONS),
ICO_NEW (new int[] {660, 540, 20, 20}, PropType.ICON),
ICO_PLUS (new int[] {660, 600, 20, 20}, PropType.ICON),
ICO_HDPLUS (new int[] {391, 1572, 64, 64}, PropType.BUTTONS),
ICO_PRINT (new int[] {660, 640, 20, 20}, PropType.ICON),
ICO_SAVE (new int[] {660, 560, 20, 20}, PropType.ICON),
ICO_HDSAVE (new int[] {391, 1704, 64, 64}, PropType.BUTTONS),
ICO_SAVEAS (new int[] {660, 580, 20, 20}, PropType.ICON),
ICO_HDSAVEAS (new int[] {391, 1638, 64, 64}, PropType.BUTTONS),
ICO_CLOSE (new int[] {640, 640, 20, 20}, PropType.ICON),
ICO_LIST (new int[] {640, 660, 20, 20}, PropType.ICON),
ICO_CARD_IMAGE (new int[] {660, 660, 20, 20}, PropType.ICON),
ICO_FOLDER (new int[] {640, 680, 20, 20}, PropType.ICON),
ICO_HDFOLDER (new int[] {392, 2, 64, 64}, PropType.BUTTONS),
ICO_SEARCH (new int[] {660, 680, 20, 20}, PropType.ICON),
ICO_HDSEARCH (new int[] {391, 1374, 64, 64}, PropType.BUTTONS),
ICO_UNKNOWN (new int[] {0, 720, 80, 80}, PropType.ICON),
ICO_LOGO (new int[] {480, 0, 200, 200}, PropType.ICON),
ICO_FLIPCARD (new int[] {400, 0, 80, 120}, PropType.ICON),
ICO_HDFLIPCARD (new int[] {2, 1268, 387, 500}, PropType.BUTTONS),
ICO_FAVICON (new int[] {0, 640, 80, 80}, PropType.ICON),
ICO_LOCK (new int[] {620, 800, 48, 48}, PropType.ICON),
@@ -229,14 +302,18 @@ public enum FSkinProp {
IMG_CUR_TAB (new int[] {644, 764, 32, 32}, PropType.IMAGE),
//editor images
IMG_STAR_OUTINE (new int[] {640, 460, 20, 20}, PropType.IMAGE),
IMG_STAR_OUTLINE (new int[] {640, 460, 20, 20}, PropType.IMAGE),
IMG_HDSTAR_OUTLINE (new int[] {391, 1308, 64, 64}, PropType.BUTTONS),
IMG_STAR_FILLED (new int[] {660, 460, 20, 20}, PropType.IMAGE),
IMG_HDSTAR_FILLED (new int[] {391, 1440, 64, 64}, PropType.BUTTONS),
IMG_ARTIFACT (new int[] {412, 658, 80, 80}, PropType.MANAICONS),
IMG_CREATURE (new int[] {2, 740, 80, 80}, PropType.MANAICONS),
IMG_ENCHANTMENT (new int[] {84, 740, 80, 80}, PropType.MANAICONS),
IMG_INSTANT (new int[] {166, 740, 80, 80}, PropType.MANAICONS),
IMG_LAND (new int[] {248, 740, 80, 80}, PropType.MANAICONS),
IMG_MULTI (new int[] {80, 720, 40, 40}, PropType.IMAGE),
IMG_HDMULTI (new int[] {2, 822, 80, 80}, PropType.MANAICONS),
IMG_PLANESWALKER (new int[] {330, 740, 80, 80}, PropType.MANAICONS),
IMG_PACK (new int[] {80, 760, 40, 40}, PropType.IMAGE),
IMG_SORCERY (new int[] {412, 740, 80, 80}, PropType.MANAICONS),
@@ -284,6 +361,28 @@ public enum FSkinProp {
IMG_BTN_DISABLED_LEFT (new int[] {80, 200, 40, 40}, PropType.ICON),
IMG_BTN_DISABLED_CENTER (new int[] {120, 200, 1, 40}, PropType.ICON),
IMG_BTN_DISABLED_RIGHT (new int[] {160, 200, 40, 40}, PropType.ICON),
//hd buttons
IMG_HDBTN_START_UP (new int[] {2, 2, 588, 312}, PropType.BTNSTART),
IMG_HDBTN_START_OVER (new int[] {1183, 2, 588, 312}, PropType.BTNSTART),
IMG_HDBTN_START_DOWN (new int[] {593, 2, 588, 312}, PropType.BTNSTART),
IMG_HDBTN_UP_LEFT (new int[] {2, 266, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_UP_CENTER (new int[] {162, 266, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_UP_RIGHT (new int[] {322, 266, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_OVER_LEFT (new int[] {2, 433, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_OVER_CENTER (new int[] {162, 433, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_OVER_RIGHT (new int[] {322, 433, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_DOWN_LEFT (new int[] {2, 600, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_DOWN_CENTER (new int[] {162, 600, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_DOWN_RIGHT (new int[] {322, 600, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_FOCUS_LEFT (new int[] {2, 767, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_FOCUS_CENTER (new int[] {162, 767, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_FOCUS_RIGHT (new int[] {322, 767, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_TOGGLE_LEFT (new int[] {2, 934, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_TOGGLE_CENTER (new int[] {162, 934, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_TOGGLE_RIGHT (new int[] {322, 934, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_DISABLED_LEFT (new int[] {2, 1101, 160, 165}, PropType.BUTTONS),
IMG_HDBTN_DISABLED_CENTER (new int[] {162, 1101, 1, 165}, PropType.BUTTONS),
IMG_HDBTN_DISABLED_RIGHT (new int[] {322, 1101, 160, 165}, PropType.BUTTONS),
IMG_FAV1 (new int[] {0, 0, 100, 100}, PropType.FAVICON),
IMG_FAV2 (new int[] {100, 0, 100, 100}, PropType.FAVICON),
@@ -293,18 +392,22 @@ public enum FSkinProp {
IMG_FAVNONE (new int[] {500, 0, 100, 100}, PropType.FAVICON),
IMG_QUEST_DRAFT_DECK (new int[] {0, 0, 680, 475}, PropType.IMAGE),
//COMMANDER
IMG_ABILITY_COMMANDER (new int[] {330, 576, 80, 80}, PropType.ABILITY),
//Ability Icons
IMG_ABILITY_DEATHTOUCH (new int[] {2, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_DEFENDER (new int[] {84, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_DOUBLE_STRIKE (new int[] {166, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_FIRST_STRIKE (new int[] {248, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_FEAR (new int[] {84, 412, 80, 80}, PropType.ABILITY),
IMG_ABILITY_FLASH (new int[] {166, 576, 80, 80}, PropType.ABILITY),
IMG_ABILITY_FLYING (new int[] {330, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_HASTE (new int[] {412, 494, 80, 80}, PropType.ABILITY),
IMG_ABILITY_HEXPROOF (new int[] {412, 2, 80, 80}, PropType.ABILITY),
IMG_ABILITY_HORSEMANSHIP (new int[] {2, 576, 80, 80}, PropType.ABILITY),
IMG_ABILITY_INDESTRUCTIBLE (new int[] {2, 84, 80, 80}, PropType.ABILITY),
IMG_ABILITY_INTIMIDATE (new int[] {166, 412, 80, 80}, PropType.ABILITY),
IMG_ABILITY_LANDWALK (new int[] {248, 576, 80, 80}, PropType.ABILITY),
IMG_ABILITY_LIFELINK (new int[] {84, 84, 80, 80}, PropType.ABILITY),
IMG_ABILITY_MENACE (new int[] {166, 84, 80, 80}, PropType.ABILITY),
IMG_ABILITY_REACH (new int[] {248, 330, 80, 80}, PropType.ABILITY),
@@ -378,6 +481,8 @@ public enum FSkinProp {
TROPHY,
ABILITY,
BORDERS,
BUTTONS,
BTNSTART,
MANAICONS,
PLANAR_CONQUEST,
FAVICON

View File

@@ -285,7 +285,10 @@ public class CardDetailUtil {
if (card.getCloneOrigin() == null)
needTranslation = false;
}
String text = card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null);
final String text = !card.isSplitCard() ?
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null) :
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null );
// LEVEL [0-9]+-[0-9]+
// LEVEL [0-9]+\+
@@ -398,6 +401,9 @@ public class CardDetailUtil {
}
area.append("(chosen type: ");
area.append(card.getChosenType());
if (!card.getChosenType2().isEmpty()) {
area.append(", ").append(card.getChosenType2());
}
area.append(")");
}
@@ -421,6 +427,14 @@ public class CardDetailUtil {
area.append(")");
}
// chosen number
if (!card.getChosenNumber().isEmpty()) {
if (area.length() != 0) {
area.append("\n");
}
area.append("(chosen number: ").append(card.getChosenNumber()).append(")");
}
// chosen player
if (card.getChosenPlayer() != null) {
if (area.length() != 0) {
@@ -447,6 +461,9 @@ public class CardDetailUtil {
area.append("Hidden");
} else {
area.append(card.getNamedCard());
if (!card.getNamedCard2().isEmpty()) {
area.append(", ").append(card.getNamedCard2());
}
}
area.append(")");
}
@@ -563,6 +580,14 @@ public class CardDetailUtil {
area.append("Current Storm Count: ").append(gameView.getStormCount());
}
}
//show owner if being controlled by a different player
if (card.getOwner() != card.getController()) {
if (area.length() != 0) {
area.append("\n\n");
}
area.append("Owner: ").append(card.getOwner().toString());
}
return area.toString();
}
}

View File

@@ -414,7 +414,7 @@ public final class CardScriptParser {
"IsTargetingSource", "sharesPermanentTypeWith",
"canProduceSameManaTypeWith", "SecondSpellCastThisTurn",
"ThisTurnCast", "withFlashback", "tapped", "untapped", "faceDown",
"faceUp", "hasLevelUp", "DrawnThisTurn",
"faceUp", "hasLevelUp", "DrawnThisTurn", "notDrawnThisTurn",
"enteredBattlefieldThisTurn", "notEnteredBattlefieldThisTurn",
"firstTurnControlled", "notFirstTurnControlled",
"startedTheTurnUntapped", "attackedOrBlockedSinceYourLastUpkeep",
@@ -446,7 +446,7 @@ public final class CardScriptParser {
"hasActivatedAbility", "hasManaAbility",
"hasNonManaActivatedAbility", "NoAbilities", "HasCounters",
"wasNotCast", "ChosenType", "IsNotChosenType", "IsCommander",
"IsRenowned", "IsNotRenowned");
"IsNotCommander","IsRenowned", "IsNotRenowned");
private static final Set<String> VALID_EXCLUSIVE_STARTSWITH = ImmutableSortedSet
.of("named", "notnamed", "OwnedBy", "ControlledBy",
"ControllerControls", "AttachedTo", "EnchantedBy",

View File

@@ -17,7 +17,6 @@ import forge.game.card.CardView;
import forge.game.event.*;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.zone.PlayerZone;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.interfaces.IGuiGame;
@@ -199,7 +198,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
final List<CardView> options = Lists.newArrayList();
for (final Entry<Player, Card> kv : ev.cards.entries()) {
//use fake card so real cards appear with proper formatting
final CardView fakeCard = new CardView(-1, null, " -- From " + Lang.getPossesive(kv.getKey().getName()) + " deck --");
final CardView fakeCard = new CardView(-1, null, " -- From " + Lang.getInstance().getPossesive(kv.getKey().getName()) + " deck --");
options.add(fakeCard);
options.add(kv.getValue().getView());
}
@@ -239,17 +238,20 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@Override
public Void visit(final GameEventSpellAbilityCast event) {
needStackUpdate = true;
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackAddition(event);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
needStackUpdate = true;
if(GuiBase.getInterface().isLibgdxPort()) {
return processEvent(); //mobile port don't have notify stack addition like the desktop
} else {
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackAddition(event);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
}
return null;
}
@@ -262,16 +264,34 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@Override
public Void visit(final GameEventSpellRemovedFromStack event) {
needStackUpdate = true;
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackRemoval(event);
if(GuiBase.getInterface().isLibgdxPort()) {
return processEvent(); //mobile port don't have notify stack addition like the desktop
} else {
processEvent();
final Runnable notifyStackAddition = new Runnable() {
@Override
public void run() {
matchController.notifyStackRemoval(event);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
}
return null;
}
@Override
public Void visit(final GameEventSubgameEnd event) {
if (event.maingame != null) {
for (Player p : event.maingame.getPlayers()) {
updateZone(p, ZoneType.Battlefield);
updateZone(p, ZoneType.Hand);
updateZone(p, ZoneType.Graveyard);
updateZone(p, ZoneType.Exile);
updateZone(p, ZoneType.Command);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
return processEvent();
}
return null;
}
@@ -288,7 +308,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@Override
public Void visit(final GameEventCardAttachment event) {
final Game game = event.equipment.getGame();
final PlayerZone zEq = (PlayerZone)game.getZoneOf(event.equipment);
final Zone zEq = (Zone)game.getZoneOf(event.equipment);
if (event.oldEntiy instanceof Card) {
updateZone(game.getZoneOf((Card)event.oldEntiy));
}
@@ -356,19 +376,33 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
}
@Override
public Void visit(final GameEventCardChangeZone event) {
if(event.to.getZoneType() == ZoneType.Battlefield)
refreshFieldUpdate = true;
//pfps the change to the zones have already been performed with add and remove calls
// this is only for playing a sound
// updateZone(event.from);
//return updateZone(event.to);
return processEvent();
public Void visit(final GameEventCombatUpdate event) {
if (!GuiBase.isNetworkplay())
return null; //not needed if single player only...
final CardCollection cards = new CardCollection();
cards.addAll(event.attackers);
cards.addAll(event.blockers);
refreshFieldUpdate = true;
processCards(cards, cardsRefreshDetails);
return processCards(cards, cardsUpdate);
}
@Override
public Void visit(final GameEventCardChangeZone event) {
if(GuiBase.getInterface().isLibgdxPort()) {
updateZone(event.from);
return updateZone(event.to);
} else {
return processEvent();
}
}
@Override
public Void visit(final GameEventCardStatsChanged event) {
refreshFieldUpdate = true;
processCards(event.cards, cardsRefreshDetails);
return processCards(event.cards, cardsUpdate);
}
@@ -377,25 +411,35 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
public Void visit(final GameEventPlayerStatsChanged event) {
final CardCollection cards = new CardCollection();
for (final Player p : event.players) {
cards.addAll(p.getAllCards());
if (event.updateCards) {
cards.addAll(p.getAllCards());
}
processPlayer(p, livesUpdate);
}
return processCards(cards, cardsRefreshDetails);
}
public Void visit(final GameEventLandPlayed event) {
processPlayer(event.player, livesUpdate);
matchController.handleLandPlayed(event.land);
return processCard(event.land, cardsRefreshDetails);
}
@Override
public Void visit(final GameEventTokenStateUpdate event) {
refreshFieldUpdate = true;
processCards(event.cards, cardsRefreshDetails);
return processCards(event.cards, cardsUpdate);
}
@Override
public Void visit(final GameEventShuffle event) {
//pfps the change to the library has already been performed by a setCards call
// this is only for playing a sound
// return updateZone(event.player.getZone(ZoneType.Library));
return processEvent();
if (GuiBase.getInterface().isLibgdxPort()) {
return updateZone(event.player.getZone(ZoneType.Library));
} else {
return processEvent();
}
}
@Override

View File

@@ -26,7 +26,7 @@ public final class CardRelationMatrixGenerator {
public static HashMap<String,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>();
public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap();
public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap<>();
/**
To ensure that only cards with at least 14 connections (as 14*4+4=60) are included in the card based deck
generation pools

View File

@@ -13,6 +13,7 @@ import forge.interfaces.IComboBox;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.util.gui.SOptionPane;
import forge.util.Localizer;
public class DeckImportController {
private final boolean replacingDeck;
@@ -62,11 +63,12 @@ public class DeckImportController {
}
public Deck accept() {
final Localizer localizer = Localizer.getInstance();
if (tokens.isEmpty()) { return null; }
if (replacingDeck) {
final String warning = "This will replace the contents of your current deck with these cards.\n\nProceed?";
if (!SOptionPane.showConfirmDialog(warning, "Replace Current Deck", "Replace", "Cancel")) {
final String warning = localizer.getMessage("lblReplaceCurrentDeckConfirm");
if (!SOptionPane.showConfirmDialog(warning, localizer.getMessage("lblReplaceCurrentDeck"), localizer.getMessage("lblReplace"), localizer.getMessage("lblCancel"))) {
return null;
}
}

View File

@@ -1,11 +1,15 @@
package forge.deck;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import forge.card.CardSplitType;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function;
@@ -260,6 +264,29 @@ public class DeckProxy implements InventoryItem {
return highestRarity;
}
public PaperCard getHighestCMCCard() {
PaperCard key = null;
Map<PaperCard, Integer> keyCMC = new HashMap<>(64);
for (final Entry <PaperCard, Integer> pc : getDeck().getAllCardsInASinglePool()) {
if (pc.getKey().getRules().getManaCost() != null) {
if (pc.getKey().getRules().getSplitType() != CardSplitType.Split)
keyCMC.put(pc.getKey(),pc.getKey().getRules().getManaCost().getCMC());
}
}
if (!keyCMC.isEmpty()) {
int max = Collections.max(keyCMC.values());
//get any max cmc
for (Entry<PaperCard, Integer> entry : keyCMC.entrySet()) {
if (entry.getValue()==max) {
return entry.getKey();
}
}
}
return key;
}
public Set<GameFormat> getFormats() {
if (formats == null) {
formats = FModel.getFormats().getAllFormatsOfDeck(getDeck());
@@ -373,6 +400,15 @@ public class DeckProxy implements InventoryItem {
return result;
}
public static Iterable<DeckProxy> getAllCommanderPreconDecks() {
return getAllCommanderPreconDecks(null);
}
public static Iterable<DeckProxy> getAllCommanderPreconDecks(final Predicate<Deck> filter) {
final List<DeckProxy> result = new ArrayList<DeckProxy>();
addDecksRecursivelly("Commander Precon", GameType.Commander, result, "", FModel.getDecks().getCommanderPrecons(), filter);
return result;
}
public static Iterable<DeckProxy> getAllTinyLeadersDecks() {
return getAllTinyLeadersDecks(null);
}
@@ -588,6 +624,45 @@ public class DeckProxy implements InventoryItem {
return decks;
}
public static List<DeckProxy> getNetArchiveStandardDecks(final NetDeckArchiveStandard category) {
final List<DeckProxy> decks = new ArrayList<>();
if (category != null) {
addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category, null);
}
return decks;
}
public static List<DeckProxy> getNetArchiveModernDecks(final NetDeckArchiveModern category) {
final List<DeckProxy> decks = new ArrayList<>();
if (category != null) {
addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category, null);
}
return decks;
}
public static List<DeckProxy> getNetArchivePioneerDecks(final NetDeckArchivePioneer category) {
final List<DeckProxy> decks = new ArrayList<>();
if (category != null) {
addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category, null);
}
return decks;
}
public static List<DeckProxy> getNetArchiveLegacyDecks(final NetDeckArchiveLegacy category) {
final List<DeckProxy> decks = new ArrayList<>();
if (category != null) {
addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category, null);
}
return decks;
}
public static List<DeckProxy> getNetArchiveVintageDecks(final NetDeckArchiveVintage category) {
final List<DeckProxy> decks = new ArrayList<>();
if (category != null) {
addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category, null);
}
return decks;
}
public static CardEdition getDefaultLandSet(Deck deck) {
List<CardEdition> availableEditions = new ArrayList<>();

View File

@@ -17,6 +17,7 @@ public enum DeckType {
DRAFT_DECK("lblDraftDecks"),
SEALED_DECK("lblSealedDecks"),
PRECONSTRUCTED_DECK("lblPreconstructedDecks"),
PRECON_COMMANDER_DECK("lblPreconCommanderDecks"),
QUEST_OPPONENT_DECK("lblQuestOpponentDecks"),
COLOR_DECK("lblRandomColorDecks"),
STANDARD_CARDGEN_DECK("lblRandomStandardArchetypeDecks"),
@@ -29,7 +30,12 @@ public enum DeckType {
THEME_DECK("lblRandomThemeDecks"),
RANDOM_DECK("lblRandomDecks"),
NET_DECK("lblNetDecks"),
NET_COMMANDER_DECK("lblNetCommanderDecks");
NET_COMMANDER_DECK("lblNetCommanderDecks"),
NET_ARCHIVE_STANDARD_DECK("lblNetArchiveStandardDecks"),
NET_ARCHIVE_PIONEER_DECK("lblNetArchivePioneerDecks"),
NET_ARCHIVE_MODERN_DECK("lblNetArchiveModernDecks"),
NET_ARCHIVE_LEGACY_DECK("lblNetArchiveLegacyDecks"),
NET_ARCHIVE_VINTAGE_DECK("lblNetArchiveVintageDecks");
public static DeckType[] ConstructedOptions;
public static DeckType[] CommanderOptions;
@@ -50,7 +56,12 @@ public enum DeckType {
DeckType.MODERN_COLOR_DECK,
DeckType.THEME_DECK,
DeckType.RANDOM_DECK,
DeckType.NET_DECK
DeckType.NET_DECK,
DeckType.NET_ARCHIVE_STANDARD_DECK,
DeckType.NET_ARCHIVE_PIONEER_DECK,
DeckType.NET_ARCHIVE_MODERN_DECK,
DeckType.NET_ARCHIVE_LEGACY_DECK,
DeckType.NET_ARCHIVE_VINTAGE_DECK
};
} else {
ConstructedOptions = new DeckType[]{
@@ -62,7 +73,13 @@ public enum DeckType {
DeckType.MODERN_COLOR_DECK,
DeckType.THEME_DECK,
DeckType.RANDOM_DECK,
DeckType.NET_DECK
DeckType.NET_DECK,
DeckType.NET_ARCHIVE_STANDARD_DECK,
DeckType.NET_ARCHIVE_PIONEER_DECK,
DeckType.NET_ARCHIVE_MODERN_DECK,
DeckType.NET_ARCHIVE_LEGACY_DECK,
DeckType.NET_ARCHIVE_VINTAGE_DECK
};
}
}
@@ -70,6 +87,7 @@ public enum DeckType {
if (FModel.isdeckGenMatrixLoaded()) {
CommanderOptions = new DeckType[]{
DeckType.COMMANDER_DECK,
DeckType.PRECON_COMMANDER_DECK,
DeckType.RANDOM_COMMANDER_DECK,
DeckType.RANDOM_CARDGEN_COMMANDER_DECK,
DeckType.RANDOM_DECK,
@@ -78,6 +96,7 @@ public enum DeckType {
}else{
CommanderOptions = new DeckType[]{
DeckType.COMMANDER_DECK,
DeckType.PRECON_COMMANDER_DECK,
DeckType.RANDOM_COMMANDER_DECK,
DeckType.RANDOM_DECK,
DeckType.NET_COMMANDER_DECK

View File

@@ -156,6 +156,9 @@ public class DeckgenUtil {
//}
}
//remove any cards not valid in format
selectedCards = Lists.newArrayList(Iterables.filter(selectedCards, format.getFilterPrinted()));
List<PaperCard> toRemove = new ArrayList<>();
//randomly remove cards
@@ -252,6 +255,9 @@ public class DeckgenUtil {
//}
}
//remove any cards not valid in format
selectedCards = Lists.newArrayList(Iterables.filter(selectedCards, format.getFilterPrinted()));
List<PaperCard> toRemove = new ArrayList<>();
//randomly remove cards

View File

@@ -0,0 +1,122 @@
package forge.deck;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import forge.GuiBase;
import forge.deck.io.DeckSerializer;
import forge.deck.io.DeckStorage;
import forge.download.GuiDownloadZipService;
import forge.game.GameType;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SGuiChoose;
import forge.util.storage.StorageBase;
public class NetDeckArchiveLegacy extends StorageBase<Deck> {
public static final String PREFIX = "NET_ARCHIVE_LEGACY_DECK";
private static Map<String, NetDeckArchiveLegacy> constructed, commander, brawl;
private static Map<String, NetDeckArchiveLegacy> loadCategories(String filename) {
Map<String, NetDeckArchiveLegacy> categories = new TreeMap<>();
if (FileUtil.doesFileExist(filename)) {
List<String> lines = FileUtil.readFile(filename);
for (String line : lines) {
int idx = line.indexOf('|');
if (idx != -1) {
String name = line.substring(0, idx).trim();
String url = line.substring(idx + 1).trim();
categories.put(name, new NetDeckArchiveLegacy(name, url));
}
}
}
return categories;
}
public static NetDeckArchiveLegacy selectAndLoad(GameType gameType) {
return selectAndLoad(gameType, null);
}
public static NetDeckArchiveLegacy selectAndLoad(GameType gameType, String name) {
Map<String, NetDeckArchiveLegacy> categories;
switch (gameType) {
case Constructed:
case Gauntlet:
if (constructed == null) {
constructed = loadCategories(ForgeConstants.NET_ARCHIVE_LEGACY_DECKS_LIST_FILE);
}
categories = constructed;
break;
default:
return null;
}
if (name != null) {
NetDeckArchiveLegacy category = categories.get(name);
if (category != null && category.map.isEmpty()) {
//if name passed in, try to load decks from current cached files
File downloadDir = new File(category.getFullPath());
if (downloadDir.exists()) {
for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
Deck deck = DeckSerializer.fromFile(file);
if (deck != null) {
category.map.put(deck.getName(), deck);
}
}
}
}
return category;
}
final NetDeckArchiveLegacy c = SGuiChoose.oneOrNone("Select a Net Deck Archive Legacy category", categories.values());
if (c == null) { return null; }
if (c.map.isEmpty()) { //only download decks once per session
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
String downloadLoc = c.getFullPath();
GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) {
@Override
protected void copyInputStream(InputStream in, String outPath) throws IOException {
super.copyInputStream(in, outPath);
Deck deck = DeckSerializer.fromFile(new File(outPath));
if (deck != null) {
c.map.put(deck.getName(), deck);
}
}
}, this);
}
};
if (!callback.invokeAndWait()) { return null; } //wait for download to finish
}
return c;
}
private final String url;
private NetDeckArchiveLegacy(String name0, String url0) {
super(name0, ForgeConstants.DECK_NET_ARCHIVE_DIR + name0, new HashMap<>());
url = url0;
}
public String getUrl() {
return url;
}
public String getDeckType() {
return "Net Archive Legacy Decks - " + name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,121 @@
package forge.deck;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import forge.GuiBase;
import forge.deck.io.DeckSerializer;
import forge.deck.io.DeckStorage;
import forge.download.GuiDownloadZipService;
import forge.game.GameType;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SGuiChoose;
import forge.util.storage.StorageBase;
public class NetDeckArchiveModern extends StorageBase<Deck> {
public static final String PREFIX = "NET_ARCHIVE_MODERN_DECK";
private static Map<String, NetDeckArchiveModern> constructed, commander, brawl;
private static Map<String, NetDeckArchiveModern> loadCategories(String filename) {
Map<String, NetDeckArchiveModern> categories = new TreeMap<>();
if (FileUtil.doesFileExist(filename)) {
List<String> lines = FileUtil.readFile(filename);
for (String line : lines) {
int idx = line.indexOf('|');
if (idx != -1) {
String name = line.substring(0, idx).trim();
String url = line.substring(idx + 1).trim();
categories.put(name, new NetDeckArchiveModern(name, url));
}
}
}
return categories;
}
public static NetDeckArchiveModern selectAndLoad(GameType gameType) {
return selectAndLoad(gameType, null);
}
public static NetDeckArchiveModern selectAndLoad(GameType gameType, String name) {
Map<String, NetDeckArchiveModern> categories;
switch (gameType) {
case Constructed:
case Gauntlet:
if (constructed == null) {
constructed = loadCategories(ForgeConstants.NET_ARCHIVE_MODERN_DECKS_LIST_FILE);
}
categories = constructed;
break;
default:
return null;
}
if (name != null) {
NetDeckArchiveModern category = categories.get(name);
if (category != null && category.map.isEmpty()) {
//if name passed in, try to load decks from current cached files
File downloadDir = new File(category.getFullPath());
if (downloadDir.exists()) {
for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
Deck deck = DeckSerializer.fromFile(file);
if (deck != null) {
category.map.put(deck.getName(), deck);
}
}
}
}
return category;
}
final NetDeckArchiveModern c = SGuiChoose.oneOrNone("Select a Net Deck Archive Modern category", categories.values());
if (c == null) { return null; }
if (c.map.isEmpty()) { //only download decks once per session
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
String downloadLoc = c.getFullPath();
GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) {
@Override
protected void copyInputStream(InputStream in, String outPath) throws IOException {
super.copyInputStream(in, outPath);
Deck deck = DeckSerializer.fromFile(new File(outPath));
if (deck != null) {
c.map.put(deck.getName(), deck);
}
}
}, this);
}
};
if (!callback.invokeAndWait()) { return null; } //wait for download to finish
}
return c;
}
private final String url;
private NetDeckArchiveModern(String name0, String url0) {
super(name0, ForgeConstants.DECK_NET_ARCHIVE_DIR + name0, new HashMap<>());
url = url0;
}
public String getUrl() {
return url;
}
public String getDeckType() {
return "Net Archive Modern Decks - " + name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,121 @@
package forge.deck;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import forge.GuiBase;
import forge.deck.io.DeckSerializer;
import forge.deck.io.DeckStorage;
import forge.download.GuiDownloadZipService;
import forge.game.GameType;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SGuiChoose;
import forge.util.storage.StorageBase;
public class NetDeckArchivePioneer extends StorageBase<Deck> {
public static final String PREFIX = "NET_ARCHIVE_PIONEER_DECK";
private static Map<String, NetDeckArchivePioneer> constructed, commander, brawl;
private static Map<String, NetDeckArchivePioneer> loadCategories(String filename) {
Map<String, NetDeckArchivePioneer> categories = new TreeMap<>();
if (FileUtil.doesFileExist(filename)) {
List<String> lines = FileUtil.readFile(filename);
for (String line : lines) {
int idx = line.indexOf('|');
if (idx != -1) {
String name = line.substring(0, idx).trim();
String url = line.substring(idx + 1).trim();
categories.put(name, new NetDeckArchivePioneer(name, url));
}
}
}
return categories;
}
public static NetDeckArchivePioneer selectAndLoad(GameType gameType) {
return selectAndLoad(gameType, null);
}
public static NetDeckArchivePioneer selectAndLoad(GameType gameType, String name) {
Map<String, NetDeckArchivePioneer> categories;
switch (gameType) {
case Constructed:
case Gauntlet:
if (constructed == null) {
constructed = loadCategories(ForgeConstants.NET_ARCHIVE_PIONEER_DECKS_LIST_FILE);
}
categories = constructed;
break;
default:
return null;
}
if (name != null) {
NetDeckArchivePioneer category = categories.get(name);
if (category != null && category.map.isEmpty()) {
//if name passed in, try to load decks from current cached files
File downloadDir = new File(category.getFullPath());
if (downloadDir.exists()) {
for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
Deck deck = DeckSerializer.fromFile(file);
if (deck != null) {
category.map.put(deck.getName(), deck);
}
}
}
}
return category;
}
final NetDeckArchivePioneer c = SGuiChoose.oneOrNone("Select a Net Deck Archive Pioneer category", categories.values());
if (c == null) { return null; }
if (c.map.isEmpty()) { //only download decks once per session
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
String downloadLoc = c.getFullPath();
GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) {
@Override
protected void copyInputStream(InputStream in, String outPath) throws IOException {
super.copyInputStream(in, outPath);
Deck deck = DeckSerializer.fromFile(new File(outPath));
if (deck != null) {
c.map.put(deck.getName(), deck);
}
}
}, this);
}
};
if (!callback.invokeAndWait()) { return null; } //wait for download to finish
}
return c;
}
private final String url;
private NetDeckArchivePioneer(String name0, String url0) {
super(name0, ForgeConstants.DECK_NET_ARCHIVE_DIR + name0, new HashMap<>());
url = url0;
}
public String getUrl() {
return url;
}
public String getDeckType() {
return "Net Archive Pioneer Decks - " + name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,122 @@
package forge.deck;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import forge.GuiBase;
import forge.deck.io.DeckSerializer;
import forge.deck.io.DeckStorage;
import forge.download.GuiDownloadZipService;
import forge.game.GameType;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SGuiChoose;
import forge.util.storage.StorageBase;
public class NetDeckArchiveStandard extends StorageBase<Deck> {
public static final String PREFIX = "NET_ARCHIVE_STANDARD_DECK";
private static Map<String, NetDeckArchiveStandard> constructed, commander, brawl;
private static Map<String, NetDeckArchiveStandard> loadCategories(String filename) {
Map<String, NetDeckArchiveStandard> categories = new TreeMap<>();
if (FileUtil.doesFileExist(filename)) {
List<String> lines = FileUtil.readFile(filename);
for (String line : lines) {
int idx = line.indexOf('|');
if (idx != -1) {
String name = line.substring(0, idx).trim();
String url = line.substring(idx + 1).trim();
categories.put(name, new NetDeckArchiveStandard(name, url));
}
}
}
return categories;
}
public static NetDeckArchiveStandard selectAndLoad(GameType gameType) {
return selectAndLoad(gameType, null);
}
public static NetDeckArchiveStandard selectAndLoad(GameType gameType, String name) {
Map<String, NetDeckArchiveStandard> categories;
switch (gameType) {
case Constructed:
case Gauntlet:
if (constructed == null) {
constructed = loadCategories(ForgeConstants.NET_ARCHIVE_STANDARD_DECKS_LIST_FILE);
}
categories = constructed;
break;
default:
return null;
}
if (name != null) {
NetDeckArchiveStandard category = categories.get(name);
if (category != null && category.map.isEmpty()) {
//if name passed in, try to load decks from current cached files
File downloadDir = new File(category.getFullPath());
if (downloadDir.exists()) {
for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
Deck deck = DeckSerializer.fromFile(file);
if (deck != null) {
category.map.put(deck.getName(), deck);
}
}
}
}
return category;
}
final NetDeckArchiveStandard c = SGuiChoose.oneOrNone("Select a Net Deck Archive Standard category", categories.values());
if (c == null) { return null; }
if (c.map.isEmpty()) { //only download decks once per session
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
String downloadLoc = c.getFullPath();
GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) {
@Override
protected void copyInputStream(InputStream in, String outPath) throws IOException {
super.copyInputStream(in, outPath);
Deck deck = DeckSerializer.fromFile(new File(outPath));
if (deck != null) {
c.map.put(deck.getName(), deck);
}
}
}, this);
}
};
if (!callback.invokeAndWait()) { return null; } //wait for download to finish
}
return c;
}
private final String url;
private NetDeckArchiveStandard(String name0, String url0) {
super(name0, ForgeConstants.DECK_NET_ARCHIVE_DIR + name0, new HashMap<>());
url = url0;
}
public String getUrl() {
return url;
}
public String getDeckType() {
return "Net Archive Standard Decks - " + name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,122 @@
package forge.deck;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import forge.GuiBase;
import forge.deck.io.DeckSerializer;
import forge.deck.io.DeckStorage;
import forge.download.GuiDownloadZipService;
import forge.game.GameType;
import forge.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.WaitCallback;
import forge.util.gui.SGuiChoose;
import forge.util.storage.StorageBase;
public class NetDeckArchiveVintage extends StorageBase<Deck> {
public static final String PREFIX = "NET_ARCHIVE_VINTAGE_DECK";
private static Map<String, NetDeckArchiveVintage> constructed, commander, brawl;
private static Map<String, NetDeckArchiveVintage> loadCategories(String filename) {
Map<String, NetDeckArchiveVintage> categories = new TreeMap<>();
if (FileUtil.doesFileExist(filename)) {
List<String> lines = FileUtil.readFile(filename);
for (String line : lines) {
int idx = line.indexOf('|');
if (idx != -1) {
String name = line.substring(0, idx).trim();
String url = line.substring(idx + 1).trim();
categories.put(name, new NetDeckArchiveVintage(name, url));
}
}
}
return categories;
}
public static NetDeckArchiveVintage selectAndLoad(GameType gameType) {
return selectAndLoad(gameType, null);
}
public static NetDeckArchiveVintage selectAndLoad(GameType gameType, String name) {
Map<String, NetDeckArchiveVintage> categories;
switch (gameType) {
case Constructed:
case Gauntlet:
if (constructed == null) {
constructed = loadCategories(ForgeConstants.NET_ARCHIVE_VINTAGE_DECKS_LIST_FILE);
}
categories = constructed;
break;
default:
return null;
}
if (name != null) {
NetDeckArchiveVintage category = categories.get(name);
if (category != null && category.map.isEmpty()) {
//if name passed in, try to load decks from current cached files
File downloadDir = new File(category.getFullPath());
if (downloadDir.exists()) {
for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) {
Deck deck = DeckSerializer.fromFile(file);
if (deck != null) {
category.map.put(deck.getName(), deck);
}
}
}
}
return category;
}
final NetDeckArchiveVintage c = SGuiChoose.oneOrNone("Select a Net Deck Archive Vintage category", categories.values());
if (c == null) { return null; }
if (c.map.isEmpty()) { //only download decks once per session
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
String downloadLoc = c.getFullPath();
GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) {
@Override
protected void copyInputStream(InputStream in, String outPath) throws IOException {
super.copyInputStream(in, outPath);
Deck deck = DeckSerializer.fromFile(new File(outPath));
if (deck != null) {
c.map.put(deck.getName(), deck);
}
}
}, this);
}
};
if (!callback.invokeAndWait()) { return null; } //wait for download to finish
}
return c;
}
private final String url;
private NetDeckArchiveVintage(String name0, String url0) {
super(name0, ForgeConstants.DECK_NET_ARCHIVE_DIR + name0, new HashMap<>());
url = url0;
}
public String getUrl() {
return url;
}
public String getDeckType() {
return "Net Archive Vintage Decks - " + name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -21,7 +21,7 @@ import forge.util.storage.StorageBase;
public class NetDeckCategory extends StorageBase<Deck> {
public static final String PREFIX = "NET_DECK_";
private static Map<String, NetDeckCategory> constructed, commander;
private static Map<String, NetDeckCategory> constructed, commander, brawl;
private static Map<String, NetDeckCategory> loadCategories(String filename) {
Map<String, NetDeckCategory> categories = new TreeMap<>();
@@ -58,6 +58,12 @@ public class NetDeckCategory extends StorageBase<Deck> {
}
categories = commander;
break;
case Brawl:
if (brawl == null) {
brawl = loadCategories(ForgeConstants.NET_DECKS_BRAWL_LIST_FILE);
}
categories = brawl;
break;
default:
return null;
}

View File

@@ -0,0 +1,246 @@
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.Localizer;
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/";
private static final Localizer localizer = Localizer.getInstance();
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 = localizer.getMessage("lblYouHaventSetUpdateChannel");
List<String> options = ImmutableList.of("Cancel", "release", "snapshot");
int option = SOptionPane.showOptionDialog(message, localizer.getMessage("lblManualCheck"), null, options, 0);
if (option < 1) {
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 = localizer.getMessage("lblNewVersionForgeAvailableUpdateConfirm", version, buildVersion);
final List<String> options = ImmutableList.of(localizer.getMessage("lblUpdateNow"), localizer.getMessage("lblUpdateLater"));
if (SOptionPane.showOptionDialog(message, localizer.getMessage("lblNewVersionAvailable"), 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", localizer.getMessage("lblNewVersionDownloading"), 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(localizer.getMessage("lblForgeHasBeenUpdateRestartForgeToUseNewVersion"), localizer.getMessage("lblExitNowConfirm"))) {
System.exit(0);
}
}
}

View File

@@ -22,7 +22,7 @@ import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.util.ImageUtil;
import forge.util.TextUtil;
import forge.ImageKeys;
import java.io.File;
import java.util.*;
@@ -63,21 +63,8 @@ public class GuiDownloadPicturesLQ extends GuiDownloadService {
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";
final String setcode2 = StaticData.instance().getEditions().getCode2ByCode(c.getEdition());
String modifier = !imageKey.contains(".full") ? ".fullborder" : "";
final String fullborderPath = ForgeConstants.CACHE_CARD_PICS_DIR + setcode2 + ForgeConstants.PATH_SEPARATOR + TextUtil.fastReplace(imageKey,".full",".fullborder") + modifier + ".jpg";
final File existingFB = new File (fullborderPath);
final File existingFB2 = new File (TextUtil.fastReplace(fullborderPath, "1.fullborder", ".fullborder"));
if (existingFB.exists()) {
return; //don't download equivalent full image with an existing fullborder image
}
if (existingFB2.exists()) {
return; //check 2
}
if (existingImages.contains(imageKey + ".jpg")) {
if (ImageKeys.getImageFile(imageKey) != null) {
return;
}

View File

@@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import forge.util.TextUtil;
import org.apache.commons.lang3.tuple.Pair;
import com.esotericsoftware.minlog.Log;
@@ -257,6 +258,7 @@ public abstract class GuiDownloadService implements Runnable {
for (Entry<String, String> kv : files.entrySet()) {
boolean isJPG = true;
boolean isLogged = false;
boolean fullborder = false;
if (cancel) {//stop prevent sleep
GuiBase.getInterface().preventSystemSleep(false);
break; }
@@ -266,9 +268,9 @@ public abstract class GuiDownloadService implements Runnable {
String url = kv.getValue();
String decodedKey = decodeURL(kv.getKey());
final File fileDest = new File(decodedKey);
File fileDest = new File(decodedKey);
final String filePath = fileDest.getPath();
final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : "\\db\\";
final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : filePath.contains("skins") ? "\\"+FileUtil.getParent(filePath)+"\\" : "\\db\\";
System.out.println(count + "/" + totalCount + " - .." + filePath.substring(filePath.lastIndexOf(subLastIndex)+1));
@@ -287,10 +289,23 @@ public abstract class GuiDownloadService implements Runnable {
conn.setInstanceFollowRedirects(false);
}
conn.connect();
//if .full file is not found try fullborder
if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) && (url.contains(".full.jpg")))
{
fullborder = true;
conn.disconnect();
url = TextUtil.fastReplace(url, ".full.jpg", ".fullborder.jpg");
imageUrl = new URL(url);
conn = (HttpURLConnection) imageUrl.openConnection(p);
conn.setInstanceFollowRedirects(false);
conn.connect();
}
// if file is not found and this is a JPG, give PNG a shot...
if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) && (url.endsWith(".jpg")))
{
fullborder = false;
isJPG = false;
conn.disconnect();
if(url.contains("/images/")){
@@ -298,12 +313,16 @@ public abstract class GuiDownloadService implements Runnable {
System.out.println("File not found: .." + url.substring(url.lastIndexOf("/images/")+1));
}
url = url.substring(0,url.length() - 4) + ".png";
imageUrl = new URL(url);
imageUrl = new URL(TextUtil.fastReplace(url, ".fullborder.", ".full."));
conn = (HttpURLConnection) imageUrl.openConnection(p);
conn.setInstanceFollowRedirects(false);
conn.connect();
}
if (fullborder) {
fileDest = new File(TextUtil.fastReplace(decodedKey, ".full.jpg", ".fullborder.jpg"));
}
switch (conn.getResponseCode()) {
case HttpURLConnection.HTTP_OK:
fos = new FileOutputStream(fileDest);
@@ -390,8 +409,11 @@ public abstract class GuiDownloadService implements Runnable {
protected abstract Map<String, String> getNeededFiles();
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir) {
addMissingItems(list, nameUrlFile, dir, false);
}
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir, boolean includeParent) {
for (Pair<String, String> nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) {
File f = new File(dir, decodeURL(nameUrlPair.getLeft()));
File f = new File(includeParent? dir+FileUtil.getParent(nameUrlPair.getRight()) : dir , decodeURL(nameUrlPair.getLeft()));
//System.out.println(f.getAbsolutePath());
if (!f.exists()) {
list.put(f.getAbsolutePath(), nameUrlPair.getRight());

View File

@@ -0,0 +1,23 @@
package forge.download;
import forge.properties.ForgeConstants;
import java.util.Map;
import java.util.TreeMap;
public class GuiDownloadSkins extends GuiDownloadService {
@Override
public String getTitle() {
return "Download Skins";
}
@Override
protected final Map<String, String> getNeededFiles() {
// read all card names and urls
final Map<String, String> urls = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
addMissingItems(urls, ForgeConstants.SKINS_LIST_FILE, ForgeConstants.CACHE_SKINS_DIR, true);
return urls;
}
}

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

@@ -29,6 +29,7 @@ import forge.GuiBase;
import forge.model.FModel;
import forge.properties.ForgePreferences;
import forge.util.gui.SOptionPane;
import forge.util.Localizer;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
@@ -42,11 +43,11 @@ import io.sentry.event.BreadcrumbBuilder;
public class BugReporter {
private static final int STACK_OVERFLOW_MAX_MESSAGE_LEN = 16 * 1024;
public static final String REPORT = "Report";
public static final String SAVE = "Save";
public static final String DISCARD = "Discard";
public static final String EXIT = "Exit";
public static final String SENTRY = "Submit bug reports automatically";
public static final String REPORT = Localizer.getInstance().getMessage("lblReport");
public static final String SAVE = Localizer.getInstance().getMessage("lblSave");
public static final String DISCARD = Localizer.getInstance().getMessage("lblDiscardError");
public static final String EXIT = Localizer.getInstance().getMessage("lblExit");
public static final String SENTRY = Localizer.getInstance().getMessage("lblAutoSubmitBugReports");
private static Throwable exception;
private static String message;
@@ -93,7 +94,7 @@ public class BugReporter {
if (isSentryEnabled()) {
sendSentry();
} else {
GuiBase.getInterface().showBugReportDialog("Report a crash", sb.toString(), true);
GuiBase.getInterface().showBugReportDialog(Localizer.getInstance().getMessage("lblReportCrash"), sb.toString(), true);
}
}
@@ -128,7 +129,7 @@ public class BugReporter {
if (isSentryEnabled()) {
sendSentry();
} else {
GuiBase.getInterface().showBugReportDialog("Report a bug", message, false);
GuiBase.getInterface().showBugReportDialog(Localizer.getInstance().getMessage("btnReportBug"), message, false);
}
}
@@ -148,8 +149,8 @@ public class BugReporter {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(f))){
bw.write(text);
} catch (final IOException ex) {
SOptionPane.showMessageDialog("There was an error during saving. Sorry!\n" + ex,
"Error saving file", SOptionPane.ERROR_ICON);
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblThereErrorWasDuringSaving", ex),
Localizer.getInstance().getMessage("lblErrorSavingFile"), SOptionPane.ERROR_ICON);
}
}

View File

@@ -42,6 +42,7 @@ public interface IGuiBase {
String showFileDialog(String title, String defaultDir);
File getSaveFile(File defaultFile);
void download(GuiDownloadService service, Callback<Boolean> callback);
void refreshSkin();
void showCardList(String title, String message, List<PaperCard> list);
boolean showBoxedProduct(String title, String message, List<PaperCard> list);
PaperCard chooseCard(String title, String message, List<PaperCard> list);

View File

@@ -11,6 +11,7 @@ import forge.assets.FSkinProp;
import forge.deck.CardPool;
import forge.game.GameEntityView;
import forge.game.GameView;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
@@ -22,6 +23,7 @@ import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
@@ -51,6 +53,7 @@ public interface IGuiGame {
void updateStack();
void notifyStackAddition(final GameEventSpellAbilityCast event);
void notifyStackRemoval(final GameEventSpellRemovedFromStack event);
void handleLandPlayed(Card land);
Iterable<PlayerZoneUpdate> tempShowZones(PlayerView controller, Iterable<PlayerZoneUpdate> zonesToUpdate);
void hideZones(PlayerView controller, Iterable<PlayerZoneUpdate> zonesToUpdate);
void updateZones(Iterable<PlayerZoneUpdate> zonesToUpdate);
@@ -62,7 +65,7 @@ public interface IGuiGame {
void updateLives(Iterable<PlayerView> livesUpdate);
void setPanelSelection(CardView hostCard);
SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent);
Map<CardView, Integer> assignDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder);
Map<CardView, Integer> assignCombatDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder);
void message(String message);
void message(String message, String title);
@@ -107,7 +110,6 @@ public interface IGuiGame {
* @return null if choices is missing, empty, or if the users' choices are
* empty; otherwise, returns the first item in the List returned by
* getChoices.
* @see #getChoices(String, int, int, Object...)
*/
<T> T oneOrNone(String message, List<T> choices);
@@ -158,8 +160,8 @@ public interface IGuiGame {
void setCard(CardView card);
void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi);
boolean openZones(Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(Map<PlayerView, Object> playersToRestoreZonesFor);
PlayerZoneUpdates openZones(PlayerView controller, Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates);
void setHighlighted(PlayerView pv, boolean b);
void setUsedToPay(CardView card, boolean value);
void setSelectables(final Iterable<CardView> cards);

View File

@@ -26,30 +26,48 @@ import forge.game.keyword.Keyword;
import forge.interfaces.IButton;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.item.SealedProduct;
import forge.model.FModel;
import forge.planarconquest.ConquestCommander;
import forge.planarconquest.ConquestPlane;
import forge.planarconquest.ConquestRegion;
import forge.quest.QuestSpellShop;
import forge.quest.QuestWorld;
import forge.util.gui.SGuiChoose;
import forge.util.gui.SOptionPane;
import forge.util.Localizer;
import forge.util.CardTranslation;
public class AdvancedSearch {
public enum FilterOption {
NONE("(none)", null, null, null),
CARD_NAME("Name", PaperCard.class, FilterOperator.STRING_OPS, new StringEvaluator<PaperCard>() {
NONE("lblNone", null, null, null),
CARD_NAME("lblName", PaperCard.class, FilterOperator.STRINGS_OPS, new StringEvaluator<PaperCard>() {
@Override
protected String getItemValue(PaperCard input) {
return input.getName();
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<String> getItemValues(PaperCard input) {
Set<String> names = new HashSet<>();
names.add(input.getName());
names.add(CardTranslation.getTranslatedName(input.getName()));
return names;
}
}),
CARD_RULES_TEXT("Rules Text", PaperCard.class, FilterOperator.STRING_OPS, new StringEvaluator<PaperCard>() {
CARD_RULES_TEXT("lblRulesText", PaperCard.class, FilterOperator.STRINGS_OPS, new StringEvaluator<PaperCard>() {
@Override
protected String getItemValue(PaperCard input) {
return input.getRules().getOracleText();
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<String> getItemValues(PaperCard input) {
Set<String> names = new HashSet<>();
names.add(input.getRules().getOracleText());
names.add(CardTranslation.getTranslatedOracle(input.getName()));
return names;
}
}),
CARD_KEYWORDS("Keywords", PaperCard.class, FilterOperator.COLLECTION_OPS, new CustomListEvaluator<PaperCard, Keyword>(Keyword.getAllKeywords()) {
CARD_KEYWORDS("lblKeywords", PaperCard.class, FilterOperator.COLLECTION_OPS, new CustomListEvaluator<PaperCard, Keyword>(Keyword.getAllKeywords()) {
@Override
protected Keyword getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -59,13 +77,13 @@ public class AdvancedSearch {
return Keyword.getKeywordSet(input);
}
}),
CARD_SET("Set", PaperCard.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<PaperCard, CardEdition>(FModel.getMagicDb().getSortedEditions(), CardEdition.FN_GET_CODE) {
CARD_SET("lblSet", PaperCard.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<PaperCard, CardEdition>(FModel.getMagicDb().getSortedEditions(), CardEdition.FN_GET_CODE) {
@Override
protected CardEdition getItemValue(PaperCard input) {
return FModel.getMagicDb().getEditions().get(input.getEdition());
}
}),
CARD_FORMAT("Format", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, GameFormat>((List<GameFormat>)FModel.getFormats().getFilterList()) {
CARD_FORMAT("lblFormat", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, GameFormat>((List<GameFormat>)FModel.getFormats().getFilterList()) {
@Override
protected GameFormat getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -75,7 +93,7 @@ public class AdvancedSearch {
return FModel.getFormats().getAllFormatsOfCard(input);
}
}),
CARD_PLANE("Plane", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
CARD_PLANE("lblPlane", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
@Override
protected ConquestPlane getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -85,7 +103,7 @@ public class AdvancedSearch {
return ConquestPlane.getAllPlanesOfCard(input);
}
}),
CARD_REGION("Region", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, ConquestRegion>(ConquestRegion.getAllRegions()) {
CARD_REGION("lblRegion", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, ConquestRegion>(ConquestRegion.getAllRegions()) {
@Override
protected ConquestRegion getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -95,7 +113,7 @@ public class AdvancedSearch {
return ConquestRegion.getAllRegionsOfCard(input);
}
}),
CARD_QUEST_WORLD("Quest World", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, QuestWorld>(ImmutableList.copyOf(FModel.getWorlds())) {
CARD_QUEST_WORLD("lblQuestWorld", PaperCard.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<PaperCard, QuestWorld>(ImmutableList.copyOf(FModel.getWorlds())) {
@Override
protected QuestWorld getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -105,7 +123,7 @@ public class AdvancedSearch {
return QuestWorld.getAllQuestWorldsOfCard(input);
}
}),
CARD_COLOR("Color", PaperCard.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<PaperCard>() {
CARD_COLOR("lblColor", PaperCard.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<PaperCard>() {
@Override
protected MagicColor.Color getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -115,7 +133,7 @@ public class AdvancedSearch {
return input.getRules().getColor().toEnumSet();
}
}),
CARD_COLOR_IDENTITY("Color Identity", PaperCard.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<PaperCard>() {
CARD_COLOR_IDENTITY("lblColorIdentity", PaperCard.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<PaperCard>() {
@Override
protected MagicColor.Color getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -125,13 +143,13 @@ public class AdvancedSearch {
return input.getRules().getColorIdentity().toEnumSet();
}
}),
CARD_COLOR_COUNT("Color Count", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 5) {
CARD_COLOR_COUNT("lblColorCount", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 5) {
@Override
protected Integer getItemValue(PaperCard input) {
return input.getRules().getColor().countColors();
}
}),
CARD_TYPE("Type", PaperCard.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<PaperCard, String>(CardType.getCombinedSuperAndCoreTypes()) {
CARD_TYPE("lblType", PaperCard.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<PaperCard, String>(CardType.getCombinedSuperAndCoreTypes()) {
@Override
protected String getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -149,7 +167,7 @@ public class AdvancedSearch {
return types;
}
}),
CARD_SUB_TYPE("Subtype", PaperCard.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<PaperCard, String>(CardType.getSortedSubTypes()) {
CARD_SUB_TYPE("lblSubtype", PaperCard.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<PaperCard, String>(CardType.getSortedSubTypes()) {
@Override
protected String getItemValue(PaperCard input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -159,19 +177,19 @@ public class AdvancedSearch {
return (Set<String>)input.getRules().getType().getSubtypes();
}
}),
CARD_CMC("CMC", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
CARD_CMC("lblCMC", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
@Override
protected Integer getItemValue(PaperCard input) {
return input.getRules().getManaCost().getCMC();
}
}),
CARD_GENERIC_COST("Generic Cost", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
CARD_GENERIC_COST("lblGenericCost", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
@Override
protected Integer getItemValue(PaperCard input) {
return input.getRules().getManaCost().getGenericCost();
}
}),
CARD_POWER("Power", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
CARD_POWER("lblPower", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
@Override
protected Integer getItemValue(PaperCard input) {
CardRules rules = input.getRules();
@@ -181,7 +199,7 @@ public class AdvancedSearch {
return null;
}
}),
CARD_TOUGHNESS("Toughness", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
CARD_TOUGHNESS("lblToughness", PaperCard.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<PaperCard>(0, 20) {
@Override
protected Integer getItemValue(PaperCard input) {
CardRules rules = input.getRules();
@@ -191,19 +209,19 @@ public class AdvancedSearch {
return null;
}
}),
CARD_MANA_COST("Mana Cost", PaperCard.class, FilterOperator.STRING_OPS, new StringEvaluator<PaperCard>() {
CARD_MANA_COST("lblManaCost", PaperCard.class, FilterOperator.STRING_OPS, new StringEvaluator<PaperCard>() {
@Override
protected String getItemValue(PaperCard input) {
return input.getRules().getManaCost().toString();
}
}),
CARD_RARITY("Rarity", PaperCard.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<PaperCard, CardRarity>(Arrays.asList(CardRarity.FILTER_OPTIONS), CardRarity.FN_GET_LONG_NAME, CardRarity.FN_GET_LONG_NAME) {
CARD_RARITY("lblRarity", PaperCard.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<PaperCard, CardRarity>(Arrays.asList(CardRarity.FILTER_OPTIONS), CardRarity.FN_GET_LONG_NAME, CardRarity.FN_GET_LONG_NAME) {
@Override
protected CardRarity getItemValue(PaperCard input) {
return input.getRarity();
}
}),
CARD_FIRST_PRINTING("First Printing", PaperCard.class, FilterOperator.BOOLEAN_OPS, new BooleanEvaluator<PaperCard>() {
CARD_FIRST_PRINTING("lblFirstPrinting", PaperCard.class, FilterOperator.BOOLEAN_OPS, new BooleanEvaluator<PaperCard>() {
@Override
protected Boolean getItemValue(PaperCard input) {
List<PaperCard> cards = FModel.getMagicDb().getCommonCards().getAllCards(input.getName());
@@ -213,25 +231,276 @@ public class AdvancedSearch {
return cards.get(0) == input;
}
}),
DECK_NAME("Name", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
INVITEM_NAME("lblName", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator<InventoryItem>() {
@Override
protected String getItemValue(InventoryItem input) {
return input.getName();
}
}),
INVITEM_RULES_TEXT("lblRulesText", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator<InventoryItem>() {
@Override
protected String getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return "";
}
return ((PaperCard)input).getRules().getOracleText();
}
}),
INVITEM_KEYWORDS("lblKeywords", InventoryItem.class, FilterOperator.COLLECTION_OPS, new CustomListEvaluator<InventoryItem, Keyword>(Keyword.getAllKeywords()) {
@Override
protected Keyword getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<Keyword> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return Keyword.getKeywordSet((PaperCard)input);
}
}),
INVITEM_SET("lblSet", InventoryItem.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<InventoryItem, CardEdition>(FModel.getMagicDb().getSortedEditions(), CardEdition.FN_GET_CODE) {
@Override
protected CardEdition getItemValue(InventoryItem input) {
if (input instanceof PaperCard) {
return FModel.getMagicDb().getEditions().get(((PaperCard)input).getEdition());
} else if (input instanceof SealedProduct) {
return FModel.getMagicDb().getEditions().get(((SealedProduct)input).getEdition());
} else {
return CardEdition.UNKNOWN;
}
}
}),
INVITEM_FORMAT("lblFormat", InventoryItem.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<InventoryItem, GameFormat>((List<GameFormat>)FModel.getFormats().getFilterList()) {
@Override
protected GameFormat getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<GameFormat> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return FModel.getFormats().getAllFormatsOfCard((PaperCard)input);
}
}),
INVITEM_PLANE("lblPlane", InventoryItem.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<InventoryItem, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
@Override
protected ConquestPlane getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<ConquestPlane> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return ConquestPlane.getAllPlanesOfCard((PaperCard)input);
}
}),
INVITEM_REGION("lblRegion", InventoryItem.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<InventoryItem, ConquestRegion>(ConquestRegion.getAllRegions()) {
@Override
protected ConquestRegion getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<ConquestRegion> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return ConquestRegion.getAllRegionsOfCard((PaperCard)input);
}
}),
INVITEM_QUEST_WORLD("lblQuestWorld", InventoryItem.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<InventoryItem, QuestWorld>(ImmutableList.copyOf(FModel.getWorlds())) {
@Override
protected QuestWorld getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<QuestWorld> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return QuestWorld.getAllQuestWorldsOfCard(((PaperCard)input));
}
}),
INVITEM_COLOR("lblColor", InventoryItem.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<InventoryItem>() {
@Override
protected MagicColor.Color getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<MagicColor.Color> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return ((PaperCard)input).getRules().getColor().toEnumSet();
}
}),
INVITEM_COLOR_IDENTITY("lblColorIdentity", InventoryItem.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<InventoryItem>() {
@Override
protected MagicColor.Color getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<MagicColor.Color> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return ((PaperCard)input).getRules().getColorIdentity().toEnumSet();
}
}),
INVITEM_COLOR_COUNT("lblColorCount", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 5) {
@Override
protected Integer getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return 0;
}
return ((PaperCard)input).getRules().getColor().countColors();
}
}),
INVITEM_TYPE("lblType", InventoryItem.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<InventoryItem, String>(CardType.getCombinedSuperAndCoreTypes()) {
@Override
protected String getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<String> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
final CardType type = ((PaperCard)input).getRules().getType();
final Set<String> types = new HashSet<>();
for (Supertype t : type.getSupertypes()) {
types.add(t.name());
}
for (CoreType t : type.getCoreTypes()) {
types.add(t.name());
}
return types;
}
}),
INVITEM_SUB_TYPE("lblSubtype", InventoryItem.class, FilterOperator.COMBINATION_OPS, new CustomListEvaluator<InventoryItem, String>(CardType.getSortedSubTypes()) {
@Override
protected String getItemValue(InventoryItem input) {
throw new RuntimeException("getItemValues should be called instead");
}
@Override
protected Set<String> getItemValues(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return new HashSet<>();
}
return (Set<String>)((PaperCard)input).getRules().getType().getSubtypes();
}
}),
INVITEM_CMC("lblCMC", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 20) {
@Override
protected Integer getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return 0;
}
return ((PaperCard)input).getRules().getManaCost().getCMC();
}
}),
INVITEM_GENERIC_COST("lblGenericCost", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 20) {
@Override
protected Integer getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return 0;
}
return ((PaperCard)input).getRules().getManaCost().getGenericCost();
}
}),
INVITEM_POWER("lblPower", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 20) {
@Override
protected Integer getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return null;
}
CardRules rules = ((PaperCard)input).getRules();
if (rules.getType().isCreature()) {
return rules.getIntPower();
}
return null;
}
}),
INVITEM_TOUGHNESS("lblToughness", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 20) {
@Override
protected Integer getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return null;
}
CardRules rules = ((PaperCard)input).getRules();
if (rules.getType().isCreature()) {
return rules.getIntToughness();
}
return null;
}
}),
INVITEM_MANA_COST("lblManaCost", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator<InventoryItem>() {
@Override
protected String getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return "";
}
return ((PaperCard)input).getRules().getManaCost().toString();
}
}),
INVITEM_FIRST_PRINTING("lblFirstPrinting", InventoryItem.class, FilterOperator.BOOLEAN_OPS, new BooleanEvaluator<InventoryItem>() {
@Override
protected Boolean getItemValue(InventoryItem input) {
List<PaperCard> cards = FModel.getMagicDb().getCommonCards().getAllCards(input.getName());
if (cards.size() <= 1) { return true; }
Collections.sort(cards, FModel.getMagicDb().getEditions().CARD_EDITION_COMPARATOR);
return cards.get(0) == input;
}
}),
INVITEM_RARITY("lblRarity", InventoryItem.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<InventoryItem, CardRarity>(Arrays.asList(CardRarity.FILTER_OPTIONS), CardRarity.FN_GET_LONG_NAME, CardRarity.FN_GET_LONG_NAME) {
@Override
protected CardRarity getItemValue(InventoryItem input) {
if (!(input instanceof PaperCard)) {
return CardRarity.Special;
}
return ((PaperCard)input).getRarity();
}
}),
INVITEM_BUY_PRICE("lblBuyPrice", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 10000000) {
@Override
protected Integer getItemValue(InventoryItem input) {
return (Integer) QuestSpellShop.fnPriceGet.apply(new AbstractMap.SimpleEntry<InventoryItem, Integer>(input, 1));
}
}),
INVITEM_SELL_PRICE("lblSellPrice", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 10000000) {
@Override
protected Integer getItemValue(InventoryItem input) {
return (Integer) QuestSpellShop.fnPriceSellGet.apply(new AbstractMap.SimpleEntry<InventoryItem, Integer>(input, 1));
}
}),
INVITEM_USED_IN_QUEST_DECKS("lblUsedInQuestDecks", InventoryItem.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<InventoryItem>(0, 100) {
@Override
protected Integer getItemValue(InventoryItem input) {
return (Integer) Integer.parseInt((String) QuestSpellShop.fnDeckGet.apply(new AbstractMap.SimpleEntry<InventoryItem, Integer>(input, 1)));
}
}),
DECK_NAME("lblName", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
@Override
protected String getItemValue(DeckProxy input) {
return input.getName();
}
}),
DECK_FOLDER("Folder", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
DECK_FOLDER("lblFolder", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
@Override
protected String getItemValue(DeckProxy input) {
return input.getPath();
}
}),
DECK_FAVORITE("Favorite", DeckProxy.class, FilterOperator.BOOLEAN_OPS, new BooleanEvaluator<DeckProxy>() {
DECK_FAVORITE("ttFavorite", DeckProxy.class, FilterOperator.BOOLEAN_OPS, new BooleanEvaluator<DeckProxy>() {
@Override
protected Boolean getItemValue(DeckProxy input) {
return input.isFavoriteDeck();
}
}),
DECK_FORMAT("Format", DeckProxy.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<DeckProxy, GameFormat>((List<GameFormat>)FModel.getFormats().getFilterList()) {
DECK_FORMAT("lblFormat", DeckProxy.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<DeckProxy, GameFormat>((List<GameFormat>)FModel.getFormats().getFilterList()) {
@Override
protected GameFormat getItemValue(DeckProxy input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -241,7 +510,7 @@ public class AdvancedSearch {
return input.getExhaustiveFormats();
}
}),
DECK_QUEST_WORLD("Quest World", DeckProxy.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<DeckProxy, QuestWorld>(ImmutableList.copyOf(FModel.getWorlds())) {
DECK_QUEST_WORLD("lblQuestWorld", DeckProxy.class, FilterOperator.MULTI_LIST_OPS, new CustomListEvaluator<DeckProxy, QuestWorld>(ImmutableList.copyOf(FModel.getWorlds())) {
@Override
protected QuestWorld getItemValue(DeckProxy input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -251,7 +520,7 @@ public class AdvancedSearch {
return QuestWorld.getAllQuestWorldsOfDeck(input.getDeck());
}
}),
DECK_COLOR("Color", DeckProxy.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<DeckProxy>() {
DECK_COLOR("lblColor", DeckProxy.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<DeckProxy>() {
@Override
protected MagicColor.Color getItemValue(DeckProxy input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -261,7 +530,7 @@ public class AdvancedSearch {
return input.getColor().toEnumSet();
}
}),
DECK_COLOR_IDENTITY("Color Identity", DeckProxy.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<DeckProxy>() {
DECK_COLOR_IDENTITY("lblColorIdentity", DeckProxy.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<DeckProxy>() {
@Override
protected MagicColor.Color getItemValue(DeckProxy input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -271,25 +540,25 @@ public class AdvancedSearch {
return input.getColorIdentity().toEnumSet();
}
}),
DECK_COLOR_COUNT("Color Count", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 5) {
DECK_COLOR_COUNT("lblColorCount", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 5) {
@Override
protected Integer getItemValue(DeckProxy input) {
return input.getColor().countColors();
}
}),
DECK_AVERAGE_CMC("Average CMC", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 20) {
DECK_AVERAGE_CMC("lblAverageCMC", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 20) {
@Override
protected Integer getItemValue(DeckProxy input) {
return input.getAverageCMC();
}
}),
DECK_MAIN("Main Deck", DeckProxy.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<DeckProxy>() {
DECK_MAIN("lblMainDeck", DeckProxy.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<DeckProxy>() {
@Override
protected Map<String, Integer> getItemValue(DeckProxy input) {
return input.getDeck().getMain().toNameLookup();
}
}),
DECK_SIDEBOARD("Sideboard", DeckProxy.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<DeckProxy>() {
DECK_SIDEBOARD("lblSideboard", DeckProxy.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<DeckProxy>() {
@Override
protected Map<String, Integer> getItemValue(DeckProxy input) {
CardPool sideboard = input.getDeck().get(DeckSection.Sideboard);
@@ -299,31 +568,31 @@ public class AdvancedSearch {
return null;
}
}),
DECK_MAIN_SIZE("Main Deck Size", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(40, 250) {
DECK_MAIN_SIZE("lblMainDeckSize", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(40, 250) {
@Override
protected Integer getItemValue(DeckProxy input) {
return input.getMainSize();
}
}),
DECK_SIDE_SIZE("Sideboard Size", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 15) {
DECK_SIDE_SIZE("lblSideboardSize", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(0, 15) {
@Override
protected Integer getItemValue(DeckProxy input) {
return Math.min(input.getSideSize(), 0);
}
}),
COMMANDER_NAME("Name", ConquestCommander.class, FilterOperator.STRING_OPS, new StringEvaluator<ConquestCommander>() {
COMMANDER_NAME("lblName", ConquestCommander.class, FilterOperator.STRING_OPS, new StringEvaluator<ConquestCommander>() {
@Override
protected String getItemValue(ConquestCommander input) {
return input.getName();
}
}),
COMMANDER_ORIGIN("Origin", ConquestCommander.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<ConquestCommander, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
COMMANDER_ORIGIN("lblOrigin", ConquestCommander.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<ConquestCommander, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
@Override
protected ConquestPlane getItemValue(ConquestCommander input) {
return input.getOriginPlane();
}
}),
COMMANDER_COLOR("Color", ConquestCommander.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<ConquestCommander>() {
COMMANDER_COLOR("lblColor", ConquestCommander.class, FilterOperator.COMBINATION_OPS, new ColorEvaluator<ConquestCommander>() {
@Override
protected MagicColor.Color getItemValue(ConquestCommander input) {
throw new RuntimeException("getItemValues should be called instead");
@@ -333,25 +602,25 @@ public class AdvancedSearch {
return input.getCard().getRules().getColorIdentity().toEnumSet();
}
}),
COMMANDER_COLOR_COUNT("Color Count", ConquestCommander.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<ConquestCommander>(0, 5) {
COMMANDER_COLOR_COUNT("lblColorCount", ConquestCommander.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<ConquestCommander>(0, 5) {
@Override
protected Integer getItemValue(ConquestCommander input) {
return input.getCard().getRules().getColorIdentity().countColors();
}
}),
COMMANDER_DECK_AVERAGE_CMC("Deck Average CMC", ConquestCommander.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<ConquestCommander>(0, 20) {
COMMANDER_DECK_AVERAGE_CMC("lblDeckAverageCMC", ConquestCommander.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<ConquestCommander>(0, 20) {
@Override
protected Integer getItemValue(ConquestCommander input) {
return DeckProxy.getAverageCMC(input.getDeck());
}
}),
COMMANDER_DECK_CONTENTS("Deck Contents", ConquestCommander.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<ConquestCommander>() {
COMMANDER_DECK_CONTENTS("lblDeckContents", ConquestCommander.class, FilterOperator.DECK_CONTENT_OPS, new DeckContentEvaluator<ConquestCommander>() {
@Override
protected Map<String, Integer> getItemValue(ConquestCommander input) {
return input.getDeck().getMain().toNameLookup();
}
}),
COMMANDER_DECK_SIZE("Deck Size", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(40, 250) {
COMMANDER_DECK_SIZE("lblDeckSize", DeckProxy.class, FilterOperator.NUMBER_OPS, new NumericEvaluator<DeckProxy>(40, 250) {
@Override
protected Integer getItemValue(DeckProxy input) {
return input.getMainSize();
@@ -364,7 +633,7 @@ public class AdvancedSearch {
private final FilterEvaluator<? extends InventoryItem, ?> evaluator;
FilterOption(String name0, Class<? extends InventoryItem> type0, FilterOperator[] operatorOptions0, FilterEvaluator<? extends InventoryItem, ?> evaluator0) {
name = name0;
name = Localizer.getInstance().getMessage(name0);
type = type0;
operatorOptions = operatorOptions0;
evaluator = evaluator0;
@@ -378,7 +647,7 @@ public class AdvancedSearch {
public enum FilterOperator {
//Boolean operators
IS_TRUE("is true", "%1$s is true", FilterValueCount.ZERO, new OperatorEvaluator<Boolean>() {
IS_TRUE("lblIsTrue", "%1$s is true", FilterValueCount.ZERO, new OperatorEvaluator<Boolean>() {
@Override
public boolean apply(Boolean input, List<Boolean> values) {
if (input != null) {
@@ -387,7 +656,7 @@ public class AdvancedSearch {
return false;
}
}),
IS_FALSE("is false", "%1$s is false", FilterValueCount.ZERO, new OperatorEvaluator<Boolean>() {
IS_FALSE("lblIsFalse", "%1$s is false", FilterValueCount.ZERO, new OperatorEvaluator<Boolean>() {
@Override
public boolean apply(Boolean input, List<Boolean> values) {
if (input != null) {
@@ -398,7 +667,7 @@ public class AdvancedSearch {
}),
//Numeric operators
EQUALS("=", "%1$s = %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
EQUALS("lblEqual", "%1$s = %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -407,7 +676,7 @@ public class AdvancedSearch {
return false;
}
}),
NOT_EQUALS("<>", "%1$s <> %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
NOT_EQUALS("lblNotEqual", "%1$s <> %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -416,7 +685,7 @@ public class AdvancedSearch {
return true;
}
}),
GREATER_THAN(">", "%1$s > %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
GREATER_THAN("lblGreaterThan", "%1$s > %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -425,7 +694,7 @@ public class AdvancedSearch {
return false;
}
}),
LESS_THAN("<", "%1$s < %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
LESS_THAN("lblLessThan", "%1$s < %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -434,7 +703,7 @@ public class AdvancedSearch {
return false;
}
}),
GT_OR_EQUAL(">=", "%1$s >= %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
GT_OR_EQUAL("lblGreaterThanOrEqual", "%1$s >= %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -443,7 +712,7 @@ public class AdvancedSearch {
return false;
}
}),
LT_OR_EQUAL("<=", "%1$s <= %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
LT_OR_EQUAL("lblLessThanOrEqual", "%1$s <= %2$d", FilterValueCount.ONE, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -452,7 +721,7 @@ public class AdvancedSearch {
return false;
}
}),
BETWEEN_INCLUSIVE("<=|<=", "%2$d <= %1$s <= %3$d", FilterValueCount.TWO, new OperatorEvaluator<Integer>() {
BETWEEN_INCLUSIVE("lblBetweenInclusive", "%2$d <= %1$s <= %3$d", FilterValueCount.TWO, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -462,7 +731,7 @@ public class AdvancedSearch {
return false;
}
}),
BETWEEN_EXCLUSIVE("<|<", "%2$d < %1$s < %3$d", FilterValueCount.TWO, new OperatorEvaluator<Integer>() {
BETWEEN_EXCLUSIVE("lblBetweenExclusive", "%2$d < %1$s < %3$d", FilterValueCount.TWO, new OperatorEvaluator<Integer>() {
@Override
public boolean apply(Integer input, List<Integer> values) {
if (input != null) {
@@ -474,7 +743,7 @@ public class AdvancedSearch {
}),
//String operators
CONTAINS("contains", "%1$s contains '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
CONTAINS("lblContains", "%1$s contains '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
@Override
public boolean apply(String input, List<String> values) {
if (input != null) {
@@ -482,8 +751,20 @@ public class AdvancedSearch {
}
return false;
}
@Override
public boolean apply(Set<String> inputs, List<String> values) {
if (inputs != null && !inputs.isEmpty() && !values.isEmpty()) {
for (String input : inputs) {
if (apply(input, values)) {
return true;
}
}
return false;
}
return false;
}
}),
STARTS_WITH("starts with", "%1$s starts with '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
STARTS_WITH("lblStartsWith", "%1$s starts with '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
@Override
public boolean apply(String input, List<String> values) {
if (input != null) {
@@ -491,8 +772,20 @@ public class AdvancedSearch {
}
return false;
}
@Override
public boolean apply(Set<String> inputs, List<String> values) {
if (inputs != null && !inputs.isEmpty() && !values.isEmpty()) {
for (String input : inputs) {
if (apply(input, values)) {
return true;
}
}
return false;
}
return false;
}
}),
ENDS_WITH("ends with", "%1$s ends with '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
ENDS_WITH("lblEndsWith", "%1$s ends with '%2$s'", FilterValueCount.ONE, new OperatorEvaluator<String>() {
@Override
public boolean apply(String input, List<String> values) {
if (input != null) {
@@ -500,10 +793,22 @@ public class AdvancedSearch {
}
return false;
}
@Override
public boolean apply(Set<String> inputs, List<String> values) {
if (inputs != null && !inputs.isEmpty() && !values.isEmpty()) {
for (String input : inputs) {
if (apply(input, values)) {
return true;
}
}
return false;
}
return false;
}
}),
//Custom list operators
IS_EXACTLY("is exactly", "%1$s is %2$s", FilterValueCount.MANY, new OperatorEvaluator<Object>() {
IS_EXACTLY("lblIsExactly", "%1$s is %2$s", FilterValueCount.MANY, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
if (input != null && values.size() == 1) {
@@ -524,7 +829,7 @@ public class AdvancedSearch {
return false;
}
}),
IS_ANY("is any of", "%1$s is %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
IS_ANY("lblIsAnyOf", "%1$s is %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
if (input != null) {
@@ -548,7 +853,7 @@ public class AdvancedSearch {
return false;
}
}),
CONTAINS_ANY("contains any of", "%1$s contains %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
CONTAINS_ANY("lblContainsAnyOf", "%1$s contains %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
if (input != null) {
@@ -572,7 +877,7 @@ public class AdvancedSearch {
return false;
}
}),
CONTAINS_ALL("contains all of", "%1$s contains %2$s", FilterValueCount.MANY_AND, new OperatorEvaluator<Object>() {
CONTAINS_ALL("lblContainsAllOf", "%1$s contains %2$s", FilterValueCount.MANY_AND, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
if (input != null && values.size() == 1) {
@@ -593,7 +898,7 @@ public class AdvancedSearch {
return false;
}
}),
CONTAIN_ANY("contain any of", "%1$s contain %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
CONTAIN_ANY("lblContainAnyOf", "%1$s contain %2$s", FilterValueCount.MANY_OR, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
throw new RuntimeException("shouldn't be called with a single input");
@@ -610,7 +915,7 @@ public class AdvancedSearch {
return false;
}
}),
CONTAIN_ALL("contain all of", "%1$s contain %2$s", FilterValueCount.MANY_AND, new OperatorEvaluator<Object>() {
CONTAIN_ALL("lblContainAllOf", "%1$s contain %2$s", FilterValueCount.MANY_AND, new OperatorEvaluator<Object>() {
@Override
public boolean apply(Object input, List<Object> values) {
throw new RuntimeException("shouldn't be called with a single input");
@@ -630,7 +935,7 @@ public class AdvancedSearch {
}),
//Deck content operators
CONTAINS_CARD("contains card", "%1$s contains %2$s", FilterValueCount.ONE, new OperatorEvaluator<Map<PaperCard, Integer>>() {
CONTAINS_CARD("lblContainsCard", "%1$s contains %2$s", FilterValueCount.ONE, new OperatorEvaluator<Map<PaperCard, Integer>>() {
@Override
public boolean apply(Map<PaperCard, Integer> input, List<Map<PaperCard, Integer>> values) {
if (input != null) {
@@ -640,7 +945,7 @@ public class AdvancedSearch {
return false;
}
}),
CONTAINS_X_COPIES_OF_CARD("contains X copies of card", "%1$s contains %3$d %2$s", FilterValueCount.TWO, new OperatorEvaluator<Map<PaperCard, Integer>>() {
CONTAINS_X_COPIES_OF_CARD("lblContainsXCopiesCard", "%1$s contains %3$d %2$s", FilterValueCount.TWO, new OperatorEvaluator<Map<PaperCard, Integer>>() {
@Override
public boolean apply(Map<PaperCard, Integer> input, List<Map<PaperCard, Integer>> values) {
if (input != null) {
@@ -660,6 +965,9 @@ public class AdvancedSearch {
public static final FilterOperator[] STRING_OPS = new FilterOperator[] {
CONTAINS, STARTS_WITH, ENDS_WITH
};
public static final FilterOperator[] STRINGS_OPS = new FilterOperator[] {
CONTAINS, STARTS_WITH, ENDS_WITH
};
public static final FilterOperator[] SINGLE_LIST_OPS = new FilterOperator[] {
IS_ANY
};
@@ -681,7 +989,7 @@ public class AdvancedSearch {
private final OperatorEvaluator<?> evaluator;
FilterOperator(String caption0, String formatStr0, FilterValueCount valueCount0, OperatorEvaluator<?> evaluator0) {
caption = caption0;
caption = Localizer.getInstance().getMessage(caption0);
formatStr = formatStr0;
valueCount = valueCount0;
evaluator = evaluator0;
@@ -714,27 +1022,32 @@ public class AdvancedSearch {
@SuppressWarnings("unchecked")
public final Filter<T> createFilter(FilterOption option, FilterOperator operator) {
final List<V> values = getValues(option, operator);
if (values == null || values.isEmpty()) { return null; }
if (values == null || values.isEmpty()) {
return null;
}
String caption = getCaption(values, option, operator);
final OperatorEvaluator<V> evaluator = (OperatorEvaluator<V>)operator.evaluator;
Predicate<T> predicate;
if (option.operatorOptions == FilterOperator.MULTI_LIST_OPS || option.operatorOptions == FilterOperator.COMBINATION_OPS || option.operatorOptions == FilterOperator.COLLECTION_OPS) {
predicate = new Predicate<T>() {
@Override
public boolean apply(T input) {
return evaluator.apply(getItemValues(input), values);
}
};
}
else {
predicate = new Predicate<T>() {
@Override
public boolean apply(T input) {
return evaluator.apply(getItemValue(input), values);
}
};
final OperatorEvaluator<V> evaluator = (OperatorEvaluator<V>) operator.evaluator;
Predicate<T> predicate = new Predicate<T>() {
@Override
public boolean apply(T input) {
return evaluator.apply(getItemValue(input), values);
}
};
;
final FilterOperator[][] manyValueOperators = { FilterOperator.MULTI_LIST_OPS,
FilterOperator.COMBINATION_OPS, FilterOperator.COLLECTION_OPS, FilterOperator.STRINGS_OPS };
for (FilterOperator[] oper : manyValueOperators) {
if (option.operatorOptions == oper) {
predicate = new Predicate<T>() {
@Override
public boolean apply(T input) {
return evaluator.apply(getItemValues(input), values);
}
};
break;
}
}
return new Filter<>(option, operator, caption, predicate);
}
@@ -930,7 +1243,7 @@ public class AdvancedSearch {
Integer amount = -1;
if (operator == FilterOperator.CONTAINS_X_COPIES_OF_CARD) { //prompt for quantity if needed
amount = SGuiChoose.getInteger("How many copies of " + card.getName() + "?", 0, 4);
amount = SGuiChoose.getInteger(Localizer.getInstance().getMessage("lblHowManyCopiesOfN", CardTranslation.getTranslatedName(card.getName())), 0, 4);
if (amount == null) { return null; }
}
@@ -967,7 +1280,7 @@ public class AdvancedSearch {
options.add(opt);
}
}
option = SGuiChoose.oneOrNone("Select a filter type", options, defaultOption, null);
option = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblSelectAFilterType"), options, defaultOption, null);
if (option == null) { return editFilter; }
}
else {
@@ -979,7 +1292,7 @@ public class AdvancedSearch {
final FilterOperator operator;
if (option.operatorOptions.length > 1) {
final FilterOperator defaultOperator = option == defaultOption ? editFilter.operator : null;
operator = SGuiChoose.oneOrNone("Select an operator for " + option.name, option.operatorOptions, defaultOperator, null);
operator = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblSelectOperatorFor", option.name), option.operatorOptions, defaultOperator, null);
if (operator == null) { return editFilter; }
}
else {
@@ -1049,7 +1362,7 @@ public class AdvancedSearch {
}
public static class Model<T extends InventoryItem> {
private static final String EMPTY_FILTER_TEXT = "Select Filter...";
private static final String EMPTY_FILTER_TEXT = Localizer.getInstance().getMessage("lblSelectingFilter");
private final List<Object> expression = new ArrayList<>();
private final List<IFilterControl<T>> controls = new ArrayList<>();
@@ -1162,7 +1475,7 @@ public class AdvancedSearch {
//the first time the user selects keywords, preload keywords for all cards
Runnable preloadTask = Keyword.getPreloadTask();
if (preloadTask != null) {
GuiBase.getInterface().runBackgroundTask("Loading keywords...", preloadTask);
GuiBase.getInterface().runBackgroundTask(Localizer.getInstance().getMessage("lblLoadingKeywords"), preloadTask);
}
}
}

View File

@@ -31,6 +31,7 @@ import forge.itemmanager.ItemColumnConfig.SortState;
import forge.limited.DraftRankCache;
import forge.model.FModel;
import forge.util.Localizer;
import forge.util.CardTranslation;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map.Entry;
@@ -57,12 +58,16 @@ public enum ColumnDef {
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override
public Comparable<?> apply(final Entry<InventoryItem, Integer> from) {
if (from.getKey() instanceof PaperCard)
return toSortableName(from.getKey().toString());
return toSortableName(from.getKey().getName());
}
},
new Function<Entry<? extends InventoryItem, Integer>, Object>() {
@Override
public Object apply(final Entry<? extends InventoryItem, Integer> from) {
if (from.getKey() instanceof PaperCard)
return from.getKey().toString();
return from.getKey().getName();
}
}),
@@ -86,13 +91,13 @@ public enum ColumnDef {
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override
public Comparable<?> apply(final Entry<InventoryItem, Integer> from) {
return toType(from.getKey());
return CardTranslation.getTranslatedType(from.getKey().getName(),toType(from.getKey()));
}
},
new Function<Entry<? extends InventoryItem, Integer>, Object>() {
@Override
public Object apply(final Entry<? extends InventoryItem, Integer> from) {
return toType(from.getKey());
return CardTranslation.getTranslatedType(from.getKey().getName(),toType(from.getKey()));
}
}),
/**The mana cost column.*/

View File

@@ -15,9 +15,10 @@ import forge.deck.DeckProxy;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.util.Localizer;
public enum GroupDef {
COLOR("Color", getColorGroups(),
COLOR("lblColor", getColorGroups(),
new Function<Integer, ColumnDef>() {
@Override
public ColumnDef apply(final Integer groupIndex) {
@@ -36,7 +37,7 @@ public enum GroupDef {
return -1;
}
}),
COLOR_IDENTITY("Color Identity", getColorGroups(),
COLOR_IDENTITY("lblColorIdentity", getColorGroups(),
new Function<Integer, ColumnDef>() {
@Override
public ColumnDef apply(final Integer groupIndex) {
@@ -55,7 +56,7 @@ public enum GroupDef {
return -1;
}
}),
SET("Set", getSetGroups(),
SET("lblSet", getSetGroups(),
new Function<Integer, ColumnDef>() {
@Override
public ColumnDef apply(final Integer groupIndex) {
@@ -74,7 +75,7 @@ public enum GroupDef {
return -1;
}
}),
DEFAULT("Default",
DEFAULT("lblDefault",
new String[] { "Creatures", "Spells", "Lands" },
new Function<Integer, ColumnDef>() {
@Override
@@ -104,7 +105,7 @@ public enum GroupDef {
}
}),
CARD_TYPE("Type",
CARD_TYPE("lblType",
new String[] { "Planeswalker", "Creature", "Sorcery", "Instant", "Artifact", "Enchantment", "Land", "Tribal instant" },
new Function<Integer, ColumnDef>() {
@Override
@@ -148,7 +149,7 @@ public enum GroupDef {
return -1;
}
}),
PW_DECK_SORT("Planeswalker Deck Sort",
PW_DECK_SORT("lblPlaneswalkerDeckSort",
new String[] { "Planeswalker", "Rares", "Creature", "Land", "Other Spells" },
new Function<Integer, ColumnDef>() {
@Override
@@ -178,7 +179,7 @@ public enum GroupDef {
return -1;
}
}),
CARD_RARITY("Rarity",
CARD_RARITY("lblRarity",
new String[] { "Mythic Rares", "Rares", "Uncommons", "Commons", "Basic Lands" },
new Function<Integer, ColumnDef>() {
@Override
@@ -210,7 +211,7 @@ public enum GroupDef {
});
GroupDef(String name0, String[] groups0, Function<Integer, ColumnDef> fnGetPileByOverride0, Function<InventoryItem, Integer> fnGroupItem0) {
this.name = name0;
this.name = Localizer.getInstance().getMessage(name0);
this.groups = groups0;
this.fnGetPileByOverride = fnGetPileByOverride0;
this.fnGroupItem = fnGroupItem0;

View File

@@ -100,6 +100,16 @@ public enum ItemManagerConfig {
null, null, 3, 0),
NET_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
NET_ARCHIVE_STANDARD_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
NET_ARCHIVE_PIONEER_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
NET_ARCHIVE_MODERN_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
NET_ARCHIVE_LEGACY_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
NET_ARCHIVE_VINTAGE_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
null, null, 3, 0),
SIDEBOARD(SColumnUtil.getDeckEditorDefaultColumns(), false, false, true,
GroupDef.DEFAULT, ColumnDef.CMC, 3, 0);

View File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Iterator;
import java.util.Map.Entry;
import forge.item.InventoryItem;
@@ -46,11 +47,11 @@ public final class ItemManagerModel<T extends InventoryItem> {
// same thing as above, it was copied to provide sorting (needed by table
// views in deck editors)
private final transient List<Entry<T, Integer>> itemsOrdered = new ArrayList<>();
private final transient List<Entry<T, Integer>> itemsOrdered = Collections.synchronizedList(new ArrayList<>());
protected transient boolean isListInSync = false;
public List<Entry<T, Integer>> getOrderedList() {
public synchronized List<Entry<T, Integer>> getOrderedList() {
if (!isListInSync) {
rebuildOrderedList();
}
@@ -123,15 +124,16 @@ public final class ItemManagerModel<T extends InventoryItem> {
}
public void refreshSort() {
if (getOrderedList().isEmpty()) { return; }
//fix newdeck editor not loading on Android if a user deleted unwanted sets on edition folder
try { Collections.sort(getOrderedList(), new MyComparator()); }
final List<Entry<T, Integer>> list = getOrderedList();
if (list.isEmpty()) { return; }
try { Collections.sort(list, new MyComparator()); }
//fix NewDeck editor not loading on Android if a user deleted unwanted sets on edition folder
catch (IllegalArgumentException ex) {}
}
//Manages sorting orders for multiple depths of sorting
public final class CascadeManager {
private final List<ItemColumn> colsToSort = new ArrayList<>(3);
private final List<ItemColumn> colsToSort = Collections.synchronizedList(new ArrayList<>(3));
private Sorter sorter = null;
// Adds a column to sort cascade list.
@@ -187,12 +189,15 @@ public final class ItemManagerModel<T extends InventoryItem> {
private Sorter createSorter() {
final List<ItemPoolSorter<InventoryItem>> oneColSorters = new ArrayList<>(maxSortDepth);
for (final ItemColumn col : colsToSort) {
oneColSorters.add(new ItemPoolSorter<>(
col.getFnSort(),
col.getConfig().getSortState().equals(SortState.ASC)));
synchronized (colsToSort) {
final Iterator<ItemColumn> it = colsToSort.iterator();
while (it.hasNext()) {
final ItemColumn col = it.next();
oneColSorters.add(new ItemPoolSorter<>(
col.getFnSort(),
col.getConfig().getSortState().equals(SortState.ASC)));
}
}
return new Sorter(oneColSorters);
}

View File

@@ -1,6 +1,7 @@
package forge.itemmanager;
import com.google.common.base.Predicate;
import forge.GuiBase;
import forge.assets.FSkinProp;
import forge.assets.IHasSkinProp;
import forge.card.CardRules;
@@ -35,7 +36,8 @@ public final class SItemManagerUtil {
RED (FSkinProp.IMG_MANA_R, CardRulesPredicates.Presets.IS_RED, "lblRedcards"),
GREEN (FSkinProp.IMG_MANA_G, CardRulesPredicates.Presets.IS_GREEN, "lblGreencards"),
COLORLESS (FSkinProp.IMG_MANA_COLORLESS, CardRulesPredicates.Presets.IS_COLORLESS, "lblColorlesscards"),
MULTICOLOR (FSkinProp.IMG_MULTI, CardRulesPredicates.Presets.IS_MULTICOLOR, "lblMulticolorcards"),
MULTICOLOR (GuiBase.getInterface().isLibgdxPort() ? FSkinProp.IMG_HDMULTI :
FSkinProp.IMG_MULTI, CardRulesPredicates.Presets.IS_MULTICOLOR, "lblMulticolorcards"),
PACK_OR_DECK (FSkinProp.IMG_PACK, null, "lblPackordeck"),
LAND (FSkinProp.IMG_LAND, CardRulesPredicates.Presets.IS_LAND, "lblLands"),
@@ -60,7 +62,8 @@ public final class SItemManagerUtil {
DECK_RED (FSkinProp.IMG_MANA_R, null, "lblReddecks"),
DECK_GREEN (FSkinProp.IMG_MANA_G, null, "lblGreendecks"),
DECK_COLORLESS (FSkinProp.IMG_MANA_COLORLESS, null, "lblColorlessdecks"),
DECK_MULTICOLOR (FSkinProp.IMG_MULTI, null, "lblMulticolordecks"),
DECK_MULTICOLOR (GuiBase.getInterface().isLibgdxPort() ? FSkinProp.IMG_HDMULTI :
FSkinProp.IMG_MULTI, null, "lblMulticolordecks"),
FOIL_OLD (FSkinProp.FOIL_11, null, "lblOldstyleFoilcards"),
FOIL_NEW (FSkinProp.FOIL_01, null, "lblNewstyleFoilcards"),

View File

@@ -20,7 +20,6 @@ package forge.limited;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.card.CardEdition;
import forge.deck.CardPool;
@@ -41,7 +40,6 @@ import forge.util.gui.SGuiChoose;
import forge.util.gui.SOptionPane;
import forge.util.storage.IStorage;
import forge.util.Localizer;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
@@ -57,6 +55,7 @@ public class BoosterDraft implements IBoosterDraft {
private final List<LimitedPlayer> players = new ArrayList<>();
private LimitedPlayer localPlayer;
private String doublePickDuringDraft = ""; // "FirstPick" or "Always"
protected int nextBoosterGroup = 0;
private int currentBoosterSize = 0;
private int currentBoosterPick = 0;
@@ -142,7 +141,11 @@ public class BoosterDraft implements IBoosterDraft {
this.product.add(block.getBooster(pp[i]));
}
} else {
final IUnOpenedProduct product1 = block.getBooster(sets.get(0));
// Only one set is chosen. If that set lets you draft 2 cards to start adjust draft settings now
String setCode = sets.get(0);
doublePickDuringDraft = FModel.getMagicDb().getEditions().get(setCode).getDoublePickDuringDraft();
final IUnOpenedProduct product1 = block.getBooster(setCode);
for (int i = 0; i < nPacks; i++) {
this.product.add(product1);
@@ -176,34 +179,40 @@ public class BoosterDraft implements IBoosterDraft {
break;
case Chaos:
/**
* A chaos draft consists of boosters from many different sets.
* Default settings are boosters from all sets with a booster size of 15 cards.
* Alternatively, the sets can be restricted to a format like Modern or to a theme.
* Examples for themes: sets that take place on a certain plane, core sets, masters sets,
* or sets that share a mechanic.
*/
// Get chaos draft themes
final List<ThemedChaosDraft> themes = new ArrayList<>();
final IStorage<ThemedChaosDraft> themeStorage = FModel.getThemedChaosDrafts();
for (final ThemedChaosDraft theme : themeStorage) {
themes.add(theme);
}
Collections.sort(themes); // sort for user interface
// Ask user to select theme
final String dialogQuestion = Localizer.getInstance().getMessage("lblChooseChaosTheme");
final ThemedChaosDraft theme = SGuiChoose.oneOrNone(dialogQuestion, themes);
if (theme == null) {
return false; // abort if no theme is selected
}
// Filter all sets by theme restrictions
final Predicate<CardEdition> themeFilter = theme.getEditionFilter();
final CardEdition.Collection allEditions = StaticData.instance().getEditions();
final Iterable<CardEdition> chaosDraftEditions = Iterables.filter(allEditions.getOrderedEditions(), new Predicate<CardEdition>() {
@Override
public boolean apply(final CardEdition cardEdition) {
boolean isExpansion = cardEdition.getType().equals(CardEdition.Type.EXPANSION);
boolean isCoreSet = cardEdition.getType().equals(CardEdition.Type.CORE);
boolean isReprintSet = cardEdition.getType().equals(CardEdition.Type.REPRINT);
if (isExpansion || isCoreSet || isReprintSet) {
// Only allow sets with 15 cards in booster packs
if (cardEdition.hasBoosterTemplate()) {
final List<Pair<String, Integer>> slots = cardEdition.getBoosterTemplate().getSlots();
int boosterSize = 0;
for (Pair<String, Integer> slot : slots) {
boosterSize += slot.getRight();
}
return boosterSize == 15;
}
}
return false;
}
});
// Randomize order of sets
List<CardEdition> shuffled = Lists.newArrayList(chaosDraftEditions);
Collections.shuffle(shuffled);
final Supplier<List<PaperCard>> ChaosDraftSupplier = new ChaosBoosterSupplier(shuffled);
final Iterable<CardEdition> chaosDraftEditions = Iterables.filter(
allEditions.getOrderedEditions(),
themeFilter);
// Add chaos "boosters" as special suppliers
final Supplier<List<PaperCard>> ChaosDraftSupplier;
try {
ChaosDraftSupplier = new ChaosBoosterSupplier(chaosDraftEditions);
} catch(IllegalArgumentException e) {
System.out.println(e.getMessage());
return false;
}
for (int i = 0; i < 3; i++) {
this.product.add(ChaosDraftSupplier);
}
@@ -220,7 +229,11 @@ public class BoosterDraft implements IBoosterDraft {
final BoosterDraft draft = new BoosterDraft(draftType);
for (String booster : boosters) {
draft.product.add(block.getBooster(booster));
try {
draft.product.add(block.getBooster(booster));
} catch (Exception ex) {
System.err.println("Booster Draft Error: "+ex.getMessage());
}
}
IBoosterDraft.LAND_SET_CODE[0] = block.getLandSet();
@@ -349,7 +362,7 @@ public class BoosterDraft implements IBoosterDraft {
public Deck[] getDecks() {
Deck[] decks = new Deck[7];
for (int i = 1; i < N_PLAYERS; i++) {
decks[i - 1] = ((LimitedPlayerAI) this.players.get(i)).buildDeck();
decks[i - 1] = ((LimitedPlayerAI) this.players.get(i)).buildDeck(IBoosterDraft.LAND_SET_CODE[0] != null ? IBoosterDraft.LAND_SET_CODE[0].getCode() : null);
}
return decks;
}
@@ -357,6 +370,13 @@ public class BoosterDraft implements IBoosterDraft {
public void passPacks() {
// Alternate direction of pack passing
int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1;
if ("FirstPick".equals(this.doublePickDuringDraft) && currentBoosterPick == 1) {
adjust = 0;
} else if (currentBoosterPick % 2 == 1 && "Always".equals(this.doublePickDuringDraft)) {
// This may not work with Conspiracy cards that mess with the draft
adjust = 0;
}
for (int i = 0; i < N_PLAYERS; i++) {
List<PaperCard> passingPack = this.players.get(i).passPack();
@@ -494,26 +514,28 @@ public class BoosterDraft implements IBoosterDraft {
}
private void recordDraftPick(final List<PaperCard> thisBooster, PaperCard c) {
if (ForgePreferences.UPLOAD_DRAFT) {
for (int i = 0; i < thisBooster.size(); i++) {
final PaperCard cc = thisBooster.get(i);
final String cnBk = cc.getName() + "|" + cc.getEdition();
if (!ForgePreferences.UPLOAD_DRAFT) {
return;
}
float pickValue;
if (cc.equals(c)) {
pickValue = thisBooster.size()
* (1f - (((float) this.currentBoosterPick / this.currentBoosterSize) * 2f));
} else {
pickValue = 0;
}
for (int i = 0; i < thisBooster.size(); i++) {
final PaperCard cc = thisBooster.get(i);
final String cnBk = cc.getName() + "|" + cc.getEdition();
if (!this.draftPicks.containsKey(cnBk)) {
this.draftPicks.put(cnBk, pickValue);
} else {
final float curValue = this.draftPicks.get(cnBk);
final float newValue = (curValue + pickValue) / 2;
this.draftPicks.put(cnBk, newValue);
}
float pickValue;
if (cc.equals(c)) {
pickValue = thisBooster.size()
* (1f - (((float) this.currentBoosterPick / this.currentBoosterSize) * 2f));
} else {
pickValue = 0;
}
if (!this.draftPicks.containsKey(cnBk)) {
this.draftPicks.put(cnBk, pickValue);
} else {
final float curValue = this.draftPicks.get(cnBk);
final float newValue = (curValue + pickValue) / 2;
this.draftPicks.put(cnBk, newValue);
}
}
}

View File

@@ -213,7 +213,7 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase {
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.FILTERED_HANDS)){
baseLandParameter--;
}
landsNeeded = new Double((baseLandParameter + 3.14f * avCMC) * targetSize/60f).intValue();
landsNeeded = Double.valueOf((baseLandParameter + 3.14f * avCMC) * targetSize/60f).intValue();
if (logToConsole) {
System.out.println("Required lands from linear regression : " + avCMC + " cmc, needed: " + landsNeeded);
}
@@ -425,8 +425,6 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase {
@Override
public boolean apply(CardRules subject) {
ManaCost mc = subject.getManaCost();
boolean generic = mc.isPureGeneric();
return ((allowedColor.containsAllColorsFrom(subject.getColorIdentity().getColor())));
}
}

View File

@@ -51,8 +51,8 @@ public class LimitedPlayerAI extends LimitedPlayer {
return bestPick;
}
public Deck buildDeck() {
public Deck buildDeck(String landSetCode) {
CardPool section = deck.getOrCreate(DeckSection.Sideboard);
return new BoosterDeckBuilder(section.toFlatList(), deckCols).buildDeck();
return new BoosterDeckBuilder(section.toFlatList(), deckCols).buildDeck(landSetCode);
}
}

View File

@@ -0,0 +1,218 @@
package forge.limited;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import forge.card.CardEdition;
import forge.game.GameFormat;
import forge.model.FModel;
import forge.util.TextUtil;
import forge.util.storage.StorageReaderFile;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
/**
* Themed chaos draft allows limiting the pool of available random boosters for a draft to a certain theme.
*/
public class ThemedChaosDraft implements Comparable<ThemedChaosDraft> {
private final String tag;
private final String label;
private final int orderNumber;
/**
* @param tag Tag name used in edition files.
* @param label Label used in user interface.
* @param orderNumber Number used to order entries in user interface.
*/
public ThemedChaosDraft(String tag, String label, int orderNumber) {
this.tag = tag;
this.label = label;
this.orderNumber = orderNumber;
}
/**
* @return theme tag
*/
public String getTag() { return tag; }
/**
* @return theme label
*/
public String getLabel() { return label; }
/**
* @return theme order number
*/
public int getOrderNumber() { return orderNumber; }
/**
* @return Predicate to sort out editions not belonging to the chaos draft theme
*/
public Predicate<CardEdition> getEditionFilter() {
Predicate<CardEdition> filter;
switch(tag) {
case "DEFAULT":
filter = DEFAULT_FILTER;
break;
case "MODERN":
case "PIONEER":
case "STANDARD":
filter = getFormatFilter(tag);
break;
default:
filter = themedFilter;
}
return filter;
}
/**
* Filter to select editions by ChaosDraftThemes tag defined in edition files.
* Tag must be defined in res/blockdata/chaosdraftthemes.txt
*/
private final Predicate<CardEdition> themedFilter = new Predicate<CardEdition>() {
@Override
public boolean apply(final CardEdition cardEdition) {
String[] themes = cardEdition.getChaosDraftThemes();
for (String theme : themes) {
if (tag.equals(theme)) return true;
}
return false;
}
};
/**
* @param formatName format to filter by, currently supported: MODERN, PIONEER, STANDARD
* @return Filter to select editions belonging to a certain constructed format.
*/
private Predicate<CardEdition> getFormatFilter(String formatName) {
GameFormat.Collection formats = FModel.getFormats();
GameFormat format;
switch(formatName) {
case "MODERN":
format = formats.getModern();
break;
case "PIONEER":
format = formats.getPioneer();
break;
case "STANDARD":
default:
format = formats.getStandard();
}
return new Predicate<CardEdition>() {
@Override
public boolean apply(final CardEdition cardEdition){
return DEFAULT_FILTER.apply(cardEdition) && format.isSetLegal(cardEdition.getCode());
}
};
}
/**
* Default filter that only allows actual sets that were printed as 15-card boosters
*/
private static final Predicate<CardEdition> DEFAULT_FILTER = new Predicate<CardEdition>() {
@Override
public boolean apply(final CardEdition cardEdition) {
boolean isExpansion = cardEdition.getType().equals(CardEdition.Type.EXPANSION);
boolean isCoreSet = cardEdition.getType().equals(CardEdition.Type.CORE);
boolean isReprintSet = cardEdition.getType().equals(CardEdition.Type.REPRINT);
if (isExpansion || isCoreSet || isReprintSet) {
// Only allow sets with 15 cards in booster packs
if (cardEdition.hasBoosterTemplate()) {
final List<Pair<String, Integer>> slots = cardEdition.getBoosterTemplate().getSlots();
int boosterSize = 0;
for (Pair<String, Integer> slot : slots) {
boosterSize += slot.getRight();
}
return boosterSize == 15;
}
}
return false;
}
};
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.label;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + ((this.tag == null) ? 0 : this.tag.hashCode());
result = (prime * result) + ((this.label == null) ? 0 : this.label.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ThemedChaosDraft other) {
return (this.orderNumber != other.orderNumber)
? this.orderNumber - other.orderNumber
: this.label.compareTo(other.label);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final ThemedChaosDraft other = (ThemedChaosDraft) obj;
if (!this.label.equals(other.label)) {
return false;
}
if (!this.tag.equals(other.tag)) {
return false;
}
return true;
}
public static final Function<ThemedChaosDraft, String> FN_GET_TAG = new Function<ThemedChaosDraft, String>() {
@Override
public String apply(ThemedChaosDraft themedChaosBooster) {
return themedChaosBooster.getTag();
}
};
public static class Reader extends StorageReaderFile<ThemedChaosDraft> {
public Reader(String pathname) {
super(pathname, ThemedChaosDraft.FN_GET_TAG);
}
@Override
protected ThemedChaosDraft read(String line, int idx) {
final String[] sParts = TextUtil.splitWithParenthesis(line, ',', 3);
int orderNumber = Integer.parseInt(sParts[0].trim(), 10);
String tag = sParts[1].trim();
String label = sParts[2].trim();
return new ThemedChaosDraft(tag, label, orderNumber);
}
}
}

View File

@@ -9,8 +9,6 @@ import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import forge.GuiBase;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -20,8 +18,10 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.FThreads;
import forge.GuiBase;
import forge.assets.FSkinProp;
import forge.game.GameView;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.event.GameEventSpellAbilityCast;
@@ -31,9 +31,11 @@ import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.interfaces.IMayViewCards;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.trackable.TrackableTypes;
import forge.util.Localizer;
public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
private PlayerView currentPlayer = null;
@@ -41,6 +43,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
private final Map<PlayerView, IGameController> gameControllers = Maps.newHashMap();
private final Map<PlayerView, IGameController> originalGameControllers = Maps.newHashMap();
private boolean gamePause = false;
private boolean ignoreConcedeChain = false;
public final boolean hasLocalPlayers() {
return !gameControllers.isEmpty();
@@ -182,8 +185,16 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
return true; //if not in game, card can be shown
}
if(GuiBase.getInterface().isLibgdxPort()){
if(gameView != null && gameView.isGameOver()) {
return true;
}
if(spectator!=null) { //workaround fix!! this is needed on above code or it will
gameControllers.remove(spectator); //bug the UI! remove spectator here since its must not be here...
for (Map.Entry<PlayerView, IGameController> e : gameControllers.entrySet()) {
if (e.getValue().equals(spectator)) {
gameControllers.remove(e.getKey());
break;
}
}
return true;
}
try{
@@ -217,7 +228,13 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
case Flipped:
case Transformed:
case Meld:
case Modal:
return true;
case Adventure:
if (cv.isFaceDown()) {
return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers());
}
return false;
default:
return false;
}
@@ -266,6 +283,16 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
}
public boolean isGamePaused() { return gamePause; }
public void setgamePause(boolean pause) { gamePause = pause; }
public void pauseMatch() {
IGameController controller = spectator;
if(controller != null && !isGamePaused())
controller.selectButtonOk();
}
public void resumeMatch() {
IGameController controller = spectator;
if(controller != null && isGamePaused())
controller.selectButtonOk();
}
/** Concede game, bring up WinLose UI. */
public boolean concede() {
@@ -273,23 +300,42 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
return true;
}
if (hasLocalPlayers()) {
if (showConfirmDialog(Localizer.getInstance().getMessage("lblConcedeCurrentGame"), Localizer.getInstance().getMessage("lblConcedeTitle"), Localizer.getInstance().getMessage("lblConcede"), Localizer.getInstance().getMessage("lblCancel"))) {
for (final IGameController c : getOriginalGameControllers()) {
// Concede each player on this Gui (except mind-controlled players)
c.concede();
boolean concedeNeeded = false;
// check if anyone still needs to confirm
for (final IGameController c : getOriginalGameControllers()) {
if (c instanceof PlayerControllerHuman) {
if (((PlayerControllerHuman) c).getPlayer().getOutcome() == null) {
concedeNeeded = true;
}
}
} else {
return false;
}
if (concedeNeeded) {
if (showConfirmDialog(Localizer.getInstance().getMessage("lblConcedeCurrentGame"), Localizer.getInstance().getMessage("lblConcedeTitle"), Localizer.getInstance().getMessage("lblConcede"), Localizer.getInstance().getMessage("lblCancel"))) {
for (final IGameController c : getOriginalGameControllers()) {
// Concede each player on this Gui (except mind-controlled players)
c.concede();
}
}
else {
return false;
}
}
else {
return !ignoreConcedeChain;
}
if (gameView.isGameOver()) {
// Don't immediately close, wait for win/lose screen
return false;
} else {
for (PlayerView player : getLocalPlayers()){
if (!player.isAI()){
}
else {
// since the nextGameDecision might come from somewhere else it will try and concede too
ignoreConcedeChain = true;
for (PlayerView player : getLocalPlayers()) {
if (!player.isAI()) {
getGameController(player).nextGameDecision(NextGameDecision.QUIT);
}
}
ignoreConcedeChain = false;
return false;
}
}
@@ -352,11 +398,14 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
return autoPassUntilEndOfTurn.contains(player);
}
private final Timer awaitNextInputTimer = new Timer();
private Timer awaitNextInputTimer;
private TimerTask awaitNextInputTask;
@Override
public final void awaitNextInput() {
if (awaitNextInputTimer == null) {
awaitNextInputTimer = new Timer("awaitNextInputTimer Game:" + this.gameView.getId() + " Player:" + this.currentPlayer.getLobbyPlayerName());
}
//delay updating prompt to await next input briefly so buttons don't flicker disabled then enabled
awaitNextInputTask = new TimerTask() {
@Override
@@ -384,6 +433,9 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
@Override
public final void cancelAwaitNextInput() {
if (awaitNextInputTimer == null) {
return;
}
synchronized (awaitNextInputTimer) { //ensure task doesn't reset awaitNextInputTask during this block
if (awaitNextInputTask != null) {
try {
@@ -569,7 +621,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
for (int i = min; i <= cutoff; i++) {
choices.add(Integer.valueOf(i));
}
choices.add("...");
choices.add(Localizer.getInstance().getMessage("lblOtherInteger"));
final Object choice = oneOrNone(message, choices.build());
if (choice instanceof Integer || choice == null) {
@@ -581,12 +633,12 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
String prompt = "";
if (min != Integer.MIN_VALUE) {
if (max != Integer.MAX_VALUE) {
prompt = localizer.getMessage("lblEnterNumberBetweenMinAndMax").replace("%min", String.valueOf(min)).replace("%max", String.valueOf(max));
prompt = localizer.getMessage("lblEnterNumberBetweenMinAndMax", String.valueOf(min), String.valueOf(max));
} else {
prompt = localizer.getMessage("lblEnterNumberGreaterThanOrEqualsToMin").replace("%min", String.valueOf(min));
prompt = localizer.getMessage("lblEnterNumberGreaterThanOrEqualsToMin", String.valueOf(min));
}
} else if (max != Integer.MAX_VALUE) {
prompt = localizer.getMessage("lblEnterNumberLessThanOrEqualsToMax").replace("%max", String.valueOf(max));
prompt = localizer.getMessage("lblEnterNumberLessThanOrEqualsToMax", String.valueOf(max));
}
while (true) {
@@ -615,6 +667,9 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
@Override
public <T> List<T> many(final String title, final String topCaption, final int min, final int max, final List<T> sourceChoices, final CardView c) {
if (max == 1) {
return getChoices(title, min, max, sourceChoices);
}
final int m2 = min >= 0 ? sourceChoices.size() - min : -1;
final int m1 = max >= 0 ? sourceChoices.size() - max : -1;
return order(title, topCaption, m1, m2, sourceChoices, null, c, false);
@@ -705,5 +760,17 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
public void notifyStackRemoval(GameEventSpellRemovedFromStack event) {
}
@Override
public void handleLandPlayed(Card land) {
}
@Override
public void afterGameEnd() {
if (awaitNextInputTimer != null) {
awaitNextInputTimer.cancel();
awaitNextInputTimer = null;
}
}
// End of Choice code
}

View File

@@ -10,7 +10,6 @@ import java.util.Map;
import java.util.Set;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
@@ -37,6 +36,7 @@ import forge.model.FModel;
import forge.net.event.UpdateLobbyPlayerEvent;
import forge.player.GamePlayerUtil;
import forge.properties.ForgePreferences.FPref;
import forge.util.Localizer;
import forge.util.NameGenerator;
import forge.util.gui.SOptionPane;
@@ -353,27 +353,27 @@ public abstract class GameLobby implements IHasGameType {
}
if (activeSlots.size() < 2) {
SOptionPane.showMessageDialog("At least two players are required to start a game.");
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblRequiredLeastTwoPlayerStartGame"));
return null;
}
if (!isEnoughTeams()) {
SOptionPane.showMessageDialog("There are not enough teams! Please adjust team allocations.");
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNotEnoughTeams"));
return null;
}
for (final LobbySlot slot : activeSlots) {
if (!slot.isReady() && slot.getType() != LobbySlotType.OPEN) {
SOptionPane.showMessageDialog(TextUtil.concatNoSpace("Player ", slot.getName(), " is not ready"));
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerIsNotReady", slot.getName()));
return null;
}
if (slot.getDeck() == null) {
SOptionPane.showMessageDialog(TextUtil.concatNoSpace("Please specify a deck for ", slot.getName()));
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPleaseSpecifyPlayerDeck", slot.getName()));
return null;
}
if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) {
if (!slot.getDeck().has(DeckSection.Commander)) {
SOptionPane.showMessageDialog(TextUtil.concatNoSpace(slot.getName(), " doesn't have a commander"));
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerDoesntHaveCommander", slot.getName()));
return null;
}
}
@@ -410,7 +410,7 @@ public abstract class GameLobby implements IHasGameType {
final String name = slot.getName();
final String errMsg = GameType.Constructed.getDeckFormat().getDeckConformanceProblem(slot.getDeck());
if (null != errMsg) {
SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Deck");
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblPlayerDeckError", name, errMsg), Localizer.getInstance().getMessage("lblInvalidDeck"));
return null;
}
}
@@ -460,7 +460,7 @@ public abstract class GameLobby implements IHasGameType {
if (checkLegality) {
final String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck);
if (errMsg != null) {
SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid " + commanderGameType + " Deck");
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblPlayerDeckError", name, errMsg), Localizer.getInstance().getMessage("lblInvalidCommanderGameTypeDeck", commanderGameType));
return null;
}
}
@@ -491,7 +491,7 @@ public abstract class GameLobby implements IHasGameType {
if (checkLegality) {
final String errMsg = DeckFormat.getSchemeSectionConformanceProblem(schemePool);
if (null != errMsg) {
SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Scheme Deck");
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblPlayerDeckError", name, errMsg), Localizer.getInstance().getMessage("lblInvalidSchemeDeck"));
return null;
}
}
@@ -504,7 +504,7 @@ public abstract class GameLobby implements IHasGameType {
if (checkLegality) {
final String errMsg = DeckFormat.getPlaneSectionConformanceProblem(planePool);
if (null != errMsg) {
SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Planar Deck");
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblPlayerDeckError", name, errMsg), Localizer.getInstance().getMessage("lblInvalidPlanarDeck"));
return null;
}
}
@@ -514,8 +514,7 @@ public abstract class GameLobby implements IHasGameType {
//Vanguard
if (variantTypes.contains(GameType.Vanguard)) {
if (avatarPool == null || avatarPool.countAll() == 0) { //ERROR! null if avatar deselected on list
SOptionPane.showMessageDialog("No Vanguard avatar selected for " + name
+ ". Please choose one or disable the Vanguard variant");
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNoSelectedVanguardAvatarForPlayer", name));
return null;
}
}

View File

@@ -34,6 +34,7 @@ import forge.game.GameRules;
import forge.game.GameType;
import forge.game.GameView;
import forge.game.Match;
import forge.game.event.*;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.player.RegisteredPlayer;
@@ -68,6 +69,7 @@ public class HostedMatch {
private final MatchUiEventVisitor visitor = new MatchUiEventVisitor();
private final Map<PlayerControllerHuman, NextGameDecision> nextGameDecisions = Maps.newHashMap();
private boolean isMatchOver = false;
public int subGameCount = 0;
public HostedMatch() {}
@@ -126,6 +128,8 @@ public class HostedMatch {
title = TextUtil.concatNoSpace("Multiplayer Game (", String.valueOf(sortedPlayers.size()), " players)");
}
this.match = new Match(gameRules, sortedPlayers, title);
this.match.subscribeToEvents(SoundSystem.instance);
this.match.subscribeToEvents(visitor);
startGame();
}
@@ -136,8 +140,7 @@ public class HostedMatch {
public void restartMatch() {
endCurrentGame();
this.match = new Match(match.getRules(), match.getPlayers(), this.title);
startGame();
startMatch(match.getRules(), null, match.getPlayers(), this.guis);
}
public void startGame() {
@@ -149,7 +152,7 @@ public class HostedMatch {
if (game.getRules().getGameType() == GameType.Quest) {
final QuestController qc = FModel.getQuest();
// Reset new list when the Match round starts, not when each game starts
if (game.getMatch().getPlayedGames().isEmpty()) {
if (game.getMatch().getOutcomes().isEmpty()) {
qc.getCards().resetNewList();
}
game.subscribeToEvents(qc); // this one listens to player's mulligans ATM
@@ -295,6 +298,7 @@ public class HostedMatch {
public void endCurrentGame() {
if (game == null) { return; }
boolean isMatchOver = game.getView().isMatchOver();
game = null;
@@ -304,7 +308,10 @@ public class HostedMatch {
humanController.getGui().clearAutoYields();
}
humanController.getGui().afterGameEnd();
if (humanCount > 0) //conceded
humanController.getGui().afterGameEnd();
else if (!GuiBase.getInterface().isLibgdxPort()||!isMatchOver)
humanController.getGui().afterGameEnd();
}
humanControllers.clear();
}
@@ -327,7 +334,7 @@ public class HostedMatch {
return isMatchOver;
}
private final class MatchUiEventVisitor implements IUiEventVisitor<Void> {
private final class MatchUiEventVisitor extends IGameEventVisitor.Base<Void> implements IUiEventVisitor<Void> {
@Override
public Void visit(final UiEventBlockerAssigned event) {
for (final PlayerControllerHuman humanController : humanControllers) {
@@ -354,6 +361,77 @@ public class HostedMatch {
return null;
}
@Override
public Void visit(final GameEventSubgameStart event) {
subGameCount++;
event.subgame.subscribeToEvents(SoundSystem.instance);
event.subgame.subscribeToEvents(visitor);
final GameView gameView = event.subgame.getView();
Runnable switchGameView = new Runnable() {
@Override
public void run() {
for (final Player p : event.subgame.getPlayers()) {
if (p.getController() instanceof PlayerControllerHuman) {
final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController();
final IGuiGame gui = guis.get(p.getRegisteredPlayer());
humanController.setGui(gui);
gui.setGameView(null);
gui.setGameView(gameView);
gui.setOriginalGameController(p.getView(), humanController);
gui.openView(new TrackableCollection<>(p.getView()));
gui.setGameView(null);
gui.setGameView(gameView);
event.subgame.subscribeToEvents(new FControlGameEventHandler(humanController));
gui.message(event.message);
}
}
}
};
if (GuiBase.getInterface().isLibgdxPort())
GuiBase.getInterface().invokeInEdtNow(switchGameView);
else
GuiBase.getInterface().invokeInEdtAndWait(switchGameView);
//ensure opponents set properly
for (final Player p : event.subgame.getPlayers()) {
p.updateOpponentsForView();
}
return null;
}
@Override
public Void visit(final GameEventSubgameEnd event) {
final GameView gameView = event.maingame.getView();
Runnable switchGameView = new Runnable() {
@Override
public void run() {
for (final Player p : event.maingame.getPlayers()) {
if (p.getController() instanceof PlayerControllerHuman) {
final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController();
final IGuiGame gui = guis.get(p.getRegisteredPlayer());
gui.setGameView(null);
gui.setGameView(gameView);
gui.setOriginalGameController(p.getView(), humanController);
gui.openView(new TrackableCollection<>(p.getView()));
gui.setGameView(null);
gui.setGameView(gameView);
gui.updatePhase();
gui.message(event.message);
}
}
}
};
if (GuiBase.getInterface().isLibgdxPort())
GuiBase.getInterface().invokeInEdtNow(switchGameView);
else
GuiBase.getInterface().invokeInEdtAndWait(switchGameView);
return null;
}
@Subscribe
public void receiveEvent(final UiEvent evt) {
try {
@@ -363,6 +441,16 @@ public class HostedMatch {
e.printStackTrace();
}
}
@Subscribe
public void receiveGameEvent(final GameEvent evt) {
try {
evt.visit(this);
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
private void addNextGameDecision(final PlayerControllerHuman controller, final NextGameDecision decision) {

View File

@@ -22,6 +22,10 @@ public final class LobbySlot implements Serializable {
private boolean isDevMode;
private Deck deck;
private ImmutableSet<AIOption> aiOptions;
private String AvatarVanguard;
private String SchemeDeckName;
private String PlanarDeckName;
private String DeckName;
public LobbySlot(final LobbySlotType type, final String name, final int avatarIndex, final int sleeveIndex, final int team, final boolean isArchenemy, final boolean isReady, final Set<AIOption> aiOptions) {
this.type = type;
@@ -79,6 +83,22 @@ public final class LobbySlot implements Serializable {
} else if (oldDeck != null && data.getSection() != null && data.getCards() != null) {
oldDeck.putSection(data.getSection(), data.getCards());
}
if (data.getSchemeDeckName() != null) {
setSchemeDeckName(data.getSchemeDeckName());
changed = true;
}
if (data.getAvatarVanguard() != null) {
setAvatarVanguard(data.getAvatarVanguard());
changed = true;
}
if (data.getPlanarDeckName() != null) {
setPlanarDeckName(data.getPlanarDeckName());
changed = true;
}
if (data.getDeckName() != null) {
setDeckName(data.getDeckName());
changed = true;
}
return changed;
}
@@ -116,6 +136,16 @@ public final class LobbySlot implements Serializable {
this.team = team;
}
public String getSchemeDeckName() { return SchemeDeckName; }
public String getAvatarVanguard() { return AvatarVanguard; }
public String getPlanarDeckName() { return PlanarDeckName; }
public String getDeckName() { return DeckName; }
public void setSchemeDeckName(String schemeDeckName) { this.SchemeDeckName = schemeDeckName; }
public void setAvatarVanguard(String avatarVanguard) { this.AvatarVanguard = avatarVanguard; }
public void setPlanarDeckName(String planarDeckName) { this.PlanarDeckName = planarDeckName; }
public void setDeckName(String DeckName) { this.DeckName = DeckName; }
public boolean isArchenemy() {
return isArchenemy;
}

View File

@@ -26,6 +26,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.event.GameEventCombatUpdate;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerView;
@@ -268,7 +269,7 @@ public class InputAttack extends InputSyncronizedBase {
combat.addAttacker(card, currentDefender, activeBand);
activateBand(activeBand);
card.getGame().fireEvent(new UiEventAttackerDeclared(
card.getGame().getMatch().fireEvent(new UiEventAttackerDeclared(
CardView.get(card),
GameEntityView.get(currentDefender)));
}
@@ -279,7 +280,7 @@ public class InputAttack extends InputSyncronizedBase {
// When removing an attacker clear the attacking band
activateBand(null);
card.getGame().fireEvent(new UiEventAttackerDeclared(
card.getGame().getMatch().fireEvent(new UiEventAttackerDeclared(
CardView.get(card), null));
return true;
}
@@ -327,7 +328,7 @@ public class InputAttack extends InputSyncronizedBase {
private void updateMessage() {
Localizer localizer = Localizer.getInstance();
String message = localizer.getMessage("lblSelectAttackCreatures") + currentDefender + localizer.getMessage("lblSelectAttackTarget");
String message = localizer.getMessage("lblSelectAttackCreatures") + " " + currentDefender + " " + localizer.getMessage("lblSelectAttackTarget");
if (potentialBanding) {
message += localizer.getMessage("lblSelectBandingTarget");
}
@@ -335,6 +336,9 @@ public class InputAttack extends InputSyncronizedBase {
updatePrompt();
if (combat != null)
getController().getGame().fireEvent(new GameEventCombatUpdate(combat.getAttackers(), combat.getAllBlockers()));
getController().getGui().showCombat(); // redraw sword icons
}
}

View File

@@ -26,6 +26,7 @@ import forge.game.card.CardView;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventCombatUpdate;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.player.PlayerControllerHuman;
@@ -85,10 +86,13 @@ public class InputBlock extends InputSyncronizedBase {
}
else {
String attackerName = currentAttacker.isFaceDown() ? localizer.getMessage("lblMorph") : currentAttacker.getName() + " (" + currentAttacker.getId() + ")";
String message = localizer.getMessage("lblSelectBlocker") + attackerName + localizer.getMessage("lblOrSelectBlockTarget");
String message = localizer.getMessage("lblSelectBlocker") + attackerName + " " + localizer.getMessage("lblOrSelectBlockTarget");
showMessage(message);
}
if (combat != null)
getController().getGame().fireEvent(new GameEventCombatUpdate(combat.getAttackers(), combat.getAllBlockers()));
getController().getGui().showCombat();
}
@@ -118,7 +122,7 @@ public class InputBlock extends InputSyncronizedBase {
boolean isCorrectAction = false;
if (triggerEvent != null && triggerEvent.getButton() == 3 && card.getController() == defender) {
combat.removeFromCombat(card);
card.getGame().fireEvent(new UiEventBlockerAssigned(CardView.get(card), null));
card.getGame().getMatch().fireEvent(new UiEventBlockerAssigned(CardView.get(card), null));
isCorrectAction = true;
}
else {
@@ -133,14 +137,14 @@ public class InputBlock extends InputSyncronizedBase {
if (combat.isBlocking(card, currentAttacker)) {
//if creature already blocking current attacker, remove blocker from combat
combat.removeBlockAssignment(currentAttacker, card);
card.getGame().fireEvent(new UiEventBlockerAssigned(CardView.get(card), null));
card.getGame().getMatch().fireEvent(new UiEventBlockerAssigned(CardView.get(card), null));
isCorrectAction = true;
}
else {
isCorrectAction = CombatUtil.canBlock(currentAttacker, card, combat);
if (isCorrectAction) {
combat.addBlocker(currentAttacker, card);
card.getGame().fireEvent(new UiEventBlockerAssigned(
card.getGame().getMatch().fireEvent(new UiEventBlockerAssigned(
CardView.get(card),
CardView.get(currentAttacker)));
}

View File

@@ -67,7 +67,7 @@ public class InputConfirmMulligan extends InputSyncronizedBase {
}
else {
sb.append(startingPlayer.getName()).append(" ").append(localizer.getMessage("lblIsGoingFirst")).append(".\n");
sb.append(player).append(", ").append(localizer.getMessage("lblYouAreGoing")).append(" ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n\n");
sb.append(player).append(", ").append(localizer.getMessage("lblYouAreGoing")).append(" ").append(Lang.getInstance().getOrdinal(game.getPosition(player, startingPlayer))).append(".\n\n");
}
getController().getGui().updateButtons(getOwner(), localizer.getMessage("lblKeep"), localizer.getMessage("lblMulligan"), true, true, true);

View File

@@ -4,7 +4,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import forge.FThreads;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.player.PlayerView;
@@ -17,10 +16,8 @@ public class InputLockUI implements Input {
private final AtomicInteger iCall = new AtomicInteger();
private final InputQueue inputQueue;
private final Game game;
private final PlayerControllerHuman controller;
public InputLockUI(final Game game0, final InputQueue inputQueue0, final PlayerControllerHuman controller) {
game = game0;
public InputLockUI(final InputQueue inputQueue0, final PlayerControllerHuman controller) {
inputQueue = inputQueue0;
this.controller = controller;
}
@@ -90,7 +87,7 @@ public class InputLockUI implements Input {
@Override
public void selectButtonCancel() {
//cancel auto pass for all players
for (final Player player : game.getPlayers()) {
for (final Player player : controller.getGame().getPlayers()) {
player.getController().autoPassCancel();
}
}

View File

@@ -54,6 +54,7 @@ public class InputLondonMulligan extends InputSyncronizedBase {
public final void showMessage() {
final Localizer localizer = Localizer.getInstance();
final Game game = player.getGame();
game.getView().updateIsMulligan(true);
int cardsLeft = toReturn - selected.size();
StringBuilder sb = new StringBuilder();
@@ -79,6 +80,7 @@ public class InputLondonMulligan extends InputSyncronizedBase {
private void done() {
resetCardHighlights();
getController().getGame().getView().updateIsMulligan(false);
stop();
}

View File

@@ -3,12 +3,12 @@ package forge.match.input;
import java.util.*;
import forge.GuiBase;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.spellability.SpellAbilityView;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
import forge.FThreads;
import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi;
@@ -16,14 +16,11 @@ import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.GameActionUtil;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
import forge.player.HumanPlay;
@@ -75,7 +72,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
// Mobile Forge allows to tap cards underneath the current card even if the current one is tapped
if (otherCardsToSelect != null) {
for (Card c : otherCardsToSelect) {
for (SpellAbility sa : c.getManaAbilities()) {
for (SpellAbility sa : getAllManaAbilities(c)) {
if (sa.canPlay()) {
delaySelectCards.add(c);
break;
@@ -83,33 +80,48 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
}
}
if (!card.getManaAbilities().isEmpty() && activateManaAbility(card)) {
if (!getAllManaAbilities(card).isEmpty() && activateManaAbility(card)) {
return true;
}
return activateDelayedCard();
} else {
List<SpellAbility> manaAbilities = getAllManaAbilities(card);
// Desktop Forge floating menu functionality
if (card.getManaAbilities().size() == 1) {
activateManaAbility(card, card.getManaAbilities().get(0));
if (manaAbilities.size() == 1) {
activateManaAbility(card, manaAbilities.get(0));
} else {
SpellAbilityView spellAbilityView;
HashMap<SpellAbilityView, SpellAbility> spellAbilityViewMap = new HashMap<>();
for (SpellAbility sa : card.getManaAbilities()) {
spellAbilityViewMap.put(sa.getView(), sa);
}
List<SpellAbilityView> choices = new ArrayList<>(spellAbilityViewMap.keySet());
spellAbilityView = getController().getGui().getAbilityToPlay(card.getView(), choices, triggerEvent);
if (spellAbilityView != null) {
activateManaAbility(card, spellAbilityViewMap.get(spellAbilityView));
SpellAbility spellAbility = getController().getAbilityToPlay(card, manaAbilities, triggerEvent);
if (spellAbility != null) {
activateManaAbility(card, spellAbility);
}
}
return true;
}
}
protected List<SpellAbility> getAllManaAbilities(Card card) {
List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : card.getManaAbilities()) {
result.add(sa);
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
for (final SpellAbility sa : result) {
sa.setActivatingPlayer(player);
// fix things like retrace
// check only if SA can't be cast normally
if (sa.canPlay(true)) {
continue;
}
toRemove.add(sa);
}
result.removeAll(toRemove);
return result;
}
@Override
public String getActivateAction(Card card) {
for (SpellAbility sa : card.getManaAbilities()) {
for (SpellAbility sa : getAllManaAbilities(card)) {
if (sa.canPlay()) {
return "pay mana with card";
}
@@ -139,6 +151,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return false;
}
@Deprecated
public List<SpellAbility> getUsefulManaAbilities(Card card) {
List<SpellAbility> abilities = new ArrayList<>();
@@ -155,7 +168,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, player.getManaPool())) {
colorCanUse |= ManaAtom.GENERIC;
}
if (colorCanUse == 0) { // no mana cost or something
if (colorCanUse == 0) { // no mana cost or something
return abilities;
}
@@ -164,15 +177,10 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return abilities;
}
for (SpellAbility ma : card.getManaAbilities()) {
for (SpellAbility ma : getAllManaAbilities(card)) {
ma.setActivatingPlayer(player);
AbilityManaPart m = ma.getManaPartRecursive();
if (m == null || !ma.canPlay()) { continue; }
if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; }
if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; }
if (!m.meetsManaRestrictions(saPaidFor)) { continue; }
abilities.add(ma);
if (ma.isManaAbilityFor(saPaidFor, colorCanUse))
abilities.add(ma);
}
return abilities;
}
@@ -193,11 +201,8 @@ public abstract class InputPayMana extends InputSyncronizedBase {
System.err.print("Should wait till previous call to playAbility finishes.");
return false;
}
// make sure computer's lands aren't selected
if (card.getController() != player) {
return false;
}
byte colorCanUse = 0;
byte colorNeeded = 0;
@@ -210,106 +215,96 @@ public abstract class InputPayMana extends InputSyncronizedBase {
colorCanUse |= ManaAtom.GENERIC;
}
if (colorCanUse == 0) { // no mana cost or something
if (colorCanUse == 0) { // no mana cost or something
return false;
}
HashMap<SpellAbilityView, SpellAbility> abilitiesMap = new HashMap<>();
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
final String typeRes = manaCost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) {
return false;
}
boolean guessAbilityWithRequiredColors = true;
int amountOfMana = -1;
for (SpellAbility ma : card.getManaAbilities()) {
ma.setActivatingPlayer(player);
AbilityManaPart m = ma.getManaPartRecursive();
if (m == null || !ma.canPlay()) { continue; }
if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; }
if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; }
if (!m.meetsManaRestrictions(saPaidFor)) { continue; }
// If Mana Abilities produce differing amounts of mana, let the player choose
int maAmount = GameActionUtil.amountOfManaGenerated(ma, true);
if (amountOfMana == -1) {
amountOfMana = maAmount;
} else {
if (amountOfMana != maAmount) {
guessAbilityWithRequiredColors = false;
}
}
abilitiesMap.put(ma.getView(), ma);
// skip express mana if the ability is not undoable or reusable
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null) {
guessAbilityWithRequiredColors = false;
}
}
if (abilitiesMap.isEmpty() || (chosenAbility != null && !abilitiesMap.containsKey(chosenAbility.getView()))) {
return false;
}
// Store some information about color costs to help with any mana choices
if (colorNeeded == 0) { // only colorless left
if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) {
String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(",");
for (String negColor : negEffects) {
byte col = ManaAtom.fromName(negColor);
colorCanUse |= col;
}
}
}
// If the card has any ability that tracks mana spent, skip express Mana choice
if (saPaidFor.tracksManaSpent()) {
colorCanUse = ColorSet.ALL_COLORS.getColor();
guessAbilityWithRequiredColors = false;
}
boolean choice = true;
boolean isPayingGeneric = false;
if (guessAbilityWithRequiredColors) {
// express Mana Choice
if (colorNeeded == 0) {
choice = false;
//avoid unnecessary prompt by pretending we need White
//for the sake of "Add one mana of any color" effects
colorNeeded = MagicColor.WHITE;
isPayingGeneric = true; // for further processing
}
else {
final HashMap<SpellAbilityView, SpellAbility> colorMatches = new HashMap<>();
for (SpellAbility sa : abilitiesMap.values()) {
if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) {
colorMatches.put(sa.getView(), sa);
}
}
if (colorMatches.isEmpty()) {
// can only match colorless just grab the first and move on.
// This is wrong. Sometimes all abilities aren't created equal
choice = false;
}
else if (colorMatches.size() < abilitiesMap.size()) {
// leave behind only color matches
abilitiesMap = colorMatches;
}
}
}
// Exceptions for cards that have conditional abilities which are better handled manually
if (card.getName().equals("Cavern of Souls") && isPayingGeneric) {
choice = true;
}
final SpellAbility chosen;
if (chosenAbility == null) {
HashMap<SpellAbilityView, SpellAbility> abilitiesMap = new HashMap<>();
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
final String typeRes = manaCost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) {
return false;
}
boolean guessAbilityWithRequiredColors = true;
int amountOfMana = -1;
for (SpellAbility ma : getAllManaAbilities(card)) {
ma.setActivatingPlayer(player);
if (!ma.isManaAbilityFor(saPaidFor, colorCanUse)) { continue; }
// If Mana Abilities produce differing amounts of mana, let the player choose
int maAmount = ma.totalAmountOfManaGenerated(saPaidFor, true);
if (amountOfMana == -1) {
amountOfMana = maAmount;
} else {
if (amountOfMana != maAmount) {
guessAbilityWithRequiredColors = false;
}
}
abilitiesMap.put(ma.getView(), ma);
// skip express mana if the ability is not undoable or reusable
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null
|| ma.isManaCannotCounter(saPaidFor)) {
guessAbilityWithRequiredColors = false;
}
}
if (abilitiesMap.isEmpty()) {
return false;
}
// Store some information about color costs to help with any mana choices
if (colorNeeded == 0) { // only colorless left
if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) {
String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(",");
for (String negColor : negEffects) {
byte col = ManaAtom.fromName(negColor);
colorCanUse |= col;
}
}
}
// If the card has any ability that tracks mana spent, skip express Mana choice
if (saPaidFor.tracksManaSpent()) {
colorCanUse = ColorSet.ALL_COLORS.getColor();
guessAbilityWithRequiredColors = false;
}
boolean choice = true;
if (guessAbilityWithRequiredColors) {
// express Mana Choice
if (colorNeeded == 0) {
choice = false;
//avoid unnecessary prompt by pretending we need White
//for the sake of "Add one mana of any color" effects
colorNeeded = MagicColor.WHITE;
}
else {
final HashMap<SpellAbilityView, SpellAbility> colorMatches = new HashMap<>();
for (SpellAbility sa : abilitiesMap.values()) {
if (sa.isManaAbilityFor(saPaidFor, colorNeeded)) {
colorMatches.put(sa.getView(), sa);
}
}
if (colorMatches.isEmpty()) {
// can only match colorless just grab the first and move on.
// This is wrong. Sometimes all abilities aren't created equal
choice = false;
}
else if (colorMatches.size() < abilitiesMap.size()) {
// leave behind only color matches
abilitiesMap = colorMatches;
}
}
}
ArrayList<SpellAbilityView> choices = new ArrayList<>(abilitiesMap.keySet());
chosen = abilitiesMap.size() > 1 && choice ? abilitiesMap.get(getController().getGui().one(Localizer.getInstance().getMessage("lblChooseManaAbility"), choices)) : abilitiesMap.get(choices.get(0));
} else {
@@ -321,14 +316,13 @@ public abstract class InputPayMana extends InputSyncronizedBase {
// Filter the colors for the express choice so that only actually producible colors can be chosen
int producedColorMask = 0;
for (final byte color : ManaAtom.MANATYPES) {
if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color))
&& colors.hasAnyColor(color)) {
if (chosen.canProduce(MagicColor.toShortString(color)) && colors.hasAnyColor(color)) {
producedColorMask |= color;
}
}
ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask);
chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors);
chosen.setManaExpressChoice(producedAndNeededColors);
// System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana");
@@ -337,8 +331,19 @@ public abstract class InputPayMana extends InputSyncronizedBase {
@Override
public void run() {
if (HumanPlay.playSpellAbility(getController(), chosen.getActivatingPlayer(), chosen)) {
player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen);
final List<AbilityManaPart> manaAbilities = chosen.getAllManaParts();
boolean restrictionsMet = true;
for (AbilityManaPart sa : manaAbilities) {
if (!sa.meetsManaRestrictions(saPaidFor)) {
restrictionsMet = false;
break;
}
}
if (restrictionsMet) {
player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen);
}
onManaAbilityPaid();
onStateChanged();
}
@@ -348,58 +353,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return true;
}
private static boolean abilityProducesManaColor(final SpellAbility am, AbilityManaPart m, final byte neededColor) {
if (0 != (neededColor & ManaAtom.GENERIC)) {
return true;
}
if (m.isAnyMana()) {
return true;
}
// check for produce mana replacement effects - they mess this up, so just use the mana ability
final Card source = am.getHostCard();
final Player activator = am.getActivatingPlayer();
final Game g = source.getGame();
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
repParams.put(AbilityKey.Mana, m.getOrigProduced());
repParams.put(AbilityKey.Affected, source);
repParams.put(AbilityKey.Player, activator);
repParams.put(AbilityKey.AbilityMana, am);
for (final Player p : g.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect.requirementsCheck(g)
&& replacementEffect.getMode() == ReplacementType.ProduceMana
&& replacementEffect.canReplace(repParams)
&& replacementEffect.hasParam("ManaReplacement")
&& replacementEffect.zonesCheck(g.getZoneOf(crd))) {
return true;
}
}
}
}
if (am.getApi() == ApiType.ManaReflected) {
final Iterable<String> reflectableColors = CardUtil.getReflectableManaColors(am);
for (final String color : reflectableColors) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return true;
}
}
}
else {
String colorsProduced = m.isComboMana() ? m.getComboColors() : m.getOrigProduced();
for (final String color : colorsProduced.split(" ")) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return true;
}
}
}
return false;
}
protected boolean isAlreadyPaid() {
if (manaCost.isPaid()) {
bPaid = true;

View File

@@ -1,161 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package forge.match.input;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent;
import forge.util.Localizer;
//pays the cost of a card played from the player's hand
//the card is removed from the players hand if the cost is paid
//CANNOT be used for ABILITIES
public class InputPayManaSimple extends InputPayMana {
// anything that uses this should be converted to Ability_Cost
/** Constant <code>serialVersionUID=3467312982164195091L</code>. */
private static final long serialVersionUID = 3467312982164195091L;
private final Card originalCard;
private final ManaCost originalManaCost;
public InputPayManaSimple(final PlayerControllerHuman controller, final Game game, final SpellAbility sa, final ManaCostBeingPaid manaCostToPay) {
super(controller, sa, sa.getActivatingPlayer());
this.originalManaCost = manaCostToPay.toManaCost();
this.originalCard = sa.getHostCard();
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
this.manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
game.getStack().add(this.saPaidFor);
}
else {
this.manaCost = manaCostToPay;
}
}
@Override
protected void onManaAbilityPaid() {
if (this.manaCost.isPaid()) {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
}
}
/** {@inheritDoc} */
@Override
protected final void onPlayerSelected(final Player selected, final ITriggerEvent triggerEvent) {
if (player == selected) {
if (player.canPayLife(this.phyLifeToLose + 2)) {
if (manaCost.payPhyrexian()) {
this.phyLifeToLose += 2;
} else {
if (player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK)) {
manaCost.decreaseShard(ManaCostShard.BLACK, 1);
this.phyLifeToLose += 2;
}
}
}
this.showMessage();
}
}
/**
* <p>
* done.
* </p>
*/
@Override
protected void done() {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
if (this.phyLifeToLose > 0) {
player.payLife(this.phyLifeToLose, this.originalCard);
}
if (!this.saPaidFor.getHostCard().isCopiedSpell()) {
if (this.saPaidFor.isSpell()) {
this.saPaidFor.setHostCard(game.getAction().moveToStack(this.originalCard, null));
}
}
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
player.getManaPool().refundManaPaid(this.saPaidFor);
// Update UI
this.stop();
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
if (isFinished()) { return; }
updateButtons();
if (this.manaCost.isPaid() && !new ManaCostBeingPaid(this.originalManaCost).isPaid()) {
this.done();
this.stop();
}
else {
updateMessage();
}
}
/* (non-Javadoc)
* @see forge.control.input.InputPayManaBase#updateMessage()
*/
@Override
protected String getMessage() {
final StringBuilder msg = new StringBuilder();
final Localizer localizer = Localizer.getInstance();
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DETAILED_SPELLDESC_IN_PROMPT)) {
msg.append(saPaidFor.getStackDescription().replace("(Targeting ERROR)", "")).append("\n\n");
}
msg.append(localizer.getMessage("lblPayManaCost")).append(" ").append(this.manaCost.toString(false, player.getManaPool()));
if (this.phyLifeToLose > 0) {
msg.append(" ").append(String.format(localizer.getMessage("lblLifePaidForPhyrexianMana"), this.phyLifeToLose));
}
boolean isLifeInsteadBlack = player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK);
if (manaCost.containsPhyrexianMana() || isLifeInsteadBlack) {
StringBuilder sb = new StringBuilder();
if (manaCost.containsPhyrexianMana() && !isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForPhyrexianMana"));
} else if (!manaCost.containsPhyrexianMana() && isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForBlackMana"));
} else if (manaCost.containsPhyrexianMana() && isLifeInsteadBlack) {
sb.append(localizer.getMessage("lblClickOnYourLifeTotalToPayLifeForPhyrexianOrBlackMana"));
}
msg.append("\n(").append(sb).append(")");
}
// has its own variant of checkIfPaid
return msg.toString();
}
}

View File

@@ -110,7 +110,7 @@ public class InputProxy implements Observer {
}
private Card getCard(final CardView cardView) {
return controller.getGame().getCard(cardView);
return controller.getCard(cardView);
}
public final String getActivateAction(final CardView cardView) {

View File

@@ -21,7 +21,7 @@ import java.util.Observable;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import forge.game.Game;
import forge.game.GameView;
import forge.player.PlayerControllerHuman;
/**
@@ -34,10 +34,10 @@ import forge.player.PlayerControllerHuman;
*/
public class InputQueue extends Observable {
private final BlockingDeque<InputSynchronized> inputStack = new LinkedBlockingDeque<>();
private final Game game;
private final GameView gameView;
public InputQueue(final Game game, final InputProxy inputProxy) {
this.game = game;
public InputQueue(final GameView gameView, final InputProxy inputProxy) {
this.gameView = gameView;
addObserver(inputProxy);
}
@@ -64,10 +64,10 @@ public class InputQueue extends Observable {
public final Input getActualInput(final PlayerControllerHuman controller) {
final Input topMost = inputStack.peek(); // incoming input to Control
if (topMost != null && !game.isGameOver()) {
if (topMost != null && !gameView.isGameOver()) {
return topMost;
}
return new InputLockUI(game, this, controller);
return new InputLockUI(this, controller);
} // getInput()
// only for debug purposes

View File

@@ -1,23 +1,22 @@
package forge.match.input;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import forge.FThreads;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.player.PlayerControllerHuman;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.game.zone.Zone;
import forge.FThreads;
import forge.util.ITriggerEvent;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSelectManyBase<T> {
private static final long serialVersionUID = -6609493252672573139L;
@@ -31,31 +30,32 @@ public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSele
}
public InputSelectEntitiesFromList(final PlayerControllerHuman controller, final int min, final int max, final FCollectionView<T> validChoices0, final SpellAbility sa0) {
super(controller, Math.min(min, validChoices0.size()), Math.min(max, validChoices0.size()),sa0);
super(controller, Math.min(min, validChoices0.size()), Math.min(max, validChoices0.size()), sa0);
validChoices = validChoices0;
if (min > validChoices.size()) { // pfps does this really do anything useful??
System.out.println(String.format("Trying to choose at least %d things from a list with only %d things!", min, validChoices.size()));
}
ArrayList<CardView> vCards = new ArrayList<>();
for ( T c : validChoices0 ) {
if ( c instanceof Card ) {
vCards.add(((Card)c).getView()) ;
}
}
getController().getGui().setSelectables(vCards);
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final GameEntity c : validChoices) {
final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null ;
if ( cz != null ) {
zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(),cz.getZoneType()));
}
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
getController().getGui().updateZones(zonesToUpdate);
zonesShown = getController().getGui().tempShowZones(controller.getPlayer().getView(),zonesToUpdate);
ArrayList<CardView> vCards = new ArrayList<>();
for (T c : validChoices0) {
if (c instanceof Card) {
vCards.add(((Card) c).getView());
}
});
}
getController().getGui().setSelectables(vCards);
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final GameEntity c : validChoices) {
final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null;
if (cz != null) {
zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(), cz.getZoneType()));
}
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
getController().getGui().updateZones(zonesToUpdate);
zonesShown = getController().getGui().tempShowZones(controller.getPlayer().getView(), zonesToUpdate);
}
});
}
@Override

View File

@@ -1,30 +1,35 @@
package forge.match.input;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import forge.FThreads;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.card.CardView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent;
import forge.util.TextUtil;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.FThreads;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.util.Aggregates;
import forge.util.ITriggerEvent;
import forge.util.TextUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public final class InputSelectTargets extends InputSyncronizedBase {
private final List<Card> choices;
@@ -32,31 +37,36 @@ public final class InputSelectTargets extends InputSyncronizedBase {
private final Map<GameEntity, Integer> targetDepth = new HashMap<>();
private final TargetRestrictions tgt;
private final SpellAbility sa;
private final Collection<Integer> divisionValues;
private Card lastTarget = null;
private boolean bCancel = false;
private boolean bOk = false;
private final boolean mandatory;
private Predicate<GameObject> filter;
private static final long serialVersionUID = -1091595663541356356L;
public final boolean hasCancelled() { return bCancel; }
public final boolean hasPressedOk() { return bOk; }
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory) {
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory, Collection<Integer> divisionValues, Predicate<GameObject> filter) {
super(controller);
this.choices = choices;
this.tgt = sa.getTargetRestrictions();
this.sa = sa;
this.mandatory = mandatory;
controller.getGui().setSelectables(CardView.getCollection(choices));
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final Card c : choices) {
zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(),c.getZone().getZoneType()));
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
controller.getGui().updateZones(zonesToUpdate);
this.divisionValues = divisionValues;
this.filter = filter;
controller.getGui().setSelectables(CardView.getCollection(choices));
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final Card c : choices) {
zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(), c.getZone().getZoneType()));
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
controller.getGui().updateZones(zonesToUpdate);
}
});
});
}
@Override
@@ -95,8 +105,8 @@ public final class InputSelectTargets extends InputSyncronizedBase {
sb.append(sa.getUniqueTargets());
}
final int maxTargets = tgt.getMaxTargets(sa.getHostCard(), sa);
final int targeted = sa.getTargets().getNumTargeted();
final int maxTargets = sa.getMaxTargets();
final int targeted = sa.getTargets().size();
if(maxTargets > 1) {
sb.append(TextUtil.concatNoSpace("\n(", String.valueOf(maxTargets - targeted), " more can be targeted)"));
}
@@ -106,10 +116,10 @@ public final class InputSelectTargets extends InputSyncronizedBase {
"(Targeting ERROR)", "");
showMessage(message, sa.getView());
if (tgt.isDividedAsYouChoose() && tgt.getMinTargets(sa.getHostCard(), sa) == 0 && sa.getTargets().getNumTargeted() == 0) {
if (sa.isDividedAsYouChoose() && sa.getMinTargets() == 0 && sa.getTargets().size() == 0) {
// extra logic for Divided with min targets = 0, should only work if num targets are 0 too
getController().getGui().updateButtons(getOwner(), true, true, false);
} else if (!tgt.isMinTargetsChosen(sa.getHostCard(), sa) || tgt.isDividedAsYouChoose()) {
} else if (!sa.isMinTargetChosen() || sa.isDividedAsYouChoose()) {
// If reached Minimum targets, enable OK button
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target
@@ -148,6 +158,9 @@ public final class InputSelectTargets extends InputSyncronizedBase {
return false;
}
// TODO should use sa.canTarget(card) instead?
// it doesn't have messages
//If the card is not a valid target
if (!card.canBeTargetedBy(sa)) {
showMessage(sa.getHostCard() + " - Cannot target this card (Shroud? Protection? Restrictions).");
@@ -170,6 +183,55 @@ public final class InputSelectTargets extends InputSyncronizedBase {
return false;
}
// If the cards share a card type
if (tgt.isWithSameCardType() && lastTarget != null && !card.sharesCardTypeWith(lastTarget)) {
showMessage(sa.getHostCard() + " - Cannot target this card (should share a Card type)");
return false;
}
if (sa.hasParam("MaxTotalTargetCMC")) {
int maxTotalCMC = tgt.getMaxTotalCMC(sa.getHostCard(), sa);
if (maxTotalCMC > 0) {
int soFar = Aggregates.sum(sa.getTargets().getTargetCards(), CardPredicates.Accessors.fnGetCmc);
if (!sa.isTargeting(card)) {
soFar += card.getCMC();
}
if (soFar > maxTotalCMC) {
showMessage(sa.getHostCard() + " - Cannot target this card (CMC limit exceeded)");
return false;
}
}
}
// If all cards must have same controllers
if (tgt.isSameController()) {
final List<Player> targetedControllers = new ArrayList<>();
for (final GameObject o : targetDepth.keySet()) {
if (o instanceof Card) {
final Player p = ((Card) o).getController();
targetedControllers.add(p);
}
}
if (!targetedControllers.isEmpty() && !targetedControllers.contains(card.getController())) {
showMessage(sa.getHostCard() + " - Cannot target this card (must have same controller)");
return false;
}
}
if (sa.hasParam("MaxTotalTargetPower")) {
int maxTotalPower = tgt.getMaxTotalPower(sa.getHostCard(), sa);
if (maxTotalPower > 0) {
int soFar = Aggregates.sum(sa.getTargets().getTargetCards(), CardPredicates.Accessors.fnGetNetPower);
if (!sa.isTargeting(card)) {
soFar += card.getNetPower();
}
if (soFar > maxTotalPower) {
showMessage(sa.getHostCard() + " - Cannot target this card (power limit exceeded)");
return false;
}
}
}
// If all cards must have different controllers
if (tgt.isDifferentControllers()) {
final List<Player> targetedControllers = new ArrayList<>();
@@ -185,50 +247,31 @@ public final class InputSelectTargets extends InputSyncronizedBase {
}
}
// If all cards must have different CMC
if (tgt.isDifferentCMC()) {
final List<Integer> targetedCMCs = new ArrayList<>();
for (final GameObject o : targetDepth.keySet()) {
if (o instanceof Card) {
final Integer cmc = ((Card) o).getCMC();
targetedCMCs.add(cmc);
}
}
if (targetedCMCs.contains(card.getCMC())) {
showMessage(sa.getHostCard() + " - Cannot target this card (must have different CMC)");
return false;
}
}
if (!choices.contains(card)) {
if (card.isPlaneswalker() && sa.getApi() == ApiType.DealDamage) {
showMessage(sa.getHostCard() + " - To deal an opposing Planeswalker direct damage, target its controller and then redirect the damage on resolution.");
}
else {
showMessage(sa.getHostCard() + " - The selected card is not a valid choice to be targeted.");
}
showMessage(sa.getHostCard() + " - The selected card is not a valid choice to be targeted.");
return false;
}
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((sa.getTargets().getNumTargeted() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
for (int i = 1; i <= stillToDivide; i++) {
choices.add(Integer.valueOf(i));
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
}
else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
}
else if (sa.getApi() == ApiType.PutCounter) {
apiBasedMessage = "Select how many counters to distribute to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(card.toString());
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
if (chosen == null) {
return true; //still return true since there was a valid choice
}
allocatedPortion = chosen;
if (sa.isDividedAsYouChoose()) {
Boolean val = onDividedAsYouChoose(card);
if (val != null) {
return val;
}
else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(card, allocatedPortion);
}
addTarget(card);
return true;
@@ -251,16 +294,59 @@ public final class InputSelectTargets extends InputSyncronizedBase {
return;
}
if (player.hasLost()) {
showMessage(sa.getHostCard() + " - Cannot target this player - already lost.");
return;
}
//TODO return the correct reason to display
if (!sa.canTarget(player)) {
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
return;
}
if (filter != null && !filter.apply(player)) {
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
return;
}
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
if (sa.isDividedAsYouChoose()) {
Boolean val = onDividedAsYouChoose(player);
if (val != null) {
return;
}
}
addTarget(player);
}
protected Boolean onDividedAsYouChoose(GameObject go) {
if (divisionValues != null) {
if (divisionValues.isEmpty()) {
return false;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
}
else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
}
else if (sa.getApi() == ApiType.PutCounter) {
apiBasedMessage = "Select how many counters to distribute to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(go.toString());
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), Lists.newArrayList(divisionValues));
if (chosen == null) {
return true; //still return true since there was a valid choice
}
divisionValues.remove(chosen);
sa.addDividedAllocation(go, chosen);
} else {
final int stillToDivide = sa.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((sa.getTargets().getNumTargeted() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
if ((sa.getTargets().size() + 1 < sa.getMaxTargets()) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
for (int i = 1; i <= stillToDivide; i++) {
choices.add(Integer.valueOf(i));
@@ -273,19 +359,18 @@ public final class InputSelectTargets extends InputSyncronizedBase {
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(player.getName());
sb.append(go.toString());
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
if (null == chosen) {
return;
return true;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(player, allocatedPortion);
sa.addDividedAllocation(go, allocatedPortion);
}
addTarget(player);
return null;
}
private void addTarget(final GameEntity ge) {
@@ -316,10 +401,9 @@ public final class InputSelectTargets extends InputSyncronizedBase {
}
private boolean hasAllTargets() {
return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose());
return sa.isMaxTargetChosen() || (sa.isDividedAsYouChoose() && sa.getStillToDivide() == 0);
}
@Override
protected void onStop() {
getController().getGui().clearSelectables();

View File

@@ -41,6 +41,7 @@ public class CardCollections {
private IStorage<Deck> scheme;
private IStorage<Deck> plane;
private IStorage<Deck> commander;
private IStorage<Deck> commanderPrecons;
private IStorage<Deck> oathbreaker;
private IStorage<Deck> tinyLeaders;
private IStorage<Deck> brawl;
@@ -121,6 +122,14 @@ public class CardCollections {
return oathbreaker;
}
public IStorage<Deck> getCommanderPrecons() {
if (commanderPrecons == null) {
commanderPrecons = new StorageImmediatelySerialized<Deck>("Commander Precon decks",
new DeckStorage(new File(ForgeConstants.COMMANDER_PRECON_DIR), ForgeConstants.QUEST_PRECON_DIR));
}
return commanderPrecons;
}
public IStorage<Deck> getTinyLeaders() {
if (tinyLeaders == null) {
tinyLeaders = new StorageImmediatelySerialized<>("Tiny Leaders decks",

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;
@@ -36,6 +37,7 @@ import forge.gauntlet.GauntletData;
import forge.interfaces.IProgressBar;
import forge.itemmanager.ItemManagerConfig;
import forge.limited.GauntletMini;
import forge.limited.ThemedChaosDraft;
import forge.planarconquest.ConquestController;
import forge.planarconquest.ConquestPlane;
import forge.planarconquest.ConquestPreferences;
@@ -50,6 +52,7 @@ import forge.quest.data.QuestPreferences;
import forge.tournament.TournamentData;
import forge.util.CardTranslation;
import forge.util.FileUtil;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.storage.IStorage;
import forge.util.storage.StorageBase;
@@ -90,11 +93,20 @@ public final class FModel {
private static IStorage<CardBlock> blocks;
private static IStorage<CardBlock> fantasyBlocks;
private static IStorage<ThemedChaosDraft> themedChaosDrafts;
private static IStorage<ConquestPlane> planes;
private static IStorage<QuestWorld> worlds;
private static GameFormat.Collection formats;
public static void initialize(final IProgressBar progressBar, Function<ForgePreferences, Void> adjustPrefs) {
//init version to log
System.out.println("Forge v." + GuiBase.getInterface().getCurrentVersion() + " (" + GuiBase.getInterface() + ")");
//Device
if (GuiBase.isAndroid()) //todo get device on other mobile platforms
System.out.println(GuiBase.getDeviceName() + " (RAM: " + GuiBase.getDeviceRAM() + "MB, Android " + GuiBase.getAndroidRelease() + " API Level " + GuiBase.getAndroidAPILevel() + ")");
else
System.out.println(System.getProperty("os.name") + " (" + System.getProperty("os.version") + " " + System.getProperty("os.arch") + ")");
ImageKeys.initializeDirs(
ForgeConstants.CACHE_CARD_PICS_DIR, ForgeConstants.CACHE_CARD_PICS_SUBDIR,
ForgeConstants.CACHE_TOKEN_PICS_DIR, ForgeConstants.CACHE_ICON_PICS_DIR,
@@ -115,9 +127,9 @@ public final class FModel {
throw new RuntimeException(exn);
}
Lang.createInstance(FModel.getPreferences().getPref(FPref.UI_LANGUAGE));
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,11 +155,16 @@ 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,
FModel.getPreferences().getPrefBoolean(FPref.LOAD_CARD_SCRIPTS_LAZILY));
magicDb = new StaticData(reader, tokenReader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR);
magicDb = new StaticData(reader, tokenReader, ForgeConstants.EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR, FModel.getPreferences().getPrefBoolean(FPref.UI_LOAD_UNKNOWN_CARDS));
CardTranslation.preloadTranslation(preferences.getPref(FPref.UI_LANGUAGE), ForgeConstants.LANG_DIR);
//create profile dirs if they don't already exist
@@ -182,6 +199,7 @@ public final class FModel {
questPreferences = new QuestPreferences();
conquestPreferences = new ConquestPreferences();
fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions()));
themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt"));
planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt"));
Map<String, QuestWorld> standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll();
Map<String, QuestWorld> customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll();
@@ -220,8 +238,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);
@@ -380,6 +396,10 @@ public final class FModel {
return fantasyBlocks;
}
public static IStorage<ThemedChaosDraft> getThemedChaosDrafts() {
return themedChaosDrafts;
}
public static TournamentData getTournamentData() { return tournamentData; }
public static void setTournamentData(TournamentData tournamentData) { FModel.tournamentData = tournamentData; }

View File

@@ -0,0 +1,49 @@
package forge.net;
import io.netty.handler.codec.serialization.ClassResolver;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.StreamCorruptedException;
public class CObjectInputStream extends ObjectInputStream {
private final ClassResolver classResolver;
CObjectInputStream(InputStream in, ClassResolver classResolver) throws IOException {
super(in);
this.classResolver = classResolver;
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
int type = read();
if (type < 0) {
throw new EOFException();
} else {
switch(type) {
case 0:
return super.readClassDescriptor();
case 1:
String className = readUTF();
Class<?> clazz = classResolver.resolve(className);
return ObjectStreamClass.lookupAny(clazz);
default:
throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
}
}
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class<?> clazz;
try {
clazz = classResolver.resolve(desc.getName());
} catch (ClassNotFoundException ignored) {
clazz = super.resolveClass(desc);
}
return clazz;
}
}

View File

@@ -0,0 +1,21 @@
package forge.net;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
public class CObjectOutputStream extends ObjectOutputStream {
static final int TYPE_THIN_DESCRIPTOR = 1;
CObjectOutputStream(OutputStream out) throws IOException {
super(out);
}
@Override
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
//we only pass this and the decoder will lookup in the stream (faster method both mobile and desktop)
write(TYPE_THIN_DESCRIPTOR);
writeUTF(desc.getName());
}
}

View File

@@ -0,0 +1,48 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.serialization.ClassResolver;
import net.jpountz.lz4.LZ4BlockInputStream;
import java.io.ObjectInputStream;
import java.io.StreamCorruptedException;
public class CompatibleObjectDecoder extends LengthFieldBasedFrameDecoder {
private final ClassResolver classResolver;
public CompatibleObjectDecoder(ClassResolver classResolver) {
this(1048576, classResolver);
}
public CompatibleObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
super(maxObjectSize, 0, 4, 0, 4);
this.classResolver = classResolver;
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
if (frame == null) {
return null;
} else {
ObjectInputStream ois = GuiBase.hasPropertyConfig() ?
new ObjectInputStream(new LZ4BlockInputStream(new ByteBufInputStream(frame, true))):
new CObjectInputStream(new LZ4BlockInputStream(new ByteBufInputStream(frame, true)),this.classResolver);
Object var5 = null;
try {
var5 = ois.readObject();
} catch (StreamCorruptedException e) {
System.err.println(String.format("Version Mismatch: %s", e.getMessage()));
} finally {
ois.close();
}
return var5;
}
}
}

View File

@@ -0,0 +1,38 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import net.jpountz.lz4.LZ4BlockOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class CompatibleObjectEncoder extends MessageToByteEncoder<Serializable> {
private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
int startIdx = out.writerIndex();
ByteBufOutputStream bout = new ByteBufOutputStream(out);
ObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = GuiBase.hasPropertyConfig() ? new ObjectOutputStream(new LZ4BlockOutputStream(bout)) : new CObjectOutputStream(new LZ4BlockOutputStream(bout));
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
int endIdx = out.writerIndex();
out.setInt(startIdx, endIdx - startIdx - 4);
}
}

View File

@@ -1,58 +0,0 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.serialization.ClassResolver;
import org.mapdb.elsa.ElsaObjectInputStream;
import java.io.ObjectInputStream;
public class CustomObjectDecoder extends LengthFieldBasedFrameDecoder {
private final ClassResolver classResolver;
public CustomObjectDecoder(ClassResolver classResolver) {
this(1048576, classResolver);
}
public CustomObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
super(maxObjectSize, 0, 4, 0, 4);
this.classResolver = classResolver;
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
} else {
if (GuiBase.hasPropertyConfig()){
ElsaObjectInputStream ois = new ElsaObjectInputStream(new ByteBufInputStream(frame, true));
Object var5;
try {
var5 = ois.readObject();
} finally {
ois.close();
}
return var5;
}
else {
ObjectInputStream ois = new ObjectInputStream(new ByteBufInputStream(frame, true));
Object var5;
try {
var5 = ois.readObject();
} finally {
ois.close();
}
return var5;
}
}
}
public static int maxObjectsize = 10000000; //10megabyte???
}

View File

@@ -1,56 +0,0 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.mapdb.elsa.ElsaObjectOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class CustomObjectEncoder extends MessageToByteEncoder<Serializable> {
private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
public CustomObjectEncoder() {
}
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
int startIdx = out.writerIndex();
ByteBufOutputStream bout = new ByteBufOutputStream(out);
if (GuiBase.hasPropertyConfig()){
ElsaObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = new ElsaObjectOutputStream(bout);
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
} else {
ObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = new ObjectOutputStream(bout);
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
}
int endIdx = out.writerIndex();
out.setInt(startIdx, endIdx - startIdx - 4);
}
}

View File

@@ -1,8 +1,10 @@
package forge.net;
import forge.FThreads;
import forge.assets.FSkinProp;
import forge.net.event.GuiGameEvent;
import forge.net.event.ReplyEvent;
import forge.util.gui.SOptionPane;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -25,6 +27,7 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
@Override
public final void channelRead(final ChannelHandlerContext ctx, final Object msg) {
final String[] catchedError = {""};
System.out.println("Received: " + msg);
if (msg instanceof ReplyEvent) {
final ReplyEvent event = (ReplyEvent) msg;
@@ -36,7 +39,9 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
final Method method = protocolMethod.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
//throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
catchedError[0] += String.format("IllegalStateException: Method %s not found (GameProtocolHandler.java Line 43)\n", protocolMethod.name());
System.err.println(String.format("Method %s not found", protocolMethod.name()));
}
final Object[] args = event.getObjects();
@@ -56,7 +61,9 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e.getTargetException());
//throw new RuntimeException(e.getTargetException());
catchedError[0] += (String.format("RuntimeException: %s (GameProtocolHandler.java Line 65)\n", e.getTargetException().toString()));
System.err.println(e.getTargetException().toString());
}
} else {
Serializable reply = null;
@@ -70,8 +77,11 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
}
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e.getTargetException());
} catch (final NullPointerException | InvocationTargetException e) {
//throw new RuntimeException(e.getTargetException());
catchedError[0] += e.toString();
SOptionPane.showMessageDialog(catchedError[0], "Error", FSkinProp.ICO_WARNING);
System.err.println(e.toString());
}
getRemote(ctx).send(new ReplyEvent(event.getId(), reply));
}

View File

@@ -7,4 +7,5 @@ import forge.net.client.FGameClient;
public interface IOnlineLobby {
ILobbyView setLobby(GameLobby lobby);
void setClient(FGameClient client);
void closeConn(String msg);
}

View File

@@ -1,10 +1,10 @@
package forge.net;
import forge.match.LobbySlotType;
import forge.properties.ForgeConstants;
import org.apache.commons.lang3.StringUtils;
import forge.GuiBase;
import forge.assets.FSkinProp;
import forge.interfaces.IGuiGame;
import forge.interfaces.ILobbyListener;
import forge.interfaces.ILobbyView;
@@ -24,12 +24,13 @@ import forge.player.GamePlayerUtil;
import forge.properties.ForgeProfileProperties;
import forge.properties.ForgePreferences.FPref;
import forge.util.gui.SOptionPane;
import forge.util.Localizer;
public class NetConnectUtil {
private NetConnectUtil() { }
public static String getServerUrl() {
final String url = SOptionPane.showInputDialog("This feature is under active development.\nYou are likely to find bugs.\n\n - = * H E R E B E E L D R A Z I * = -\n\nEnter the URL of the server to join.\nLeave blank to host your own server.", "Connect to Server");
final String url = SOptionPane.showInputDialog(Localizer.getInstance().getMessage("lblOnlineMultiplayerDest"), Localizer.getInstance().getMessage("lblConnectToServer"));
if (url == null) { return null; }
//prompt user for player one name if needed
@@ -101,7 +102,7 @@ public class NetConnectUtil {
view.update(true);
return new ChatMessage(null, String.format("Hosting on port %d.", port));
return new ChatMessage(null, Localizer.getInstance().getMessage("lblHostingPortOnN", String.valueOf(port)));
}
public static void copyHostedServerUrl() {
@@ -116,13 +117,13 @@ public class NetConnectUtil {
GuiBase.getInterface().copyToClipboard(internalAddress);
}
String message = "Share the following URL with anyone who wishes to join your server. It has been copied to your clipboard for convenience.\n\n";
String message = "";
if (externalUrl != null) {
message += externalUrl + "\n\nFor internal games, use the following URL: " + internalUrl;
message = Localizer.getInstance().getMessage("lblShareURLToMakePlayerJoinServer", externalUrl, internalUrl);
} else {
message = "Forge was unable to determine your external IP!\n\n" + message + internalUrl;
message = Localizer.getInstance().getMessage("lblForgeUnableDetermineYourExternalIP", message + internalUrl);
}
SOptionPane.showMessageDialog(message, "Server URL", SOptionPane.INFORMATION_ICON);
SOptionPane.showMessageDialog(message, Localizer.getInstance().getMessage("lblServerURL"), SOptionPane.INFORMATION_ICON);
}
public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) {
@@ -145,8 +146,8 @@ public class NetConnectUtil {
}
@Override
public final void close() {
SOptionPane.showMessageDialog("Your connection to the host (" + url + ") was interrupted.", "Error", FSkinProp.ICO_WARNING);
onlineLobby.setClient(null);
GuiBase.setInterrupted(true);
onlineLobby.closeConn(Localizer.getInstance().getMessage("lblYourConnectionToHostWasInterrupted", url));
}
@Override
public ClientGameLobby getLobby() {
@@ -178,9 +179,10 @@ public class NetConnectUtil {
client.connect(hostname, port);
}
catch (Exception ex) {
return null;
//return a message to close the connection so we will not crash...
return new ChatMessage(null, ForgeConstants.CLOSE_CONN_COMMAND);
}
return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port));
return new ChatMessage(null, Localizer.getInstance().getMessage("lblConnectedIPPort", hostname, String.valueOf(port)));
}
}

View File

@@ -1,14 +1,6 @@
package forge.net;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import forge.GuiBase;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
@@ -22,10 +14,15 @@ import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.match.NextGameDecision;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil;
import org.apache.commons.lang3.SerializationUtils;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* The methods that can be sent through this protocol.
@@ -57,7 +54,7 @@ public enum ProtocolMethod {
updateLives (Mode.SERVER, Void.TYPE, Iterable/*PlayerView*/.class),
setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class),
getAbilityToPlay (Mode.SERVER, SpellAbilityView.class, CardView.class, List/*SpellAbilityView*/.class, ITriggerEvent.class),
assignDamage (Mode.SERVER, Map.class, CardView.class, List/*CardView*/.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE),
assignCombatDamage (Mode.SERVER, Map.class, CardView.class, List/*CardView*/.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE),
message (Mode.SERVER, Void.TYPE, String.class, String.class),
showErrorDialog (Mode.SERVER, Void.TYPE, String.class, String.class),
showConfirmDialog (Mode.SERVER, Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE),
@@ -68,15 +65,15 @@ public enum ProtocolMethod {
order (Mode.SERVER, List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE),
sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class, String.class),
chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, List.class, DelayedReveal.class, Boolean.TYPE),
chooseEntitiesForEffect(Mode.SERVER, GameEntityView.class, String.class, List.class, Integer.TYPE, Integer.TYPE, DelayedReveal.class),
chooseEntitiesForEffect(Mode.SERVER, List.class, String.class, List.class, Integer.TYPE, Integer.TYPE, DelayedReveal.class),
manipulateCardList (Mode.SERVER, List.class, String.class, Iterable.class, Iterable.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE),
setCard (Mode.SERVER, Void.TYPE, CardView.class),
setSelectables (Mode.SERVER, Void.TYPE, Iterable/*CardView*/.class),
clearSelectables (Mode.SERVER),
refreshField (Mode.SERVER),
// TODO case "setPlayerAvatar":
openZones (Mode.SERVER, Boolean.TYPE, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
restoreOldZones (Mode.SERVER, Void.TYPE, Map/*PlayerView,Object*/.class),
openZones (Mode.SERVER, PlayerZoneUpdates.class, PlayerView.class, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
restoreOldZones (Mode.SERVER, Void.TYPE, PlayerView.class, PlayerZoneUpdates.class),
isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class),
setRememberedActions(Mode.SERVER, Void.TYPE),
nextRememberedAction(Mode.SERVER, Void.TYPE),
@@ -159,36 +156,15 @@ public enum ProtocolMethod {
}
public void checkArgs(final Object[] args) {
if (GuiBase.hasPropertyConfig())
return; //uses custom serializer for Android 8+..
if(!GuiBase.hasPropertyConfig())
return; //if the experimental network option is enabled, then check the args, else let the default decoder handle it
for (int iArg = 0; iArg < args.length; iArg++) {
Object arg = null;
Class<?> type = null;
try {
arg = args[iArg];
if (this.args.length > iArg)
type = this.args[iArg];
}
catch (ArrayIndexOutOfBoundsException ex){ ex.printStackTrace(); }
catch(ConcurrentModificationException ex) { ex.printStackTrace(); }
if (arg != null)
if (type != null)
if (!ReflectionUtil.isInstance(arg, type)) {
throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
}
if (arg != null) {
// attempt to Serialize each argument, this will throw an exception if it can't.
try {
byte[] serialized = SerializationUtils.serialize((Serializable) arg);
SerializationUtils.deserialize(serialized);
} catch (ArrayIndexOutOfBoundsException ex) {
// not sure why this one would be thrown, but it is
// it also doesn't prevent things from working, so, log for now, pending full network rewrite
ex.printStackTrace();
} catch(ConcurrentModificationException ex) {
// can't seem to avoid this from periodically happening
ex.printStackTrace();
}
final Object arg = args[iArg];
final Class<?> type = this.args[iArg];
if (!ReflectionUtil.isInstance(arg, type)) {
//throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java)", name(), iArg, arg.getClass().getName(), type.getName()));
}
}
}
@@ -199,7 +175,8 @@ public enum ProtocolMethod {
return;
}
if (!ReflectionUtil.isInstance(value, returnType)) {
throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName()));
//throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName()));
System.err.println(String.format("IllegalStateException: Protocol method %s: illegal return object type %s returned by client, expected %s (ProtocolMethod.java)", name(), value.getClass().getName(), getReturnType().getName()));
}
}
}

View File

@@ -1,7 +1,7 @@
package forge.net.client;
import forge.net.CustomObjectDecoder;
import forge.net.CustomObjectEncoder;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@@ -58,8 +58,8 @@ public class FGameClient implements IToServer {
public void initChannel(final SocketChannel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(
new CustomObjectEncoder(),
new CustomObjectDecoder(CustomObjectDecoder.maxObjectsize, ClassResolvers.cacheDisabled(null)),
new CompatibleObjectEncoder(),
new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)),
new MessageHandler(),
new LobbyUpdateHandler(),
new GameClientHandler(FGameClient.this));
@@ -86,7 +86,8 @@ public class FGameClient implements IToServer {
}
public void close() {
channel.close();
if (channel != null)
channel.close();
}
@Override

View File

@@ -40,12 +40,12 @@ public class NetGameController implements IGameController {
@Override
public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) {
send(ProtocolMethod.selectPlayer, playerView, triggerEvent);
send(ProtocolMethod.selectPlayer, playerView, null/*triggerEvent*/); //some platform don't have mousetriggerevent class or it will not allow them to click/tap
}
@Override
public boolean selectCard(final CardView cardView, final List<CardView> otherCardViewsToSelect, final ITriggerEvent triggerEvent) {
send(ProtocolMethod.selectCard, cardView, otherCardViewsToSelect, triggerEvent);
send(ProtocolMethod.selectCard, cardView, otherCardViewsToSelect, null/*triggerEvent*/); //some platform don't have mousetriggerevent class or it will not allow them to click/tap
// Difference from local games! Always consider a card as successfully selected,
// to avoid blocks where server and client wait for each other to respond.
// Some cost in functionality but a huge gain in stability & speed.

View File

@@ -25,6 +25,10 @@ public final class UpdateLobbyPlayerEvent implements NetEvent {
private DeckSection section = null;
private CardPool cards = null;
private Set<AIOption> aiOptions = null;
private String AvatarVanguard = null;
private String SchemeDeckName = null;
private String PlanarDeckName = null;
private String DeckName = null;
public static UpdateLobbyPlayerEvent create(final LobbySlotType type, final String name, final int avatarIndex, final int sleeveIndex, final int team, final boolean isArchenemy, final boolean isReady, final Set<AIOption> aiOptions) {
@@ -53,17 +57,39 @@ public final class UpdateLobbyPlayerEvent implements NetEvent {
public static UpdateLobbyPlayerEvent sleeveUpdate(final int index) {
return new UpdateLobbyPlayerEvent(index, false);
}
private UpdateLobbyPlayerEvent(int index, boolean avatar) {
public static UpdateLobbyPlayerEvent isReadyUpdate(final boolean isReady) {
return new UpdateLobbyPlayerEvent(isReady);
}
public static UpdateLobbyPlayerEvent teamUpdate(int team) {
return new UpdateLobbyPlayerEvent(team);
}
public static UpdateLobbyPlayerEvent setDeckSchemePlaneVanguard(final String DeckName, final String Scheme, final String Plane, final String Vanguard) {
return new UpdateLobbyPlayerEvent(DeckName, Scheme, Plane, Vanguard);
}
private UpdateLobbyPlayerEvent(final int index, final boolean avatar) {
if (avatar)
this.avatarIndex = index;
else
this.sleeveIndex = index;
}
private UpdateLobbyPlayerEvent(final int team) {
this.team = team;
}
private UpdateLobbyPlayerEvent(final String DeckName, final String Scheme, final String Plane, final String Vanguard) {
this.SchemeDeckName = Scheme;
this.PlanarDeckName = Plane;
this.AvatarVanguard = Vanguard;
this.DeckName = DeckName;
}
private UpdateLobbyPlayerEvent(final Deck deck) {
this.deck = deck;
}
private UpdateLobbyPlayerEvent(final boolean isReady) {
this.isReady = isReady;
}
private UpdateLobbyPlayerEvent(final DeckSection section, final CardPool cards) {
this.section = section;
this.cards = cards;
@@ -149,4 +175,8 @@ public final class UpdateLobbyPlayerEvent implements NetEvent {
public Set<AIOption> getAiOptions() {
return aiOptions == null ? null : Collections.unmodifiableSet(aiOptions);
}
public String getAvatarVanguard() { return AvatarVanguard; }
public String getSchemeDeckName() { return SchemeDeckName; }
public String getPlanarDeckName() { return PlanarDeckName; }
public String getDeckName() { return DeckName; }
}

View File

@@ -6,8 +6,8 @@ import forge.interfaces.IGuiGame;
import forge.interfaces.ILobbyListener;
import forge.match.LobbySlot;
import forge.match.LobbySlotType;
import forge.net.CustomObjectDecoder;
import forge.net.CustomObjectEncoder;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import forge.net.event.LobbyUpdateEvent;
import forge.net.event.LoginEvent;
import forge.net.event.LogoutEvent;
@@ -99,8 +99,8 @@ public final class FServerManager {
public final void initChannel(final SocketChannel ch) throws Exception {
final ChannelPipeline p = ch.pipeline();
p.addLast(
new CustomObjectEncoder(),
new CustomObjectDecoder(CustomObjectDecoder.maxObjectsize, ClassResolvers.cacheDisabled(null)),
new CompatibleObjectEncoder(),
new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)),
new MessageHandler(),
new RegisterClientHandler(),
new LobbyInputHandler(),
@@ -180,6 +180,15 @@ public final class FServerManager {
this.localLobby = lobby;
}
public void unsetReady() {
if (this.localLobby != null) {
if (this.localLobby.getSlot(0) != null) {
this.localLobby.getSlot(0).setIsReady(false);
updateLobbyState();
}
}
}
public boolean isMatchActive() {
return this.localLobby != null && this.localLobby.isMatchActive();
}

Some files were not shown because too many files have changed in this diff Show More