Merge branch 'master' of https://git.cardforge.org/core-developers/forge.git into questMode_wildOpponents

This commit is contained in:
Alessandro Coli
2020-09-17 20:40:51 +02:00
2868 changed files with 35951 additions and 12813 deletions

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

View File

@@ -6,23 +6,21 @@ 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;
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 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

@@ -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();
@@ -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

@@ -11,7 +11,7 @@ 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

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

@@ -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

@@ -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

@@ -19,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

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

@@ -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

@@ -397,6 +397,7 @@ public enum FSkinProp {
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),

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;
@@ -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,19 @@ 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);
}
};
GuiBase.getInterface().invokeInEdtLater(notifyStackAddition);
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;
}
@@ -288,7 +293,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 +361,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);
}
@@ -393,16 +412,18 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
@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

@@ -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

@@ -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

@@ -6,6 +6,7 @@ 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;
@@ -28,6 +29,7 @@ public class AutoUpdater {
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"};
@@ -74,9 +76,9 @@ public class AutoUpdater {
// TODO This doesn't work yet, because FSkin isn't loaded at the time.
return false;
} else if (updateChannel.equals("none")) {
String message = "You haven't set an update channel. Do you want to check a channel now?";
String message = localizer.getMessage("lblYouHaventSetUpdateChannel");
List<String> options = ImmutableList.of("Cancel", "release", "snapshot");
int option = SOptionPane.showOptionDialog(message, "Manual Check", null, options, 0);
int option = SOptionPane.showOptionDialog(message, localizer.getMessage("lblManualCheck"), null, options, 0);
if (option == 0) {
return false;
} else {
@@ -180,12 +182,9 @@ public class AutoUpdater {
return downloadFromBrowser();
}
String message = "A new version of Forge is available (" + version + ").\n" +
"You are currently on version (" + buildVersion + ").\n\n" +
"Would you like to update to the new version now?";
final List<String> options = ImmutableList.of("Update Now", "Update Later");
if (SOptionPane.showOptionDialog(message, "New Version Available", null, options, 0) == 0) {
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();
}
@@ -208,7 +207,7 @@ public class AutoUpdater {
WaitCallback<Boolean> callback = new WaitCallback<Boolean>() {
@Override
public void run() {
GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", "Download the new version..", packageUrl, "tmp/", null, null) {
GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", localizer.getMessage("lblNewVersionDownloading"), packageUrl, "tmp/", null, null) {
@Override
public void downloadAndUnzip() {
packagePath = download(version + "-upgrade.tar.bz2");
@@ -240,7 +239,7 @@ public class AutoUpdater {
}
private void restartForge() {
if (isLoading || SOptionPane.showConfirmDialog("Forge has been downloaded. You should extract the package and restart Forge for the new version.", "Exit now?")) {
if (isLoading || SOptionPane.showConfirmDialog(localizer.getMessage("lblForgeHasBeenUpdateRestartForgeToUseNewVersion"), localizer.getMessage("lblExitNowConfirm"))) {
System.exit(0);
}
}

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,7 +268,7 @@ 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\\";
@@ -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);

View File

@@ -62,7 +62,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);

View File

@@ -26,10 +26,12 @@ 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;
@@ -215,6 +217,257 @@ public class AdvancedSearch {
return cards.get(0) == input;
}
}),
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) {

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

@@ -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

@@ -57,6 +57,7 @@ public class BoosterDraft implements IBoosterDraft {
private final List<LimitedPlayer> players = new ArrayList<>();
private LimitedPlayer localPlayer;
private boolean doublePickToStartRound = false;
protected int nextBoosterGroup = 0;
private int currentBoosterSize = 0;
private int currentBoosterPick = 0;
@@ -142,7 +143,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);
doublePickToStartRound = FModel.getMagicDb().getEditions().get(setCode).getDoublePickToStartRound();
final IUnOpenedProduct product1 = block.getBooster(setCode);
for (int i = 0; i < nPacks; i++) {
this.product.add(product1);
@@ -357,6 +362,10 @@ public class BoosterDraft implements IBoosterDraft {
public void passPacks() {
// Alternate direction of pack passing
int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1;
if (this.doublePickToStartRound && currentBoosterPick == 1) {
adjust = 0;
}
for (int i = 0; i < N_PLAYERS; i++) {
List<PaperCard> passingPack = this.players.get(i).passPack();
@@ -494,26 +503,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

@@ -182,6 +182,9 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
return true; //if not in game, card can be shown
}
if(GuiBase.getInterface().isLibgdxPort()){
if(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...
return true;
@@ -217,6 +220,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
case Flipped:
case Transformed:
case Meld:
case Modal:
return true;
default:
return false;
@@ -569,7 +573,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 +585,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) {

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

@@ -295,6 +295,7 @@ public class HostedMatch {
public void endCurrentGame() {
if (game == null) { return; }
boolean isMatchOver = game.getView().isMatchOver();
game = null;
@@ -304,7 +305,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();
}

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;
@@ -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;
@@ -89,6 +90,9 @@ public class InputBlock extends InputSyncronizedBase {
showMessage(message);
}
if (combat != null)
getController().getGame().fireEvent(new GameEventCombatUpdate(combat.getAttackers(), combat.getAllBlockers()));
getController().getGui().showCombat();
}

View File

@@ -9,6 +9,8 @@ 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;
@@ -92,15 +94,9 @@ public abstract class InputPayMana extends InputSyncronizedBase {
if (card.getManaAbilities().size() == 1) {
activateManaAbility(card, card.getManaAbilities().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, Lists.newArrayList(card.getManaAbilities()), triggerEvent);
if (spellAbility != null) {
activateManaAbility(card, spellAbility);
}
}
return true;
@@ -390,7 +386,11 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
}
else {
String colorsProduced = m.isComboMana() ? m.getComboColors() : m.getOrigProduced();
// treat special mana if it always can be paid
if (m.isSpecialMana()) {
return true;
}
String colorsProduced = m.isComboMana() ? m.getComboColors() : m.mana();
for (final String color : colorsProduced.split(" ")) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return 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

@@ -6,6 +6,7 @@ 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;
@@ -16,6 +17,7 @@ import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.util.Aggregates;
import forge.util.ITriggerEvent;
import forge.util.TextUtil;
@@ -148,6 +150,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 +175,35 @@ public final class InputSelectTargets extends InputSyncronizedBase {
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 all cards must have different controllers
if (tgt.isDifferentControllers()) {
final List<Player> targetedControllers = new ArrayList<>();
@@ -185,13 +219,23 @@ 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;
}
@@ -319,7 +363,6 @@ public final class InputSelectTargets extends InputSyncronizedBase {
return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose());
}
@Override
protected void onStop() {
getController().getGui().clearSelectables();

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

@@ -18,12 +18,9 @@ import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil;
import org.apache.commons.lang3.SerializationUtils;
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;
@@ -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,7 +65,7 @@ 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),
@@ -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,5 +1,7 @@
package forge.net.client;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@@ -26,8 +28,6 @@ import forge.net.event.IdentifiableNetEvent;
import forge.net.event.LobbyUpdateEvent;
import forge.net.event.MessageEvent;
import forge.net.event.NetEvent;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
public class FGameClient implements IToServer {
@@ -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 ObjectEncoder(),
new ObjectDecoder(9766*1024, 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,6 +6,8 @@ import forge.interfaces.IGuiGame;
import forge.interfaces.ILobbyListener;
import forge.match.LobbySlot;
import forge.match.LobbySlotType;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import forge.net.event.LobbyUpdateEvent;
import forge.net.event.LoginEvent;
import forge.net.event.LogoutEvent;
@@ -24,8 +26,6 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
@@ -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 ObjectEncoder(),
new ObjectDecoder(9766*1024, 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();
}

View File

@@ -41,7 +41,7 @@ public class NetGuiGame extends AbstractGuiGame {
return sender.sendAndWait(method, args);
}
private void updateGameView() {
public void updateGameView() {
send(ProtocolMethod.setGameView, getGameView());
}
@@ -191,12 +191,12 @@ public class NetGuiGame extends AbstractGuiGame {
@Override
public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
return sendAndWait(ProtocolMethod.getAbilityToPlay, hostCard, abilities, triggerEvent);
return sendAndWait(ProtocolMethod.getAbilityToPlay, hostCard, abilities, null/*triggerEvent*/); //someplatform don't have mousetriggerevent class or it will not allow them to click/tap
}
@Override
public Map<CardView, Integer> assignDamage(final CardView attacker, final List<CardView> blockers, final int damage, final GameEntityView defender, final boolean overrideOrder) {
return sendAndWait(ProtocolMethod.assignDamage, attacker, blockers, damage, defender, overrideOrder);
public Map<CardView, Integer> assignCombatDamage(final CardView attacker, final List<CardView> blockers, final int damage, final GameEntityView defender, final boolean overrideOrder) {
return sendAndWait(ProtocolMethod.assignCombatDamage, attacker, blockers, damage, defender, overrideOrder);
}
@Override

View File

@@ -45,7 +45,12 @@ public class ConquestRegion {
return name;
}
public void clearArt() {
art = null;
}
public ISkinImage getArt() {
clearArt(); //force clear this so it will be redrawn since loadingcache invalidates the cache every screen change
if (art == null) {
PaperCard pc = cardPool.getCard(artCardName);

View File

@@ -9,11 +9,14 @@ import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -22,6 +25,7 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardView;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.cost.*;
import forge.game.player.Player;
@@ -35,6 +39,7 @@ import forge.match.input.InputSelectManyBase;
import forge.util.Aggregates;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import forge.util.gui.SGuiChoose;
import forge.util.ITriggerEvent;
import forge.util.Localizer;
import forge.util.CardTranslation;
@@ -123,6 +128,23 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
return PaymentDecision.card(randomSubset);
}
if (discardType.equals("DifferentNames")) {
final CardCollection discarded = new CardCollection();
while (c > 0) {
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, hand, ability);
inp.setMessage(Localizer.getInstance().getMessage("lblSelectOneDifferentNameCardToDiscardAlreadyChosen") + discarded);
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled()) {
return null;
}
final Card first = inp.getFirstSelected();
discarded.add(first);
hand = CardLists.filter(hand, Predicates.not(CardPredicates.sharesNameWith(first)));
c--;
}
return PaymentDecision.card(discarded);
}
if (discardType.contains("+WithSameName")) {
final String type = TextUtil.fastReplace(discardType, "+WithSameName", "");
hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability);
@@ -310,11 +332,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (nNeeded == 0) {
return PaymentDecision.number(0);
}
final Game game = controller.getGame();
final Player p = game.getPlayer(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileFromWhoseZone", cost.getFrom().getTranslatedName()), PlayerView.getCollection(payableZone)));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
final PlayerView pv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileFromWhoseZone", cost.getFrom().getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
final Player p = gameCachePlayer.get(pv);
final CardCollection typeList = CardLists.filter(list, CardPredicates.isOwner(p));
final int count = typeList.size();
@@ -322,8 +345,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return null;
}
final CardCollection toExile = game.getCardList(controller.getGui().many(Localizer.getInstance().getMessage("lblExileFromZone", cost.getFrom().getTranslatedName()), Localizer.getInstance().getMessage("lblToBeExiled"), nNeeded, CardView.getCollection(typeList), null));
return PaymentDecision.card(toExile);
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(typeList);
List<CardView> views = controller.getGui().many(
Localizer.getInstance().getMessage("lblExileFromZone", cost.getFrom().getTranslatedName()),
Localizer.getInstance().getMessage("lblToBeExiled"), nNeeded, gameCacheExile.getTrackableKeys(), null);
List<Card> result = Lists.newArrayList();
gameCacheExile.addToList(views, result);
return PaymentDecision.card(result);
}
@Override
@@ -395,20 +423,17 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(list);
}
private Card getCard(final CardView cardView) {
return controller.getGame().getCard(cardView);
}
private PaymentDecision exileFromMiscZone(final CostExile cost, final SpellAbility sa, final int nNeeded, final CardCollection typeList) {
if (typeList.size() < nNeeded) { return null; }
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
final CardCollection exiled = new CardCollection();
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileProgressFromZone", String.valueOf(i + 1), String.valueOf(nNeeded), cost.getFrom().getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) { return null; }
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblExileProgressFromZone", String.valueOf(i + 1), String.valueOf(nNeeded), cost.getFrom().getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) { return null; }
typeList.remove(c);
exiled.add(c);
exiled.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(exiled);
}
@@ -438,12 +463,17 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (ability.isOptionalTrigger()) {
min = 0;
}
final CardCollection choice = controller.getGame().getCardList(controller.getGui().many(Localizer.getInstance().getMessage("lblChooseAnExiledCardPutIntoGraveyard"), Localizer.getInstance().getMessage("lblToGraveyard"), min, c, CardView.getCollection(list), CardView.get(source)));
if (choice == null || choice.size() < c) {
GameEntityViewMap<Card, CardView> gameCacheExile = GameEntityView.getMap(list);
List<CardView> views = controller.getGui().many(
Localizer.getInstance().getMessage("lblChooseAnExiledCardPutIntoGraveyard"),
Localizer.getInstance().getMessage("lblToGraveyard"), min, c, CardView.getCollection(list), CardView.get(source));
if (views == null || views.size() < c) {
return null;
}
return PaymentDecision.card(choice);
List<Card> result = Lists.newArrayList();
gameCacheExile.addToList(views, result);
return PaymentDecision.card(result);
}
@Override
@@ -495,14 +525,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
// Generalize this
if (sVar.equals("XChoice")) {
c = chooseXValue(cost.getLKIList().size());
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
c = AbilityUtils.calculateAmount(source, amount, ability);
}
if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantFlipNCoinAction", String.valueOf(c)), ability)) {
return null;
}
return PaymentDecision.number(c);
}
@@ -555,11 +584,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.players(oppsThatCanGainLife);
}
final Player chosenToGain = controller.getGame().getPlayer(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblCardChooseAnOpponentToGainNLife", CardTranslation.getTranslatedName(source.getName()), String.valueOf(c)), PlayerView.getCollection(oppsThatCanGainLife)));
if (chosenToGain == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(oppsThatCanGainLife);
final PlayerView pv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblCardChooseAnOpponentToGainNLife", CardTranslation.getTranslatedName(source.getName()), String.valueOf(c)), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
return PaymentDecision.players(Lists.newArrayList(chosenToGain));
return PaymentDecision.players(Lists.newArrayList(gameCachePlayer.get(pv)));
}
@Override
@@ -568,19 +598,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
// Generalize this
if (sVar.equals("XChoice")) {
c = chooseXValue(cost.getLKIList().size());
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
c = AbilityUtils.calculateAmount(source, amount, ability);
}
if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblMillNCardsFromYourLibraryConfirm", String.valueOf(c)), ability)) {
return null;
}
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
return PaymentDecision.number(c);
}
@Override
@@ -613,7 +637,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(final CostPayEnergy cost) {
final String amount = cost.getAmount();
final int energy = player.getCounters(CounterType.ENERGY);
final int energy = player.getCounters(CounterEnumType.ENERGY);
Integer c = cost.convertAmount();
if (c == null) {
@@ -629,7 +653,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
if (player.canPayEnergy(c) &&
player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblPayEnergyConfirm", cost.toString(), String.valueOf(player.getCounters(CounterType.ENERGY)), "{E}"), ability)) {
player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblPayEnergyConfirm", cost.toString(), String.valueOf(player.getCounters(CounterEnumType.ENERGY)), "{E}"), ability)) {
return PaymentDecision.number(c);
}
return null;
@@ -694,13 +718,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
final CardCollection chosen = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblFromZonePutToLibrary", fromZone.getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) {
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblFromZonePutToLibrary", fromZone.getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) {
return null;
}
typeList.remove(c);
chosen.add(c);
chosen.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(chosen);
}
@@ -710,10 +734,12 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(0);
}
final Player p = controller.getGame().getPlayer(controller.getGui().oneOrNone(TextUtil.concatNoSpace(Localizer.getInstance().getMessage("lblPutCardsFromWhoseZone"), fromZone.getTranslatedName()), PlayerView.getCollection(payableZone)));
if (p == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
PlayerView pv = SGuiChoose.oneOrNone(TextUtil.concatNoSpace(Localizer.getInstance().getMessage("lblPutCardsFromWhoseZone"), fromZone.getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return null;
}
Player p = gameCachePlayer.get(pv);
final CardCollection typeList = CardLists.filter(list, CardPredicates.isOwner(p));
if (typeList.size() < nNeeded) {
@@ -721,13 +747,13 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
final CardCollection chosen = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheCard = GameEntityView.getMap(typeList);
for (int i = 0; i < nNeeded; i++) {
final Card c = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblPutZoneCardsToLibrary", fromZone.getTranslatedName()), CardView.getCollection(typeList)));
if (c == null) {
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblPutZoneCardsToLibrary", fromZone.getTranslatedName()), gameCacheCard.getTrackableKeys());
if (cv == null || !gameCacheCard.containsKey(cv)) {
return null;
}
typeList.remove(c);
chosen.add(c);
chosen.add(gameCacheCard.remove(cv));
}
return PaymentDecision.card(chosen);
}
@@ -1068,15 +1094,14 @@ public class HumanCostDecision extends CostDecisionMakerBase {
}
// Rift Elemental only - always removes 1 counter, so there will be no code for N counters.
final List<CardView> suspended = Lists.newArrayList();
for (final Card crd : validCards) {
if (crd.getCounters(cost.counter) > 0) {
suspended.add(CardView.get(crd));
}
GameEntityViewMap<Card, CardView> gameCacheSuspended = GameEntityView.getMap(CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter)));
final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), gameCacheSuspended.getTrackableKeys());
if (cv == null || !gameCacheSuspended.containsKey(cv)) {
return null;
}
final Card card = getCard(controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), suspended));
return null == card ? null : PaymentDecision.card(card, c);
return PaymentDecision.card(gameCacheSuspended.get(cv), c);
}
@Override

View File

@@ -6,10 +6,11 @@ import forge.FThreads;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.ability.effects.FlipCoinEffect;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.*;
@@ -23,7 +24,6 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.match.input.InputPayMana;
import forge.match.input.InputPayManaOfCostPayment;
import forge.match.input.InputPayManaSimple;
import forge.match.input.InputSelectCardsFromList;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
@@ -46,17 +46,20 @@ public class HumanPlay {
* <p>
* playSpellAbility.
* </p>
*
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
public final static boolean playSpellAbility(final PlayerControllerHuman controller, final Player p, SpellAbility sa) {
FThreads.assertExecutedByEdt(false);
Card source = sa.getHostCard();
if (sa instanceof LandAbility) {
sa.setActivatingPlayer(p);
if (sa.canPlay()) {
sa.resolve();
p.getGame().updateLastStateForCard(source);
}
return false;
}
@@ -64,7 +67,6 @@ public class HumanPlay {
boolean castFaceDown = sa.isCastFaceDown();
sa.setActivatingPlayer(p);
Card source = sa.getHostCard();
boolean flippedToCast = sa instanceof Spell && source.isFaceDown();
source.setSplitStateToPlayAbility(sa);
@@ -79,7 +81,7 @@ public class HumanPlay {
}
if (flippedToCast && !castFaceDown) {
source.turnFaceUp(false, false);
source.forceTurnFaceUp();
}
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
@@ -92,34 +94,9 @@ public class HumanPlay {
source.animateBestow();
}
// Need to check PayCosts, and Ability + All SubAbilities for Target
boolean newAbility = sa.getPayCosts() != null;
SpellAbility ability = sa;
while ((ability != null) && !newAbility) {
final TargetRestrictions tgt = ability.getTargetRestrictions();
newAbility |= tgt != null;
ability = ability.getSubAbility();
}
// System.out.println("Playing:" + sa.getDescription() + " of " + sa.getHostCard() + " new = " + newAbility);
if (newAbility) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
if (!req.playAbility(true, false, false)) {
if (flippedToCast && !castFaceDown) {
source.turnFaceDown(true);
}
return false;
}
} else if (payManaCostIfNeeded(controller, p, sa)) {
if (sa.isSpell() && !source.isCopiedSpell()) {
sa.setHostCard(p.getGame().getAction().moveToStack(source, sa));
}
p.getGame().getStack().add(sa);
} else {
// Failed to pay costs, revert to original state
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
if (!req.playAbility(true, false, false)) {
if (flippedToCast && !castFaceDown) {
source.turnFaceDown(true);
}
@@ -131,7 +108,7 @@ public class HumanPlay {
/**
* choose optional additional costs. For HUMAN only
* @param p
*
*
* @param original
* the original sa
* @return an ArrayList<SpellAbility>.
@@ -157,31 +134,11 @@ public class HumanPlay {
//final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
}
private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) {
final ManaCostBeingPaid manaCost;
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
}
else {
manaCost = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
CostAdjustment.adjust(manaCost, sa, null, false);
}
boolean isPaid = manaCost.isPaid();
if (!isPaid) {
InputPayManaSimple inputPay = new InputPayManaSimple(controller, p.getGame(), sa, manaCost);
inputPay.showAndWait();
isPaid = inputPay.isPaid();
}
return isPaid;
}
/**
* <p>
* playSpellAbilityForFree.
* </p>
*
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
@@ -191,33 +148,22 @@ public class HumanPlay {
source.setSplitStateToPlayAbility(sa);
if (sa.getPayCosts() != null) {
if (!sa.isCopied()) {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
sa = AbilityUtils.addSpliceEffects(sa);
if (!sa.isCopied()) {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
sa = AbilityUtils.addSpliceEffects(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false);
}
else {
if (sa.isSpell()) {
final Card c = sa.getHostCard();
if (!c.isCopiedSpell()) {
sa.setHostCard(game.getAction().moveToStack(c, sa));
}
}
game.getStack().add(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false);
}
/**
* <p>
* playSpellAbility_NoStack.
* </p>
*
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
@@ -228,14 +174,8 @@ public class HumanPlay {
public final static void playSpellAbilityNoStack(final PlayerControllerHuman controller, final Player player, final SpellAbility sa, boolean useOldTargets) {
sa.setActivatingPlayer(player);
if (sa.getPayCosts() != null) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(!useOldTargets, false, true);
}
else if (payManaCostIfNeeded(controller, player, sa)) {
AbilityUtils.resolve(sa);
}
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(!useOldTargets, false, true);
}
// ------------------------------------------------------------------------
@@ -345,7 +285,7 @@ public class HumanPlay {
}
for (Player player : res) {
player.drawCards(amount);
player.drawCards(amount, sourceAbility);
}
}
else if (part instanceof CostGainLife) {
@@ -378,19 +318,25 @@ public class HumanPlay {
return false;
}
CardCollectionView listmill = p.getCardsIn(ZoneType.Library, amount);
((CostMill) part).executePayment(sourceAbility, listmill);
((CostMill) part).payAsDecided(p, PaymentDecision.card(listmill), sourceAbility);
}
else if (part instanceof CostFlipCoin) {
final int amount = getAmountFromPart(part, source, sourceAbility);
if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblDoYouWantFlipNCoinOrDoAction", String.valueOf(amount), orString), sourceAbility)) {
if (!part.canPay(sourceAbility, p)) {
return false;
}
final int n = FlipCoinEffect.getFilpMultiplier(p);
for (int i = 0; i < amount; i++) {
FlipCoinEffect.flipCoinCall(p, sourceAbility, n);
}
PaymentDecision pd = part.accept(hcd);
if (pd == null)
return false;
else
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostDamage) {
if (!part.canPay(sourceAbility, p)) {
return false;
}
// not a pay life but damage!
PaymentDecision pd = part.accept(hcd);
@@ -400,6 +346,10 @@ public class HumanPlay {
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostPutCounter) {
if (!part.canPay(sourceAbility, p)) {
return false;
}
PaymentDecision pd = part.accept(hcd);
if (pd == null)
@@ -486,7 +436,7 @@ public class HumanPlay {
return false;
}
costExile.executePayment(sourceAbility, p.getCardsIn(ZoneType.Graveyard));
costExile.payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Graveyard)), sourceAbility);
}
else {
from = costExile.getFrom();
@@ -500,20 +450,19 @@ public class HumanPlay {
return false;
}
list = list.subList(0, nNeeded);
costExile.executePayment(sourceAbility, list);
costExile.payAsDecided(p, PaymentDecision.card(list), sourceAbility);
} else {
// replace this with input
CardCollection newList = new CardCollection();
GameEntityViewMap<Card, CardView> gameCacheList = GameEntityView.getMap(list);
for (int i = 0; i < nNeeded; i++) {
final Card c = p.getGame().getCard(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblExileFromZone", from.getTranslatedName()), CardView.getCollection(list)));
if (c == null) {
final CardView cv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblExileFromZone", from.getTranslatedName()), gameCacheList.getTrackableKeys());
if (cv == null || !gameCacheList.containsKey(cv)) {
return false;
}
list.remove(c);
newList.add(c);
newList.add(gameCacheList.remove(cv));
}
costExile.executePayment(sourceAbility, newList);
costExile.payAsDecided(p, PaymentDecision.card(newList), sourceAbility);
}
}
}
@@ -541,31 +490,32 @@ public class HumanPlay {
payableZone.add(player);
}
}
Player chosen = controller.getGame().getPlayer(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardFromWhoseZone", from.getTranslatedName()), PlayerView.getCollection(payableZone)));
if (chosen == null) {
GameEntityViewMap<Player, PlayerView> gameCachePlayer = GameEntityView.getMap(payableZone);
PlayerView pv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardFromWhoseZone", from.getTranslatedName()), gameCachePlayer.getTrackableKeys());
if (pv == null || !gameCachePlayer.containsKey(pv)) {
return false;
}
Player chosen = gameCachePlayer.get(pv);
List<Card> typeList = CardLists.filter(list, CardPredicates.isOwner(chosen));
GameEntityViewMap<Card, CardView> gameCacheTypeList = GameEntityView.getMap(typeList);
for (int i = 0; i < amount; i++) {
if (typeList.isEmpty()) {
if (gameCacheTypeList.isEmpty()) {
return false;
}
final Card c = p.getGame().getCard(SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardToLibrary"), CardView.getCollection(typeList)));
if (c != null) {
typeList.remove(c);
p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos()), null);
}
else {
final CardView cv = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblPutCardToLibrary"), gameCacheTypeList.getTrackableKeys());
if (cv == null || !gameCacheTypeList.containsKey(cv)) {
return false;
}
final Card c = gameCacheTypeList.get(cv);
gameCacheTypeList.remove(c);
p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos()), null);
}
}
else { // Tainted Specter, Gurzigost, etc.
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblPutIntoLibrary") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblPutIntoLibrary") + orString);
if (!hasPaid) {
return false;
}
@@ -582,13 +532,13 @@ public class HumanPlay {
else if (part instanceof CostGainControl) {
int amount = Integer.parseInt(part.getAmount());
CardCollectionView list = CardLists.getValidCards(p.getGame().getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblGainControl") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblGainControl") + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostReturn) {
CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReturnToHand") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReturnToHand") + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostDiscard) {
@@ -597,11 +547,11 @@ public class HumanPlay {
return false;
}
((CostDiscard)part).executePayment(sourceAbility, p.getCardsIn(ZoneType.Hand));
((CostDiscard)part).payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Hand)), sourceAbility);
} else {
CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lbldiscard") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lbldiscard") + orString);
if (!hasPaid) { return false; }
}
}
@@ -609,14 +559,14 @@ public class HumanPlay {
CostReveal costReveal = (CostReveal) part;
CardCollectionView list = CardLists.getValidCards(p.getCardsIn(costReveal.getRevealFrom()), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReveal") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReveal") + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostTapType) {
CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
list = CardLists.filter(list, Presets.UNTAPPED);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(controller, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblTap") + orString);
boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblTap") + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostPartMana) {
@@ -625,7 +575,7 @@ public class HumanPlay {
}
}
else if (part instanceof CostPayEnergy) {
CounterType counterType = CounterType.ENERGY;
CounterType counterType = CounterType.get(CounterEnumType.ENERGY);
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!part.canPay(sourceAbility, p)) {
@@ -675,7 +625,7 @@ public class HumanPlay {
return paid;
}
private static boolean payCostPart(final PlayerControllerHuman controller, SpellAbility sourceAbility, CostPartWithList cpl, int amount, CardCollectionView list, String actionName) {
private static boolean payCostPart(final PlayerControllerHuman controller, Player p, SpellAbility sourceAbility, CostPartWithList cpl, int amount, CardCollectionView list, String actionName) {
if (list.size() < amount) { return false; } // unable to pay (not enough cards)
InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, amount, amount, list, sourceAbility);
@@ -687,11 +637,8 @@ public class HumanPlay {
return false;
}
cpl.executePayment(sourceAbility, new CardCollection(inp.getSelected()));
cpl.payAsDecided(p, PaymentDecision.card(inp.getSelected()), sourceAbility);
if (sourceAbility != null) {
cpl.reportPaidCardsTo(sourceAbility);
}
return true;
}
@@ -748,10 +695,10 @@ public class HumanPlay {
if (mc.getAmountOfX() > 0 && !"Count$xPaid".equals(xInCard)) { // announce X will overwrite whatever was in card script
int xPaid = AbilityUtils.calculateAmount(source, "X", ability);
toPay.setXManaCostPaid(xPaid, ability.getParam("XColor"));
source.setXManaCostPaid(xPaid);
ability.setXManaCostPaid(xPaid);
}
else if (source.getXManaCostPaid() > 0) { //ensure pre-announced X value retained
toPay.setXManaCostPaid(source.getXManaCostPaid(), ability.getParam("XColor"));
else if (ability.getXManaCostPaid() != null) { //ensure pre-announced X value retained
toPay.setXManaCostPaid(ability.getXManaCostPaid(), ability.getParam("XColor"));
}
int timesMultikicked = source.getKickerMagnitude();

View File

@@ -103,7 +103,7 @@ public class HumanPlaySpellAbility {
// This is should happen earlier, before the Modal spell is chosen
// Turn face-down card face up (except case of morph spell)
if (ability.isSpell() && !ability.isCastFaceDown() && fromState == CardStateName.FaceDown) {
c.turnFaceUp();
c.turnFaceUp(null);
}
ability.setHostCard(game.getAction().moveToStack(c, ability));
}
@@ -114,7 +114,7 @@ public class HumanPlaySpellAbility {
ability = GameActionUtil.addExtraKeywordCost(ability);
Cost abCost = ability.getPayCosts() == null ? new Cost("0", ability.isAbility()) : ability.getPayCosts();
Cost abCost = ability.getPayCosts();
CostPayment payment = new CostPayment(abCost, ability);
// TODO Apply this to the SAStackInstance instead of the Player
@@ -161,7 +161,6 @@ public class HumanPlaySpellAbility {
// if a player failed to play madness cost, move the card to graveyard
Card newCard = game.getAction().moveToGraveyard(c, null);
newCard.setMadnessWithoutCast(true);
newCard.setMadness(false);
} else if (ability.getHostCard().isBestowed()) {
ability.getHostCard().unanimateBestow();
}
@@ -214,7 +213,7 @@ public class HumanPlaySpellAbility {
final FCollection<Player> candidates = AbilityUtils.getDefinedPlayers(source, currentAbility.getParam("TargetingPlayer"), currentAbility);
// activator chooses targeting player
targetingPlayer = ability.getActivatingPlayer().getController().chooseSingleEntityForEffect(
candidates, currentAbility, "Choose the targeting player");
candidates, currentAbility, "Choose the targeting player", null);
} else {
targetingPlayer = ability.getActivatingPlayer();
}
@@ -223,10 +222,10 @@ public class HumanPlaySpellAbility {
return false;
}
}
final SpellAbility subAbility = currentAbility.getSubAbility();
final AbilitySub subAbility = currentAbility.getSubAbility();
if (subAbility != null) {
// This is necessary for "TargetsWithDefinedController$ ParentTarget"
((AbilitySub) subAbility).setParent(currentAbility);
subAbility.setParent(currentAbility);
}
currentAbility = subAbility;
} while (currentAbility != null);
@@ -314,10 +313,10 @@ public class HumanPlaySpellAbility {
if (value == null) {
return false;
}
card.setXManaCostPaid(value);
ability.setXManaCostPaid(value);
}
} else if (manaCost.getMana().isZero() && ability.isSpell()) {
card.setXManaCostPaid(0);
ability.setXManaCostPaid(0);
}
}
return true;

View File

@@ -6,12 +6,12 @@
* 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/>.
*/
@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardUtil;
@@ -44,7 +46,7 @@ import java.util.Map;
* <p>
* Target_Selection class.
* </p>
*
*
* @author Forge
* @version $Id: TargetSelection.java 25148 2014-03-12 08:28:52Z swordshine $
*/
@@ -63,14 +65,18 @@ public class TargetSelection {
private boolean bTargetingDone = false;
private boolean isMandatory() {
return ability.isMandatory() || getTgt().getMandatory();
}
public final boolean chooseTargets(Integer numTargets) {
final TargetRestrictions tgt = getTgt();
final boolean canTarget = tgt != null && tgt.doesTarget();
if (!canTarget) {
throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability);
}
// Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect)
// Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect)
final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability);
final int maxTargets = numTargets != null ? numTargets.intValue() : tgt.getMaxTargets(ability.getHostCard(), ability);
//final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability);
@@ -95,14 +101,14 @@ public class TargetSelection {
// Cancel ability if there aren't any valid Candidates
return false;
}
if (tgt.getMandatory() && !hasCandidates && hasEnoughTargets) {
if (isMandatory() && !hasCandidates && hasEnoughTargets) {
// Mandatory target selection, that has no candidates but enough targets (Min == 0, but no choices)
return true;
}
final List<ZoneType> zones = tgt.getZone();
final boolean mandatory = tgt.getMandatory() && hasCandidates;
final boolean mandatory = isMandatory() && hasCandidates;
final boolean choiceResult;
final boolean random = tgt.isRandomTarget();
if (random) {
@@ -167,7 +173,7 @@ public class TargetSelection {
choiceResult = this.chooseCardFromList(validTargets, true, mandatory);
}
}
// some inputs choose cards one-by-one and need to be called again
// some inputs choose cards one-by-one and need to be called again
return choiceResult && chooseTargets(numTargets);
}
@@ -175,6 +181,8 @@ public class TargetSelection {
// Send in a list of valid cards, and popup a choice box to target
final Game game = ability.getActivatingPlayer().getGame();
GameEntityViewMap<Card, CardView> gameCacheChooseCard = GameEntityView.getMap(choices);
final List<CardView> crdsBattle = Lists.newArrayList();
final List<CardView> crdsExile = Lists.newArrayList();
final List<CardView> crdsGrave = Lists.newArrayList();
@@ -228,7 +236,7 @@ public class TargetSelection {
// is there a more elegant way of doing this?
choicesFiltered.add(msgDone);
}
Object chosen = null;
if (!choices.isEmpty() && mandatory) {
chosen = controller.getGui().one(getTgt().getVTSelection(), choicesFiltered);
@@ -245,7 +253,10 @@ public class TargetSelection {
}
if (chosen instanceof CardView) {
ability.getTargets().add(game.getCard((CardView) chosen));
if (!gameCacheChooseCard.containsKey(chosen)) {
return false;
}
ability.getTargets().add(gameCacheChooseCard.get((CardView) chosen));
}
return true;
}
@@ -292,7 +303,7 @@ public class TargetSelection {
if (madeChoice instanceof StackItemView) {
ability.getTargets().add(stackItemViewCache.get(madeChoice).getSpellAbility(true));
}
else {// 'FINISH TARGETING' chosen
else {// 'FINISH TARGETING' chosen
bTargetingDone = true;
}
}

View File

@@ -47,6 +47,8 @@ public final class ForgeConstants {
public static final String IMAGE_LIST_ACHIEVEMENTS_FILE = LISTS_DIR + "achievement-images.txt";
public static final String NET_DECKS_LIST_FILE = LISTS_DIR + "net-decks.txt";
public static final String NET_DECKS_COMMANDER_LIST_FILE = LISTS_DIR + "net-decks-commander.txt";
public static final String NET_DECKS_BRAWL_LIST_FILE = LISTS_DIR + "net-decks-brawl.txt";
public static final String CHANGES_FILE = ASSETS_DIR + "README.txt";
public static final String CHANGES_FILE_NO_RELEASE = ASSETS_DIR + "CHANGES.txt";
@@ -69,6 +71,7 @@ public final class ForgeConstants {
public static final String LANG_DIR = RES_DIR + "languages" + PATH_SEPARATOR;
public static final String EFFECTS_DIR = RES_DIR + "effects" + PATH_SEPARATOR;
public static final String PUZZLE_DIR = RES_DIR + "puzzle" + PATH_SEPARATOR;
public static final String TUTORIAL_DIR = RES_DIR + "tutorial" + PATH_SEPARATOR;
public static final String DECK_GEN_DIR = RES_DIR + "deckgendecks" + PATH_SEPARATOR;
@@ -237,6 +240,7 @@ public final class ForgeConstants {
public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences";
public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<";
// data that has defaults in the program dir but overrides/additions in the user dir
private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR;

View File

@@ -84,6 +84,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
UI_IMAGE_CACHE_MAXIMUM("400"),
UI_OVERLAY_FOIL_EFFECT ("true"),
UI_HIDE_REMINDER_TEXT ("false"),
UI_SR_OPTIMIZE ("false"),
UI_OPEN_PACKS_INDIV ("false"),
UI_STACK_CREATURES ("false"),
UI_UPLOAD_DRAFT ("false"),
@@ -139,7 +140,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
UI_ENABLE_PRELOAD_EXTENDED_ART("false"),
UI_ENABLE_BORDER_MASKING("false"),
UI_SHOW_FPS("false"),
UI_USE_ELSA("false"),
UI_NETPLAY_COMPAT("false"),
UI_LOAD_UNKNOWN_CARDS("true"),
UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("Never"),
UI_DEFAULT_FONT_SIZE("12"),

View File

@@ -16,10 +16,10 @@ public class PuzzleIO {
public static final String SUFFIX_DATA = ".pzl";
public static final String SUFFIX_COMPLETE = ".complete";
public static ArrayList<Puzzle> loadPuzzles() {
public static ArrayList<Puzzle> loadPuzzles(String directory) {
String[] pList;
// get list of puzzles
final File pFolder = new File(ForgeConstants.PUZZLE_DIR);
final File pFolder = new File(directory);
if (!pFolder.exists()) {
throw new RuntimeException("Puzzles : folder not found -- folder is " + pFolder.getAbsolutePath());
}
@@ -33,7 +33,7 @@ public class PuzzleIO {
ArrayList<Puzzle> puzzles = Lists.newArrayList();
for (final String element : pList) {
if (element.endsWith(SUFFIX_DATA)) {
final List<String> pfData = FileUtil.readFile(ForgeConstants.PUZZLE_DIR + element);
final List<String> pfData = FileUtil.readFile(directory + element);
String filename = element.replace(SUFFIX_DATA, "");
boolean completed = FileUtil.doesFileExist(ForgeConstants.USER_PUZZLE_DIR + element.replace(SUFFIX_DATA, SUFFIX_COMPLETE));
@@ -48,9 +48,4 @@ public class PuzzleIO {
public static Map<String, List<String>> parsePuzzleSections(List<String> pfData) {
return FileSection.parseSections(pfData);
}
public static File getPuzzleFile(final String name) {
return new File(ForgeConstants.PUZZLE_DIR, name + SUFFIX_DATA);
}
}

View File

@@ -23,7 +23,7 @@ import java.util.List;
* This class is used to store the Quest starting pool preferences.
* (It could be expanded to store other Quest starting preferences as well,
* in order to reduce the number of parameters that need to be passed to
* QuestController.newGame from CSubmenuQuestData)
* QuestController.newGame from CSubmenuQuestStart)
*
*/
public final class StartingPoolPreferences {

View File

@@ -1,19 +1,21 @@
package forge.quest;
import forge.util.Localizer;
public enum StartingPoolType {
Complete("Unrestricted"),
Sanctioned("Sanctioned format"),
Casual("Casual/Historic format"),
CustomFormat("Custom format"),
Precon("Event or starter deck"),
SealedDeck("My sealed deck"),
DraftDeck("My draft deck"),
Cube("Predefined cube");
Complete("lblUnrestricted"),
Sanctioned("lblSanctionedFormat"),
Casual("lblCasualOrHistoricFormat"),
CustomFormat("lblCustomFormat"),
Precon("lblEventOrStartDeck"),
SealedDeck("lblMySealedDeck"),
DraftDeck("lblMyDraftDeck"),
Cube("lblPredefinedCube");
private final String caption;
StartingPoolType(String caption0) {
caption = caption0;
caption = Localizer.getInstance().getMessage(caption0);
}
/* (non-Javadoc)

View File

@@ -296,6 +296,9 @@ public class QuestAchievements {
catch(ArrayIndexOutOfBoundsException e) {
return null;
}
catch(IndexOutOfBoundsException e) {
return null;
}
}

View File

@@ -22,6 +22,7 @@ import forge.game.event.GameEventCardSacrificed;
import forge.game.event.GameEventCardTapped;
import forge.game.event.GameEventFlipCoin;
import forge.game.event.GameEventGameOutcome;
import forge.game.event.GameEventGameStarted;
import forge.game.event.GameEventLandPlayed;
import forge.game.event.GameEventManaBurn;
import forge.game.event.GameEventPlayerLivesChanged;
@@ -47,6 +48,11 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> imp
this.player = lobbyPlayer;
}
@Override
public SoundEffectType visit(GameEventGameStarted event) {
return SoundEffectType.StartDuel;
}
@Override
public SoundEffectType visit(final GameEventCardDamaged event) { return SoundEffectType.Damage; }
@Override

View File

@@ -83,6 +83,7 @@ public enum SoundEffectType {
ScriptedEffect("", false), // Plays the effect defined by SVar:SoundEffect
Shuffle("shuffle.wav", false),
Sorcery("sorcery.wav", false),
StartDuel("start_duel.wav",false),
Tap("tap.wav", false),
Token("token.wav", true),
Untap("untap.wav", true),

View File

@@ -51,8 +51,12 @@ public class SOptionPane {
}
public static boolean showConfirmDialog(final String message, final String title, final String yesButtonText, final String noButtonText, final boolean defaultYes) {
return showConfirmDialog(message, title, yesButtonText, noButtonText, defaultYes, false);
}
public static boolean showConfirmDialog(final String message, final String title, final String yesButtonText, final String noButtonText, final boolean defaultYes, final boolean noicon) {
final List<String> options = ImmutableList.of(yesButtonText, noButtonText);
final int reply = SOptionPane.showOptionDialog(message, title, QUESTION_ICON, options, defaultYes ? 0 : 1);
final int reply = SOptionPane.showOptionDialog(message, title, noicon ? null : QUESTION_ICON, options, defaultYes ? 0 : 1);
return (reply == 0);
}