From 51b266a2de7e08e51647bd975a525b4bedd12579 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 15 Mar 2018 10:47:31 +0100 Subject: [PATCH 01/65] fixed https://sonarcloud.io/project/issues?id=org.xmage%3Amage-root&issues=AWIlv32RgrzAwlaaQ7rP&open=AWIlv32RgrzAwlaaQ7rP --- Mage.Client/src/main/java/mage/client/cards/ManaPieChart.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/cards/ManaPieChart.java b/Mage.Client/src/main/java/mage/client/cards/ManaPieChart.java index a8d447e700d..cf33d0e1e71 100644 --- a/Mage.Client/src/main/java/mage/client/cards/ManaPieChart.java +++ b/Mage.Client/src/main/java/mage/client/cards/ManaPieChart.java @@ -67,6 +67,10 @@ public class ManaPieChart extends JComponent { for (int i = 0; i < slices.length; i++) { total += slices[i].value; } + + if (total == 0.0D) { + return; //there are no slices or no slices with a value > 0, stop here + } double curValue = 0.0D; int startAngle = 0; From 7b3d6a60b6568f9dbed21947155ccd2f0efc7bf9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 10:52:53 +0100 Subject: [PATCH 02/65] ensure closing of scanner if it was opened --- .../src/mage/player/ai/ComputerPlayer6.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 287b31752c6..6d032c83daa 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -957,10 +957,11 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } protected final void getSuggestedActions() { + Scanner scanner = null; try { File file = new File(FILE_WITH_INSTRUCTIONS); if (file.exists()) { - Scanner scanner = new Scanner(file); + scanner = new Scanner(file); while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("cast:") @@ -976,6 +977,10 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } catch (Exception e) { // swallow e.printStackTrace(); + } finally { + if(scanner != null) { + scanner.close(); + } } } From d0bde94ffe1fc06ace16a0e9cd4ce6eade6a8b80 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 11:57:15 +0100 Subject: [PATCH 03/65] Refactored method in EmpyrialArchAngel to not always return same value. --- Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java b/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java index 133675ffb77..c1c4a8bfcb8 100644 --- a/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java +++ b/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java @@ -90,8 +90,7 @@ class EmpyrialArchangelEffect extends ReplacementEffectImpl { DamagePlayerEvent damageEvent = (DamagePlayerEvent) event; Permanent p = game.getPermanent(source.getSourceId()); if (p != null) { - p.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable()); - return true; + p.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable());l } return true; } From 5096410aec96675c308ad519756782929403d8c5 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 11:57:49 +0100 Subject: [PATCH 04/65] Refactored method in FalkenrathAristocrat to not always return same value. --- Mage.Sets/src/mage/cards/f/FalkenrathAristocrat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathAristocrat.java b/Mage.Sets/src/mage/cards/f/FalkenrathAristocrat.java index 71962fce827..5dfba5bc4a7 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathAristocrat.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathAristocrat.java @@ -105,7 +105,7 @@ class FalkenrathAristocratEffect extends OneShotEffect { Permanent sourceCreature = game.getPermanent(source.getSourceId()); if (sacrificedCreature.hasSubtype(SubType.HUMAN, game) && sourceCreature != null) { sourceCreature.addCounters(CounterType.P1P1.createInstance(), source, game); - return true; + break; } } } From a490f9e468ec4ea7fdd042772d72f6b250449335 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 11:58:10 +0100 Subject: [PATCH 05/65] Refactored method in GilderBairn to not always return the same value. --- Mage.Sets/src/mage/cards/g/GilderBairn.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GilderBairn.java b/Mage.Sets/src/mage/cards/g/GilderBairn.java index 1ce80895467..7f71ffd6266 100644 --- a/Mage.Sets/src/mage/cards/g/GilderBairn.java +++ b/Mage.Sets/src/mage/cards/g/GilderBairn.java @@ -95,12 +95,11 @@ class GilderBairnEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent target = game.getPermanent(source.getFirstTarget()); - if (target == null) { - return false; - } - for (Counter counter : target.getCounters(game).values()) { - Counter newCounter = new Counter(counter.getName(), counter.getCount()); - target.addCounters(newCounter, source, game); + if (target != null) { + for (Counter counter : target.getCounters(game).values()) { + Counter newCounter = new Counter(counter.getName(), counter.getCount()); + target.addCounters(newCounter, source, game); + } } return false; } From 9187248e85eac88483a6ab271e755e00b87dfcf1 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 12:01:25 +0100 Subject: [PATCH 06/65] fixed left open resources, ensured quiet closing of the streams --- .../magefree/update/helpers/FileHelper.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Mage.Updater/src/main/java/com/magefree/update/helpers/FileHelper.java b/Mage.Updater/src/main/java/com/magefree/update/helpers/FileHelper.java index 63b9d873e13..d23c0bdf4c5 100644 --- a/Mage.Updater/src/main/java/com/magefree/update/helpers/FileHelper.java +++ b/Mage.Updater/src/main/java/com/magefree/update/helpers/FileHelper.java @@ -4,7 +4,7 @@ import java.io.*; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.List; - +import java.io.Closeable; /** * Helper for file operations. * @@ -84,15 +84,17 @@ public final class FileHelper { */ public static void downloadFile(String filename, HttpURLConnection urlConnection) { System.out.println("Downloading " + filename); + InputStream in = null; + FileOutputStream out = null; try { - InputStream in = urlConnection.getInputStream(); + in = urlConnection.getInputStream(); File f = new File(filename); if (!f.exists() && f.getParentFile() != null) { f.getParentFile().mkdirs(); System.out.println("Directories have been created: " + f.getParentFile().getPath()); } - FileOutputStream out = new FileOutputStream(filename); + out = new FileOutputStream(filename); byte[] buf = new byte[4 * 1024]; int bytesRead; @@ -103,6 +105,19 @@ public final class FileHelper { System.out.println("File has been updated: " + filename); } catch (IOException e) { System.out.println("i/o exception - " + e.getMessage()); + } finally { + closeQuietly(in); + closeQuietly(out); + } + } + + public static void closeQuietly(Closeable s) { + if(s != null) { + try { + s.close(); + } catch (Exception e) { + System.out.println("i/o exception - " + e.getMessage()); + } } } } From becae01f73dbc110b9ef9622b5196aaee617834d Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 12:02:48 +0100 Subject: [PATCH 07/65] Refactored method in IceCave to not always return same value. --- Mage.Sets/src/mage/cards/i/IceCave.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/i/IceCave.java b/Mage.Sets/src/mage/cards/i/IceCave.java index 188f79f42b2..bcdf7f4d6f5 100644 --- a/Mage.Sets/src/mage/cards/i/IceCave.java +++ b/Mage.Sets/src/mage/cards/i/IceCave.java @@ -102,7 +102,7 @@ class IceCaveEffect extends OneShotEffect { if (cost.pay(source, game, source.getSourceId(), playerId, false, null)) { game.informPlayers(player.getLogName() + " pays" + cost.getText() + " to counter " + spell.getIdName() + '.'); game.getStack().counter(spell.getId(), source.getSourceId(), game); - return true; + break; } } } From dfba15351927844dc5c7e8c85bf39935d832e097 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 12:03:09 +0100 Subject: [PATCH 08/65] Refactored method in KjeldoranRoyalGuard to not always return same value. --- Mage.Sets/src/mage/cards/k/KjeldoranRoyalGuard.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/k/KjeldoranRoyalGuard.java b/Mage.Sets/src/mage/cards/k/KjeldoranRoyalGuard.java index 87b11712502..0ff4c0e5036 100644 --- a/Mage.Sets/src/mage/cards/k/KjeldoranRoyalGuard.java +++ b/Mage.Sets/src/mage/cards/k/KjeldoranRoyalGuard.java @@ -99,7 +99,6 @@ class KjeldoranRoyalGuardEffect extends ReplacementEffectImpl { Permanent p = game.getPermanent(source.getSourceId()); if (p != null) { p.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable()); - return true; } return true; } From 3ab65c1d860953d1c420a658c0c5714cadc4db4c Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 12:05:12 +0100 Subject: [PATCH 09/65] Refactored method in LegionsInitiative to not always return same value. --- Mage.Sets/src/mage/cards/l/LegionsInitiative.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/l/LegionsInitiative.java b/Mage.Sets/src/mage/cards/l/LegionsInitiative.java index 3206227ed38..e20c35f1dcc 100644 --- a/Mage.Sets/src/mage/cards/l/LegionsInitiative.java +++ b/Mage.Sets/src/mage/cards/l/LegionsInitiative.java @@ -129,7 +129,6 @@ class LegionsInitiativeExileEffect extends OneShotEffect { //create delayed triggered ability AtTheBeginOfCombatDelayedTriggeredAbility delayedAbility = new AtTheBeginOfCombatDelayedTriggeredAbility(new LegionsInitiativeReturnFromExileEffect()); game.addDelayedTriggeredAbility(delayedAbility, source); - return true; } return true; } From 22d8a4ab36e81e6233b454a2ad73daf018a262f4 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 12:13:11 +0100 Subject: [PATCH 10/65] Refactored method in NaturesWill to not always return same value. --- Mage.Sets/src/mage/cards/n/NaturesWill.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Mage.Sets/src/mage/cards/n/NaturesWill.java b/Mage.Sets/src/mage/cards/n/NaturesWill.java index 215999aa9be..c6a2e8bbc06 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesWill.java +++ b/Mage.Sets/src/mage/cards/n/NaturesWill.java @@ -84,19 +84,16 @@ class NaturesWillEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Set damagedPlayers = (HashSet) this.getValue("damagedPlayers"); - if (damagedPlayers == null) { - return false; - } - - List lands = game.getBattlefield().getActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), source.getSourceId(), game); - for (Permanent land : lands) { - if (damagedPlayers.contains(land.getControllerId())) { - land.tap(game); - } else if (land.getControllerId().equals(source.getControllerId())) { - land.untap(game); + if (damagedPlayers != null) { + List lands = game.getBattlefield().getActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), source.getSourceId(), game); + for (Permanent land : lands) { + if (damagedPlayers.contains(land.getControllerId())) { + land.tap(game); + } else if (land.getControllerId().equals(source.getControllerId())) { + land.untap(game); + } } } - return false; } } From 9912a230078af135dd1cd609a0f00a97b63706cc Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 12:46:53 +0100 Subject: [PATCH 11/65] added quiet closing method in new streamutils class, used to clean up the connectdialog --- .../mage/client/dialog/ConnectDialog.java | 18 +++++++--------- .../src/main/java/mage/utils/StreamUtils.java | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 Mage.Common/src/main/java/mage/utils/StreamUtils.java diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java index 5a613389dcb..4f09e31eea3 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -43,6 +43,7 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.Writer; +import java.io.Closeable; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketException; @@ -75,6 +76,7 @@ import mage.client.util.Config; import mage.client.util.gui.countryBox.CountryItemEditor; import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; +import mage.utils.StreamUtils; import org.apache.log4j.Logger; /** @@ -567,6 +569,7 @@ public class ConnectDialog extends MageDialog { private void findPublicServerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed BufferedReader in = null; + Writer output = null; try { String serverUrl = PreferencesDialog.getCachedValue(KEY_CONNECTION_URL_SERVER_LIST, "http://xmage.de/files/server-list.txt"); if (serverUrl.contains("xmage.info/files/")) { @@ -620,7 +623,7 @@ public class ConnectDialog extends MageDialog { } List servers = new ArrayList<>(); if (in != null) { - Writer output = null; + if (!URLNotFound) { // write serverlist to be able to read if URL is not available File file = new File("serverlist.txt"); @@ -639,10 +642,6 @@ public class ConnectDialog extends MageDialog { } } - if (output != null) { - output.close(); - } - in.close(); } if (servers.isEmpty()) { JOptionPane.showMessageDialog(null, "Couldn't find any server."); @@ -670,15 +669,12 @@ public class ConnectDialog extends MageDialog { } catch (Exception ex) { logger.error(ex, ex); } finally { - if (in != null) { - try { - in.close(); - } catch (Exception e) { - } - } + StreamUtils.closeQuietly(in); + StreamUtils.closeQuietly(output); } }//GEN-LAST:event_jButton1ActionPerformed + private void jProxySettingsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jProxySettingsButtonActionPerformed PreferencesDialog.main(new String[]{PreferencesDialog.OPEN_CONNECTION_TAB}); }//GEN-LAST:event_jProxySettingsButtonActionPerformed diff --git a/Mage.Common/src/main/java/mage/utils/StreamUtils.java b/Mage.Common/src/main/java/mage/utils/StreamUtils.java new file mode 100644 index 00000000000..8fadb33adc5 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/StreamUtils.java @@ -0,0 +1,21 @@ +package mage.utils; + +import java.io.Closeable; + +public final class StreamUtils { + + /*** + * Quietly closes the closable, ignoring nulls and exceptions + * @param c - the closable to be closed + */ + public static void closeQuietly(Closeable c) { + if (c != null) { + try { + c.close(); + } + catch (Exception e) { + } + } + } + +} From 1799143be070116d786336f4528d335cfd16dc3c Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 12:53:04 +0100 Subject: [PATCH 12/65] Fix small typo --- Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java b/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java index c1c4a8bfcb8..f622375b462 100644 --- a/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java +++ b/Mage.Sets/src/mage/cards/e/EmpyrialArchangel.java @@ -90,7 +90,7 @@ class EmpyrialArchangelEffect extends ReplacementEffectImpl { DamagePlayerEvent damageEvent = (DamagePlayerEvent) event; Permanent p = game.getPermanent(source.getSourceId()); if (p != null) { - p.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable());l + p.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable()); } return true; } From 7233f5d86f569472374bacfed2402642ed2d87bd Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 12:55:37 +0100 Subject: [PATCH 13/65] added quiet closing to saveobjectutil --- .../java/mage/client/util/object/SaveObjectUtil.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/util/object/SaveObjectUtil.java b/Mage.Client/src/main/java/mage/client/util/object/SaveObjectUtil.java index 397da1cff03..c319f27b5bd 100644 --- a/Mage.Client/src/main/java/mage/client/util/object/SaveObjectUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/object/SaveObjectUtil.java @@ -1,5 +1,7 @@ package mage.client.util.object; +import mage.utils.StreamUtils; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -61,10 +63,9 @@ public final class SaveObjectUtil { oos.writeObject(object); oos.close(); - } catch (FileNotFoundException e) { - return; - } catch (IOException io) { - return; + } catch (Exception e) { + } finally { + StreamUtils.closeQuietly(oos); } } } From 532a1905879d69a7e42365eff20bc01284eb0cb9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 13:52:23 +0100 Subject: [PATCH 14/65] closed resources in savegame method of gamecontroller --- .../src/main/java/mage/utils/StreamUtils.java | 9 +++++++++ .../java/mage/server/game/GameController.java | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Mage.Common/src/main/java/mage/utils/StreamUtils.java b/Mage.Common/src/main/java/mage/utils/StreamUtils.java index 8fadb33adc5..1b228b454a7 100644 --- a/Mage.Common/src/main/java/mage/utils/StreamUtils.java +++ b/Mage.Common/src/main/java/mage/utils/StreamUtils.java @@ -18,4 +18,13 @@ public final class StreamUtils { } } + public static void closeQuietly(AutoCloseable ac) { + if (ac != null) { + try { + ac.close(); + } + catch (Exception e) { + } + } + } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 8082d6576da..8bce119961f 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -64,6 +64,7 @@ import mage.server.util.ConfigSettings; import mage.server.util.Splitter; import mage.server.util.SystemUtil; import mage.server.util.ThreadExecutor; +import mage.utils.StreamUtils; import mage.utils.timer.PriorityTimer; import mage.view.*; import mage.view.ChatMessage.MessageColor; @@ -902,17 +903,23 @@ public class GameController implements GameCallback { } public boolean saveGame() { + OutputStream file = null; + ObjectOutput output = null; + OutputStream buffer = null; try { - OutputStream file = new FileOutputStream("saved/" + game.getId().toString() + ".game"); - OutputStream buffer = new BufferedOutputStream(file); - try (ObjectOutput output = new ObjectOutputStream(new GZIPOutputStream(buffer))) { - output.writeObject(game); - output.writeObject(game.getGameStates()); - } + file = new FileOutputStream("saved/" + game.getId().toString() + ".game"); + buffer = new BufferedOutputStream(file); + output = new ObjectOutputStream(new GZIPOutputStream(buffer)); + output.writeObject(game); + output.writeObject(game.getGameStates()); logger.debug("Saved game:" + game.getId()); return true; } catch (IOException ex) { logger.fatal("Cannot save game.", ex); + } finally { + StreamUtils.closeQuietly(file); + StreamUtils.closeQuietly(output); + StreamUtils.closeQuietly(buffer); } return false; } From 2a9d23221ed34f3d1ce871540545eec926dcccd9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 13:58:46 +0100 Subject: [PATCH 15/65] properly close resources in loadGame method of GameReplay class --- .../java/mage/server/game/GameReplay.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/game/GameReplay.java b/Mage.Server/src/main/java/mage/server/game/GameReplay.java index 820bc9a6d32..c8834b8628d 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameReplay.java +++ b/Mage.Server/src/main/java/mage/server/game/GameReplay.java @@ -40,6 +40,7 @@ import mage.game.GameState; import mage.game.GameStates; import mage.server.Main; import mage.util.CopierObjectInputStream; +import mage.utils.StreamUtils; import org.apache.log4j.Logger; @@ -84,21 +85,28 @@ public class GameReplay { } private Game loadGame(UUID gameId) { + InputStream file = null; + InputStream buffer = null; + ObjectInput input = null; try{ - InputStream file = new FileInputStream("saved/" + gameId.toString() + ".game"); - InputStream buffer = new BufferedInputStream(file); - try (ObjectInput input = new CopierObjectInputStream(Main.classLoader, new GZIPInputStream(buffer))) { - Game loadGame = (Game) input.readObject(); - GameStates states = (GameStates) input.readObject(); - loadGame.loadGameStates(states); - return loadGame; - } + file = new FileInputStream("saved/" + gameId.toString() + ".game"); + buffer = new BufferedInputStream(file); + input = new CopierObjectInputStream(Main.classLoader, new GZIPInputStream(buffer)) + Game loadGame = (Game) input.readObject(); + GameStates states = (GameStates) input.readObject(); + loadGame.loadGameStates(states); + return loadGame; + } catch(ClassNotFoundException ex) { logger.fatal("Cannot load game. Class not found.", ex); } catch(IOException ex) { logger.fatal("Cannot load game:" + gameId, ex); + } finally { + StreamUtils.closeQuietly(file); + StreamUtils.closeQuietly(buffer); + StreamUtils.closeQuietly(input); } return null; } From 91b538be63c370a3f2e1d8b0b67d9080f0567e9b Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:10:03 +0100 Subject: [PATCH 16/65] further proper resource closing in ServerMessagesUtil --- .../mage/server/util/ServerMessagesUtil.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java index d596f8a6538..2cedad7aa88 100644 --- a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java @@ -27,6 +27,7 @@ */ package mage.server.util; +import mage.utils.StreamUtils; import org.apache.log4j.Logger; import java.io.File; @@ -49,7 +50,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * @author nantuko */ public enum ServerMessagesUtil { -instance; + instance; private static final Logger log = Logger.getLogger(ServerMessagesUtil.class); private static final String SERVER_MSG_TXT_FILE = "server.msg.txt"; private ScheduledExecutorService updateExecutor; @@ -147,13 +148,22 @@ instance; log.warn("Couldn't find server.msg"); return null; } - Scanner scanner = new Scanner(is); + + Scanner scanner = null; List newMessages = new ArrayList<>(); - while (scanner.hasNextLine()) { - String message = scanner.nextLine(); - if (!message.trim().isEmpty()) { - newMessages.add(message.trim()); + try { + scanner = new Scanner(is); + while (scanner.hasNextLine()) { + String message = scanner.nextLine(); + if (!message.trim().isEmpty()) { + newMessages.add(message.trim()); + } } + } catch(Exception e) { + log.error(e,e); + } finally { + StreamUtils.closeQuietly(scanner); + StreamUtils.closeQuietly(is); } return newMessages; } From dc25eedfc38a4e1de0b78fbcd0955766e1ebcfe6 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:18:42 +0100 Subject: [PATCH 17/65] fixed unclosed resources in copy method in mage framework Copier --- .../java/mage/server/game/GameReplay.java | 2 +- Mage/src/main/java/mage/util/Copier.java | 15 +++++++--- Mage/src/main/java/mage/util/StreamUtils.java | 30 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 Mage/src/main/java/mage/util/StreamUtils.java diff --git a/Mage.Server/src/main/java/mage/server/game/GameReplay.java b/Mage.Server/src/main/java/mage/server/game/GameReplay.java index c8834b8628d..dd41f89c10f 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameReplay.java +++ b/Mage.Server/src/main/java/mage/server/game/GameReplay.java @@ -91,7 +91,7 @@ public class GameReplay { try{ file = new FileInputStream("saved/" + gameId.toString() + ".game"); buffer = new BufferedInputStream(file); - input = new CopierObjectInputStream(Main.classLoader, new GZIPInputStream(buffer)) + input = new CopierObjectInputStream(Main.classLoader, new GZIPInputStream(buffer)); Game loadGame = (Game) input.readObject(); GameStates states = (GameStates) input.readObject(); loadGame.loadGameStates(states); diff --git a/Mage/src/main/java/mage/util/Copier.java b/Mage/src/main/java/mage/util/Copier.java index e6b7aa4b490..df5b6772be5 100644 --- a/Mage/src/main/java/mage/util/Copier.java +++ b/Mage/src/main/java/mage/util/Copier.java @@ -50,22 +50,29 @@ public class Copier { public T copy(T obj) { T copy = null; + + FastByteArrayOutputStream fbos = null; + ObjectOutputStream out = null; + ObjectInputStream in = null; try { - FastByteArrayOutputStream fbos = new FastByteArrayOutputStream(); - ObjectOutputStream out= new ObjectOutputStream(fbos); + fbos = new FastByteArrayOutputStream(); + out = new ObjectOutputStream(fbos); // Write the object out to a byte array out.writeObject(obj); out.flush(); - out.close(); // Retrieve an input stream from the byte array and read // a copy of the object back in. - ObjectInputStream in = new CopierObjectInputStream(loader, fbos.getInputStream()); + in = new CopierObjectInputStream(loader, fbos.getInputStream()); copy = (T) in.readObject(); } catch(IOException | ClassNotFoundException e) { e.printStackTrace(); + } finally { + StreamUtils.closeQuietly(fbos); + StreamUtils.closeQuietly(out); + StreamUtils.closeQuietly(in); } return copy; diff --git a/Mage/src/main/java/mage/util/StreamUtils.java b/Mage/src/main/java/mage/util/StreamUtils.java new file mode 100644 index 00000000000..2be62157c39 --- /dev/null +++ b/Mage/src/main/java/mage/util/StreamUtils.java @@ -0,0 +1,30 @@ +package mage.util; + +import java.io.Closeable; + +public final class StreamUtils { + + /*** + * Quietly closes the closable, ignoring nulls and exceptions + * @param c - the closable to be closed + */ + public static void closeQuietly(Closeable c) { + if (c != null) { + try { + c.close(); + } + catch (Exception e) { + } + } + } + + public static void closeQuietly(AutoCloseable ac) { + if (ac != null) { + try { + ac.close(); + } + catch (Exception e) { + } + } + } +} From b2e2f48ad9b1f65cd44e67f81d60a8473f6be6c2 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:20:31 +0100 Subject: [PATCH 18/65] closed unclosed resources in copyCompressed method in Copier --- Mage/src/main/java/mage/util/Copier.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/util/Copier.java b/Mage/src/main/java/mage/util/Copier.java index df5b6772be5..56c23854248 100644 --- a/Mage/src/main/java/mage/util/Copier.java +++ b/Mage/src/main/java/mage/util/Copier.java @@ -79,14 +79,15 @@ public class Copier { } public byte[] copyCompressed(T obj) { + FastByteArrayOutputStream fbos = null; + ObjectOutputStream out = null; try { - FastByteArrayOutputStream fbos = new FastByteArrayOutputStream(); - ObjectOutputStream out= new ObjectOutputStream(new GZIPOutputStream(fbos)); + fbos = new FastByteArrayOutputStream(); + out = new ObjectOutputStream(new GZIPOutputStream(fbos)); // Write the object out to a byte array out.writeObject(obj); out.flush(); - out.close(); byte[] copy = new byte[fbos.getSize()]; System.arraycopy(fbos.getByteArray(), 0, copy, 0, fbos.getSize()); @@ -94,6 +95,9 @@ public class Copier { } catch(IOException e) { e.printStackTrace(); + } finally { + StreamUtils.closeQuietly(fbos); + StreamUtils.closeQuietly(out); } return null; } From a0a2ac1b5ad99ba639b2bbc4d3e495bca39b3545 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:40:58 +0100 Subject: [PATCH 19/65] ensure closing of filewriter in manasymbols --- .../main/java/org/mage/card/arcane/ManaSymbols.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index f5c1af5d52d..9350ff3a5b0 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -41,6 +41,7 @@ import mage.client.constants.Constants.ResourceSymbolSize; import mage.client.util.GUISizeHelper; import mage.client.util.ImageHelper; import mage.client.util.gui.BufferedImageBuilder; +import mage.utils.StreamUtils; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; @@ -249,10 +250,15 @@ public final class ManaSymbols { + "color-rendering: optimizeQuality;" + "image-rendering: optimizeQuality;" + "}"; + File cssFile = File.createTempFile("batik-default-override-", ".css"); - FileWriter w = new FileWriter(cssFile); - w.write(css); - w.close(); + FileWriter w = null; + try { + w = new FileWriter(cssFile); + w.write(css); + } finally { + StreamUtils.closeQuietly(w); + } TranscodingHints transcoderHints = new TranscodingHints(); @@ -284,7 +290,6 @@ public final class ManaSymbols { try { TranscoderInput input = new TranscoderInput(new FileInputStream(svgFile)); - ImageTranscoder t = new ImageTranscoder() { @Override From 63dbfb8a9b458b1e18c46ae5c9cb9faf3196ca29 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:48:22 +0100 Subject: [PATCH 20/65] ensure proper closing of Stream in arcane UI --- Mage.Client/src/main/java/org/mage/card/arcane/UI.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/UI.java b/Mage.Client/src/main/java/org/mage/card/arcane/UI.java index 0b2ed25f00f..d07264d8a85 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/UI.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/UI.java @@ -1,5 +1,7 @@ package org.mage.card.arcane; +import mage.utils.StreamUtils; + import java.awt.Component; import java.awt.Container; import java.awt.Dimension; @@ -72,8 +74,8 @@ public final class UI { } public static ImageIcon getImageIcon (String path) { + InputStream stream = null; try { - InputStream stream; stream = UI.class.getResourceAsStream(path); if (stream == null && new File(path).exists()) { stream = new FileInputStream(path); @@ -86,6 +88,8 @@ public final class UI { return new ImageIcon(data); } catch (IOException ex) { throw new RuntimeException("Error reading image: " + path); + } finally { + StreamUtils.closeQuietly(stream); } } From 338e9833cce6b7b2d78a66ee6c464ddc03f313e8 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 14:59:41 +0100 Subject: [PATCH 21/65] ensure closing of datagram socket in arcane Util --- .../src/main/java/org/mage/card/arcane/Util.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java index 57f704056f9..2645c314dbb 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java @@ -37,9 +37,13 @@ public final class Util { } public static void broadcast(byte[] data, int port) throws IOException { - DatagramSocket socket = new DatagramSocket(); - broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces()); - socket.close(); + DatagramSocket socket = null; + try { + socket = new DatagramSocket(); + broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces()); + } finally { + socket.close(); + } } private static void broadcast(DatagramSocket socket, byte[] data, int port, Enumeration ifaces) From 1ec5a3fb4d3822f9edd20a04b3f227688f51007c Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:00:08 +0100 Subject: [PATCH 22/65] ensure resource closing in deckimport from clipboard --- .../client/deckeditor/DeckImportFromClipboardDialog.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportFromClipboardDialog.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportFromClipboardDialog.java index 1eeb251ad40..eef1987dfaa 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportFromClipboardDialog.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportFromClipboardDialog.java @@ -1,5 +1,7 @@ package mage.client.deckeditor; +import mage.util.StreamUtils; + import java.awt.*; import java.awt.event.*; import java.io.BufferedWriter; @@ -39,15 +41,16 @@ public class DeckImportFromClipboardDialog extends JDialog { } private void onOK() { + BufferedWriter bw = null; try { File temp = File.createTempFile("cbimportdeck", ".txt"); - BufferedWriter bw = new BufferedWriter(new FileWriter(temp)); + bw = new BufferedWriter(new FileWriter(temp)); bw.write(txtDeckList.getText()); - bw.close(); - tmpPath = temp.getPath(); } catch (IOException e) { e.printStackTrace(); + } finally { + StreamUtils.closeQuietly(bw); } dispose(); From ccb0d3da523e66097d1ad8086db47fff49720eba Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:11:59 +0100 Subject: [PATCH 23/65] ensure closing of plugin classloader --- .../main/java/mage/server/ExtensionPackageLoader.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ExtensionPackageLoader.java b/Mage.Server/src/main/java/mage/server/ExtensionPackageLoader.java index 6c5b3f80adc..8894ae6720b 100644 --- a/Mage.Server/src/main/java/mage/server/ExtensionPackageLoader.java +++ b/Mage.Server/src/main/java/mage/server/ExtensionPackageLoader.java @@ -29,6 +29,7 @@ package mage.server; import mage.server.util.PluginClassLoader; +import mage.util.StreamUtils; import java.io.File; import java.io.IOException; @@ -54,10 +55,12 @@ public final class ExtensionPackageLoader { String entryPoint = entryPointReader.nextLine().trim(); entryPointReader.close(); - PluginClassLoader classLoader = new PluginClassLoader(); - for(File f : packagesDirectory.listFiles()) classLoader.addURL(f.toURI().toURL()); - + PluginClassLoader classLoader = null; try { + classLoader = new PluginClassLoader(); + for(File f : packagesDirectory.listFiles()) { + classLoader.addURL(f.toURI().toURL()); + } return (ExtensionPackage) Class.forName(entryPoint, false, classLoader).newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); @@ -65,6 +68,8 @@ public final class ExtensionPackageLoader { throw new RuntimeException("Entry point class not found!", e); } catch (ClassCastException e) { throw new RuntimeException("Entry point not an instance of ExtensionPackage.", e); + } finally { + StreamUtils.closeQuietly(classLoader); } } } From 9402c4a59c53bf8024e195933d802cdafdc3d2ba Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:19:59 +0100 Subject: [PATCH 24/65] ensured closing of zipinputstream resource --- Mage.Verify/src/main/java/mage/verify/MtgJson.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Mage.Verify/src/main/java/mage/verify/MtgJson.java b/Mage.Verify/src/main/java/mage/verify/MtgJson.java index b1876671408..e0b2d133ee4 100644 --- a/Mage.Verify/src/main/java/mage/verify/MtgJson.java +++ b/Mage.Verify/src/main/java/mage/verify/MtgJson.java @@ -2,6 +2,7 @@ package mage.verify; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import mage.util.StreamUtils; import java.io.File; import java.io.FileInputStream; @@ -94,9 +95,15 @@ public final class MtgJson { } stream = new FileInputStream(file); } - ZipInputStream zipInputStream = new ZipInputStream(stream); - zipInputStream.getNextEntry(); - return new ObjectMapper().readValue(zipInputStream, ref); + ZipInputStream zipInputStream = null; + try { + zipInputStream = new ZipInputStream(stream); + zipInputStream.getNextEntry(); + return new ObjectMapper().readValue(zipInputStream, ref); + } finally { + StreamUtils.closeQuietly(zipInputStream); + } + } public static Map sets() { From 1e18875725136224858e5a656e1c2de8826157b0 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:27:14 +0100 Subject: [PATCH 25/65] ensure closing of fileoutputstream in ScryfallSymbolsSource --- .../plugins/card/dl/sources/ScryfallSymbolsSource.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java index caef7e5d211..e90d1cf9c5d 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import mage.util.StreamUtils; import org.mage.plugins.card.dl.DownloadJob; import static org.mage.card.arcane.ManaSymbols.getSymbolFileNameAsSVG; @@ -106,20 +107,21 @@ public class ScryfallSymbolsSource implements Iterable { if (destFile.exists() && (destFile.length() > 0)){ continue; } - + FileOutputStream stream = null; try { // base64 transform String data64 = foundedData.get(searchCode); Base64.Decoder dec = Base64.getDecoder(); byte[] fileData = dec.decode(data64); - FileOutputStream stream = new FileOutputStream(destFile); + stream = new FileOutputStream(destFile); stream.write(fileData); - stream.close(); LOGGER.info("New svg symbol downloaded: " + needCode); } catch (Exception e) { LOGGER.error("Can't decode svg icon and save to file: " + destFile.getPath() + ", reason: " + e.getMessage()); + } finally { + StreamUtils.closeQuietly(stream); } } } From f883d6b0a8c8380028db4b46ab80622d34b151d3 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:37:25 +0100 Subject: [PATCH 26/65] ensure closing resources after finishing/canceling download of pictures --- .../plugins/card/images/DownloadPictures.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 9e333430b78..13e4a2eef9c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -24,6 +24,7 @@ import mage.client.MageFrame; import mage.client.dialog.PreferencesDialog; import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; +import mage.util.StreamUtils; import net.java.truevfs.access.TFile; import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TVFS; @@ -782,18 +783,16 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab if (responseCode == 200) { // download OK // save data to temp - BufferedOutputStream out; - try (BufferedInputStream in = new BufferedInputStream(httpConn.getInputStream())) { + BufferedOutputStream out = null; + BufferedInputStream in = null; + try { + in = new BufferedInputStream(httpConn.getInputStream()); out = new BufferedOutputStream(new TFileOutputStream(fileTempImage)); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { // user cancelled if (cancel) { - in.close(); - out.flush(); - out.close(); - // stop download, save current state and exit TFile archive = destFile.getTopLevelArchive(); ///* not need to unmout/close - it's auto action @@ -804,8 +803,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } catch (Exception e) { logger.error("Can't close archive file: " + e.getMessage(), e); } - - }//*/ + } try { TFile.rm(fileTempImage); } catch (Exception e) { @@ -816,9 +814,11 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab out.write(buf, 0, len); } } - // TODO: remove to finnaly section? - out.flush(); - out.close(); + finally { + StreamUtils.closeQuietly(in); + StreamUtils.closeQuietly(out); + } + // TODO: add two faces card correction? (WTF) // SAVE final data From d299ee0882c761b87d599a9c2779b4cf642080e9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:37:46 +0100 Subject: [PATCH 27/65] remove commented code --- .../plugins/card/images/DownloadPictures.java | 123 ------------------ 1 file changed, 123 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 13e4a2eef9c..99491554acc 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -746,34 +746,6 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } } - /* - if(!destFile.getParentFile().exists()){ - destFile.getParentFile().mkdirs(); - } - */ - - /* - // WTF start?! TODO: wtf - File existingFile = new File(imagePath.replaceFirst("\\w{3}.zip", "")); - if (existingFile.exists()) { - try { - new TFile(existingFile).cp_rp(outputFile); - } catch (IOException e) { - logger.error("Error while copying file " + card.getName(), e); - } - synchronized (sync) { - update(cardIndex + 1, count); - } - existingFile.delete(); - File parent = existingFile.getParentFile(); - if (parent != null && parent.isDirectory() && parent.list().length == 0) { - parent.delete(); - } - return; - } - // WTF end?! - */ - // START to download cardImageSource.doPause(url.getPath()); URLConnection httpConn = url.openConnection(p); httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); @@ -847,81 +819,6 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } } - /* - // Logger.getLogger(this.getClass()).info(url.toString()); - boolean useTempFile = false; - int responseCode = 0; - URLConnection httpConn = null; - - if (temporaryFile != null && temporaryFile.length() > 100) { - useTempFile = true; - } else { - cardImageSource.doPause(url.getPath()); - httpConn = url.openConnection(p); - httpConn.connect(); - responseCode = ((HttpURLConnection) httpConn).getResponseCode(); - } - - if (responseCode == 200 || useTempFile) { - if (!useTempFile) { - BufferedOutputStream out; - try (BufferedInputStream in = new BufferedInputStream(httpConn.getInputStream())) { - //try (BufferedInputStream in = new BufferedInputStream(url.openConnection(p).getInputStream())) { - out = new BufferedOutputStream(new TFileOutputStream(temporaryFile)); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) != -1) { - // user cancelled - if (cancel) { - in.close(); - out.flush(); - out.close(); - temporaryFile.delete(); - return; - } - out.write(buf, 0, len); - } - - } - out.flush(); - out.close(); - } - - // TODO: WTF?! start - if (card != null && card.isTwoFacedCard()) { - BufferedImage image = ImageIO.read(temporaryFile); - if (image.getHeight() == 470) { - BufferedImage renderedImage = new BufferedImage(265, 370, BufferedImage.TYPE_INT_RGB); - renderedImage.getGraphics(); - Graphics2D graphics2D = renderedImage.createGraphics(); - if (card.isTwoFacedCard() && card.isSecondSide()) { - graphics2D.drawImage(image, 0, 0, 265, 370, 313, 62, 578, 432, null); - } else { - graphics2D.drawImage(image, 0, 0, 265, 370, 41, 62, 306, 432, null); - } - graphics2D.dispose(); - writeImageToFile(renderedImage, outputFile); - } else { - outputFile.getParentFile().mkdirs(); - new TFile(temporaryFile).cp_rp(outputFile); - } - //temporaryFile.delete(); - } else { - outputFile.getParentFile().mkdirs(); - new TFile(temporaryFile).cp_rp(outputFile); - } - // WTF?! end - } else { - if (card != null && !useSpecifiedPaths) { - logger.warn("Image download for " + card.getName() - + (!card.getDownloadName().equals(card.getName()) ? " downloadname: " + card.getDownloadName() : "") - + '(' + card.getSet() + ") failed - responseCode: " + responseCode + " url: " + url.toString()); - } - if (logger.isDebugEnabled()) { // Shows the returned html from the request to the web server - logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream())); - } - } - */ } catch (AccessDeniedException e) { logger.error("Can't access to files: " + card.getName() + "(" + card.getSet() + "). Try rebooting your system to remove the file lock."); } catch (Exception e) { @@ -933,26 +830,6 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab update(cardIndex + 1, count); } } - -// private void writeImageToFile(BufferedImage image, TFile file) throws IOException { -// Iterator iter = ImageIO.getImageWritersByFormatName("jpg"); -// -// ImageWriter writer = (ImageWriter) iter.next(); -// ImageWriteParam iwp = writer.getDefaultWriteParam(); -// iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); -// iwp.setCompressionQuality(0.96f); -// -// File tempFile = new File(getImagesDir() + File.separator + image.hashCode() + file.getName()); -// FileImageOutputStream output = new FileImageOutputStream(tempFile); -// writer.setOutput(output); -// IIOImage image2 = new IIOImage(image, null, null); -// writer.write(null, image2, iwp); -// writer.dispose(); -// output.close(); -// -// new TFile(tempFile).cp_rp(file); -// tempFile.delete(); -// } } private void update(int card, int count) { From ea7c75cb5238f5238c1b459c54f74bf2cc6f271a Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 15:47:34 +0100 Subject: [PATCH 28/65] move locks to try block to ensure unlocking along all execution paths --- Mage.Server/src/main/java/mage/server/ChatSession.java | 2 +- Mage.Server/src/main/java/mage/server/UserManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index da9b897f7e7..e73c0410f15 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -90,8 +90,8 @@ public class ChatSession { String userName = clients.get(userId); if (reason != DisconnectReason.LostConnection) { // for lost connection the user will be reconnected or session expire so no removeUserFromAllTablesAndChat of chat yet final Lock w = lock.writeLock(); - w.lock(); try { + w.lock(); clients.remove(userId); } finally { w.unlock(); diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index cd30111745a..767f7bb59a5 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -231,8 +231,8 @@ public enum UserManager { } logger.debug("Users to remove " + toRemove.size()); final Lock w = lock.readLock(); - w.lock(); try { + w.lock(); for (User user : toRemove) { users.remove(user.getId()); } From 6debe066f1552ec36d715fa34a0f567c1f0e9152 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 16:01:48 +0100 Subject: [PATCH 29/65] remove dangerous instance of double-checked locking --- .../java/mage/client/util/gui/ArrowBuilder.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/util/gui/ArrowBuilder.java b/Mage.Client/src/main/java/mage/client/util/gui/ArrowBuilder.java index 1c19bd5eca0..3520af3b4c5 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/ArrowBuilder.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/ArrowBuilder.java @@ -45,16 +45,12 @@ public class ArrowBuilder { * Get the panel where all arrows are being drawn. * @return */ - public JPanel getArrowsManagerPanel() { + public synchronized JPanel getArrowsManagerPanel() { if (arrowsManagerPanel == null) { - synchronized (ArrowBuilder.class) { - if (arrowsManagerPanel == null) { - arrowsManagerPanel = new JPanel(); - arrowsManagerPanel.setVisible(true); - arrowsManagerPanel.setOpaque(false); - arrowsManagerPanel.setLayout(null); - } - } + arrowsManagerPanel = new JPanel(); + arrowsManagerPanel.setVisible(true); + arrowsManagerPanel.setOpaque(false); + arrowsManagerPanel.setLayout(null); } return arrowsManagerPanel; } From 3e1312064fe44587886b8a7f9d0e42d8764000ab Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Tue, 20 Mar 2018 16:04:10 +0100 Subject: [PATCH 30/65] removed dangerous instance of double checked locking in settingsmanager --- .../org/mage/plugins/card/properties/SettingsManager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/properties/SettingsManager.java b/Mage.Client/src/main/java/org/mage/plugins/card/properties/SettingsManager.java index 075a3d9008b..c7a6b87d62c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/properties/SettingsManager.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/properties/SettingsManager.java @@ -12,13 +12,9 @@ public class SettingsManager { private static SettingsManager settingsManager = null; - public static SettingsManager getIntance() { + public static synchronized SettingsManager getIntance() { if (settingsManager == null) { - synchronized (SettingsManager.class) { - if (settingsManager == null) { - settingsManager = new SettingsManager(); - } - } + settingsManager = new SettingsManager(); } return settingsManager; } From 90631eff600ad09c7e46b3100e0e33e138871192 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Tue, 20 Mar 2018 16:10:09 +0100 Subject: [PATCH 31/65] Removed dangerous instance of double-checked locking in ThemePluginImpl --- .../mage/plugins/theme/ThemePluginImpl.java | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java index 43c788e9836..6f9b00879fd 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java @@ -150,47 +150,43 @@ public class ThemePluginImpl implements ThemePlugin { return bgPanel; } - private ImagePanel createImagePanelInstance() { + private synchronized ImagePanel createImagePanelInstance() { if (background == null) { - synchronized (ThemePluginImpl.class) { - if (background == null) { - String filename = "/background.png"; - try { - if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_BACKGROUND_IMAGE_DEFAULT, "true").equals("true")) { - InputStream is = this.getClass().getResourceAsStream(filename); - if (is == null) { - throw new FileNotFoundException("Couldn't find " + filename + " in resources."); - } - background = ImageIO.read(is); - } else { - String path = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_BACKGROUND_IMAGE, ""); - if (path != null && !path.isEmpty()) { - try { - File f = new File(path); - if (f != null) { - background = ImageIO.read(f); - } - } catch (Exception e) { - background = null; - } - } - } - if (background == null) { - InputStream is = this.getClass().getResourceAsStream(filename); - if (is == null) { - throw new FileNotFoundException("Couldn't find " + filename + " in resources."); - } - background = ImageIO.read(is); - } - if (background == null) { + String filename = "/background.png"; + try { + if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_BACKGROUND_IMAGE_DEFAULT, "true").equals("true")) { + InputStream is = this.getClass().getResourceAsStream(filename); + if (is == null) { throw new FileNotFoundException("Couldn't find " + filename + " in resources."); } - } catch (Exception e) { - log.error(e.getMessage(), e); - return null; + background = ImageIO.read(is); + } else { + String path = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_BACKGROUND_IMAGE, ""); + if (path != null && !path.isEmpty()) { + try { + File f = new File(path); + if (f != null) { + background = ImageIO.read(f); + } + } catch (Exception e) { + background = null; + } + } } + if (background == null) { + InputStream is = this.getClass().getResourceAsStream(filename); + if (is == null) { + throw new FileNotFoundException("Couldn't find " + filename + " in resources."); + } + background = ImageIO.read(is); + } + if (background == null) { + throw new FileNotFoundException("Couldn't find " + filename + " in resources."); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; } - } } return new ImagePanel(background, ImagePanelStyle.SCALED); } From c2c395420e7bf116a6541c87f22c50520cb04392 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 22 Mar 2018 10:30:10 +0100 Subject: [PATCH 32/65] close resource which did not happen certainly --- .../org/mage/plugins/card/images/DownloadPictures.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 99491554acc..5755522b1be 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -755,11 +755,13 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab if (responseCode == 200) { // download OK // save data to temp - BufferedOutputStream out = null; - BufferedInputStream in = null; + OutputStream out = null; + OutputStream tfileout = null; + InputStream in = null; try { in = new BufferedInputStream(httpConn.getInputStream()); - out = new BufferedOutputStream(new TFileOutputStream(fileTempImage)); + tfileout = new TFileOutputStream(fileTempImage); + out = new BufferedOutputStream(tfileout); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { @@ -789,6 +791,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab finally { StreamUtils.closeQuietly(in); StreamUtils.closeQuietly(out); + StreamUtils.closeQuietly(tfileout); } From 82e439c14bc708ac24ca526127780fe94109641b Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 22 Mar 2018 10:32:48 +0100 Subject: [PATCH 33/65] close another stream --- Mage.Verify/src/main/java/mage/verify/MtgJson.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Verify/src/main/java/mage/verify/MtgJson.java b/Mage.Verify/src/main/java/mage/verify/MtgJson.java index e0b2d133ee4..287aecef3dd 100644 --- a/Mage.Verify/src/main/java/mage/verify/MtgJson.java +++ b/Mage.Verify/src/main/java/mage/verify/MtgJson.java @@ -102,6 +102,7 @@ public final class MtgJson { return new ObjectMapper().readValue(zipInputStream, ref); } finally { StreamUtils.closeQuietly(zipInputStream); + StreamUtils.closeQuietly(stream); } } From a22eaff49c4c3c9b8478b8f00a1c6974e4272f27 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 22 Mar 2018 12:51:55 +0100 Subject: [PATCH 34/65] ensure closing of inputstream --- Mage.Server/src/main/java/mage/server/game/GameReplay.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage.Server/src/main/java/mage/server/game/GameReplay.java b/Mage.Server/src/main/java/mage/server/game/GameReplay.java index dd41f89c10f..01a4a1dd995 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameReplay.java +++ b/Mage.Server/src/main/java/mage/server/game/GameReplay.java @@ -87,11 +87,13 @@ public class GameReplay { private Game loadGame(UUID gameId) { InputStream file = null; InputStream buffer = null; + InputStream gzip = null; ObjectInput input = null; try{ file = new FileInputStream("saved/" + gameId.toString() + ".game"); buffer = new BufferedInputStream(file); - input = new CopierObjectInputStream(Main.classLoader, new GZIPInputStream(buffer)); + gzip = new GZIPInputStream(buffer); + input = new CopierObjectInputStream(Main.classLoader, gzip); Game loadGame = (Game) input.readObject(); GameStates states = (GameStates) input.readObject(); loadGame.loadGameStates(states); @@ -107,6 +109,7 @@ public class GameReplay { StreamUtils.closeQuietly(file); StreamUtils.closeQuietly(buffer); StreamUtils.closeQuietly(input); + StreamUtils.closeQuietly(gzip); } return null; } From 08ffbecdb405871bc1dafc085aeb0d8a48d38981 Mon Sep 17 00:00:00 2001 From: spjspj Date: Wed, 28 Mar 2018 02:22:28 +1100 Subject: [PATCH 35/65] Have a faded life total over the avatar which glows then fades again reddish for damage and bluish for life gain. --- .../mage/client/components/HoverButton.java | 6 +- .../java/mage/client/game/PlayerPanelExt.java | 90 ++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 7dbf3891525..5292a148d84 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -52,7 +52,7 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private final Color centerTextColor = Color.YELLOW; + private Color centerTextColor = Color.YELLOW; private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); @@ -174,6 +174,10 @@ public class HoverButton extends JPanel implements MouseListener { g2d.drawString(set, 0, 0); } } + + public void setCenterColor(Color c) { + centerTextColor = c; + } private int calculateOffset(Graphics2D g2d) { if (textOffsetX == -1) { // calculate once diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index cf7b843556e..9f0c9767113 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -38,6 +38,8 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.LinkedHashSet; @@ -53,6 +55,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.SwingConstants; +import javax.swing.Timer; import javax.swing.border.Border; import javax.swing.border.LineBorder; import mage.cards.decks.importer.DckDeckImporter; @@ -113,6 +116,13 @@ public class PlayerPanelExt extends javax.swing.JPanel { private int avatarId = -1; private String flagName; private String basicTooltipText; + private static final Map playerLives = new HashMap<>(); + private int loseX; + private boolean doLoseFade = true; + private int gainX; + private boolean doGainFade = true; + Timer faderGainLife = null; + Timer faderLoseLife = null; private PriorityTimer timer; @@ -175,9 +185,85 @@ public class PlayerPanelExt extends javax.swing.JPanel { public void update(PlayerView player) { this.player = player; + int pastLife = player.getLife(); + if (playerLives != null) { + if (playerLives.containsKey(player.getPlayerId())) { + pastLife = playerLives.get(player.getPlayerId()); + } + playerLives.put(player.getPlayerId(), player.getLife()); + } int playerLife = player.getLife(); - avatar.setCenterText("true".equals(MageFrame.getPreferences().get(PreferencesDialog.KEY_DISPLAY_LIVE_ON_AVATAR, "true")) - ? String.valueOf(playerLife) : null); + + boolean displayLife = "true".equals(MageFrame.getPreferences().get(PreferencesDialog.KEY_DISPLAY_LIVE_ON_AVATAR, "true")); + avatar.setCenterText(displayLife ? String.valueOf(playerLife) : null); + + if (displayLife) { + if (playerLife != pastLife) { + if (playerLife > pastLife) { + if (faderGainLife == null && doGainFade) { + doGainFade = false; + faderGainLife = new Timer(25, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + gainX++; + avatar.setCenterColor(new Color(2 * gainX, 190, 255, 250 - gainX)); + avatar.repaint(); + if (gainX >= 100) { + avatar.setCenterColor(new Color(200, 190, 0, 180)); + gainX = 100; + + if (faderGainLife != null) { + faderGainLife.stop(); + faderGainLife.setRepeats(false); + faderGainLife.setDelay(50000); + } + } + } + }); + gainX = 0; + faderGainLife.setInitialDelay(25); + faderGainLife.setRepeats(true); + faderGainLife.start(); + } + } else if (playerLife < pastLife) { + if (faderLoseLife == null && doLoseFade) { + doLoseFade = false; + faderLoseLife = new Timer(25, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + loseX++; + avatar.setCenterColor(new Color(250 - loseX / 2, 140 + loseX / 2, 0, 250 - loseX)); + avatar.repaint(); + if (loseX >= 100) { + avatar.setCenterColor(new Color(200, 190, 0, 180)); + loseX = 100; + + if (faderLoseLife != null) { + faderLoseLife.stop(); + faderLoseLife.setRepeats(false); + faderLoseLife.setDelay(50000); + } + } + } + }); + loseX = 0; + faderLoseLife.setInitialDelay(25); + faderLoseLife.setRepeats(true); + faderLoseLife.start(); + } + } + } else if (playerLife == pastLife) { + if (faderGainLife != null && gainX >= 100) { + faderGainLife.stop(); + faderGainLife = null; + } + doGainFade = true; + if (faderLoseLife != null && loseX >= 100) { + faderLoseLife.stop(); + faderLoseLife = null; + } + doLoseFade = true; + } + } + updateAvatar(); if (playerLife > 99) { From e69423af27c26b2061bfded10ccc4f673354cd1e Mon Sep 17 00:00:00 2001 From: Christiaan Date: Tue, 27 Mar 2018 17:26:19 +0200 Subject: [PATCH 36/65] performance improvements for startup added option to skip generating small icons that already exist, load symbol images multithreaded --- .../src/main/java/mage/client/MageFrame.java | 9 +++ .../org/mage/card/arcane/ManaSymbols.java | 78 ++++++++++--------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 77d7ebf6ef4..0dc296b3fb0 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -104,6 +104,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static final String LITE_MODE_ARG = "-lite"; private static final String GRAY_MODE_ARG = "-gray"; private static final String FILL_SCREEN_ARG = "-fullscreen"; + private static final String SKIP_DONE_SYMBOLS = "-skipDoneSymbols"; private static final String NOT_CONNECTED_TEXT = ""; private static MageFrame instance; @@ -121,6 +122,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { //TODO: make gray theme, implement theme selector in preferences dialog private static boolean grayMode = false; private static boolean fullscreenMode = false; + private static boolean skipSmallSymbolGenerationForExisting = false; private static final Map CHATS = new HashMap<>(); private static final Map GAMES = new HashMap<>(); @@ -152,6 +154,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { public static boolean isGray() { return grayMode; } + + public static boolean isSkipSmallSymbolGenerationForExisting() { + return skipSmallSymbolGenerationForExisting; + } @Override public MageVersion getVersion() { @@ -1191,6 +1197,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { if (arg.startsWith(FILL_SCREEN_ARG)) { fullscreenMode = true; } + if (arg.startsWith(SKIP_DONE_SYMBOLS)) { + skipSmallSymbolGenerationForExisting = true; + } } if (!liteMode) { final SplashScreen splash = SplashScreen.getSplashScreen(); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 8b3c5d9589b..b0ba9953f5a 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -31,10 +31,14 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; +import java.util.stream.IntStream; import javax.imageio.ImageIO; import javax.swing.*; import mage.cards.repository.ExpansionRepository; +import mage.client.MageFrame; import mage.client.constants.Constants; import mage.client.constants.Constants.ResourceSetSize; import mage.client.constants.Constants.ResourceSymbolSize; @@ -56,7 +60,7 @@ public final class ManaSymbols { private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); private static final Map> manaImages = new HashMap<>(); - private static final Map> setImages = new HashMap<>(); + private static final Map> setImages = new ConcurrentHashMap<>(); private static final HashSet onlyMythics = new HashSet<>(); private static final HashSet withoutSymbols = new HashSet<>(); @@ -76,7 +80,7 @@ public final class ManaSymbols { } private static final Map setImagesExist = new HashMap<>(); private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", "BR", "BP", "2B", "G", "GU", "GW", "GP", "2G", @@ -166,37 +170,39 @@ public final class ManaSymbols { } catch (Exception e) { } } - + // generate small size try { File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM)); if (!file.exists()) { file.mkdirs(); } - + String pathRoot = getResourceSetsPath(ResourceSetSize.SMALL) + set; for (String code : codes) { - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getResourceSetsPath(ResourceSetSize.SMALL) + set + '-' + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { + File newFile = new File(pathRoot + '-' + code + ".png"); + if(!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())){// skip if option enabled and file already exists + file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png"); if (file.exists()) { - file.delete(); + continue; + } + file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } } } } @@ -204,7 +210,6 @@ public final class ManaSymbols { } catch (Exception e) { } } - // mark loaded images // TODO: delete that code, images draw-show must dynamicly File file; @@ -225,7 +230,6 @@ public final class ManaSymbols { } public static BufferedImage loadSVG(File svgFile, int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException { - // debug: disable shadow gen, need to test it useShadow = false; @@ -419,17 +423,17 @@ public final class ManaSymbols { } private static boolean loadSymbolImages(int size) { - // load all symbols to cash + // load all symbols to cache // priority: SVG -> GIF // gif remain for backward compatibility - boolean fileErrors = false; - - HashMap sizedSymbols = new HashMap<>(); - for (String symbol : symbols) { - + //boolean fileErrors = false; + AtomicBoolean fileErrors = new AtomicBoolean(false); + Map sizedSymbols = new ConcurrentHashMap<>(); + IntStream.range(0, symbols.length).parallel().forEach(i-> { + String symbol = symbols[i]; BufferedImage image = null; - File file = null; + File file; // svg file = getSymbolFileNameAsSVG(symbol); @@ -451,13 +455,13 @@ public final class ManaSymbols { if (image != null) { sizedSymbols.put(symbol, image); } else { - fileErrors = true; + fileErrors.set(true); LOGGER.warn("SVG or GIF symbol can't be load: " + symbol); } - } + }); manaImages.put(size, sizedSymbols); - return !fileErrors; + return !fileErrors.get(); } private static void renameSymbols(String path) { From 98c2b171ded7b00840a68dd843add7446f7f57e4 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Wed, 28 Mar 2018 13:49:04 +0200 Subject: [PATCH 37/65] Added the URLHandler which makes the URL's in the message of the day clickable. At the moment when there are 2 URL's in 1 message, it only makes the last one clickable. --- .../java/mage/client/table/TablesPanel.java | 7 +- .../java/mage/client/util/URLHandler.java | 119 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/util/URLHandler.java diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 5c11b3c6dc9..474e6956bb8 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -60,6 +60,7 @@ import mage.client.util.ButtonColumn; import mage.client.util.GUISizeHelper; import mage.client.util.IgnoreList; import mage.client.util.MageTableRowSorter; +import mage.client.util.URLHandler; import mage.client.util.gui.GuiDisplayUtil; import mage.client.util.gui.TableUtil; import mage.constants.*; @@ -579,7 +580,7 @@ public class TablesPanel extends javax.swing.JPanel { this.jPanelBottom.setVisible(false); } else { this.jPanelBottom.setVisible(true); - this.jLabelFooterText.setText(serverMessages.get(0)); + URLHandler.handleMessage(serverMessages.get(0), this.jLabelFooterText); this.jButtonFooterNext.setVisible(serverMessages.size() > 1); } } @@ -1283,7 +1284,9 @@ public class TablesPanel extends javax.swing.JPanel { if (currentMessage >= messages.size()) { currentMessage = 0; } - this.jLabelFooterText.setText(messages.get(currentMessage)); + + URLHandler.RemoveMouseAdapter(jLabelFooterText); + URLHandler.handleMessage(messages.get(currentMessage), this.jLabelFooterText); } } }//GEN-LAST:event_jButtonFooterNextActionPerformed diff --git a/Mage.Client/src/main/java/mage/client/util/URLHandler.java b/Mage.Client/src/main/java/mage/client/util/URLHandler.java new file mode 100644 index 00000000000..20d05058f60 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/util/URLHandler.java @@ -0,0 +1,119 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.client.util; + +import java.awt.Desktop; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URL; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import javax.swing.JLabel; + +/** + * + * @author Dahny + */ +public class URLHandler { + + private static MouseAdapter currentMouseAdapter; + + /** + * This method makes a URL in a message click-able and converts the message + * into HTML. + * + * @param message: The selected message + * @param label: The message of the day label + */ + public static void handleMessage(String message, JLabel label) { + String url = detectURL(message); + + if (!url.equals("")) { + label.addMouseListener(createMouseAdapter(url)); + } + + label.setText(convertToHTML(message)); + } + + public static void RemoveMouseAdapter(JLabel label) { + label.removeMouseListener(currentMouseAdapter); + currentMouseAdapter = null; + } + + private static MouseAdapter createMouseAdapter(String url) { + currentMouseAdapter = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() > 0) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + URI uri = new URI(url); + desktop.browse(uri); + } catch (IOException | URISyntaxException ex) { + // do nothing + } + } else { + //do nothing + } + } + } + }; + + return currentMouseAdapter; + } + + public static String convertToHTML(String input) { + String s = input; + String output = ""; + // separate the input by spaces + String[] parts = s.split("\\s+"); + + for (String item : parts) { + try { + URL url = new URL(item); + // The item is a valid URL + output = output + "" + url + " "; + + } catch (MalformedURLException e) { + //The item might still be an URL + if (item.startsWith("www.")) { + output = output + "" + item + " "; + } else { + output = output + item + " "; + } + + } + } + + output = output + ""; + return output; + } + + public static String detectURL(String input) { + String s = input; + String output = ""; + // separate the input by spaces + String[] parts = s.split("\\s+"); + + for (String item : parts) { + try { + URL url = new URL(item); + // The item is a valid URL + output = url.toString(); + } catch (MalformedURLException e) { + //The item might still be an URL + if (item.startsWith("www.")) { + output = "http://" + item; + } + } + } + + return output; + } + +} From 938ac3598173f7eab37f6f68a9502f0eb59f0aca Mon Sep 17 00:00:00 2001 From: spjspj Date: Thu, 29 Mar 2018 00:28:56 +1100 Subject: [PATCH 38/65] Have a faded life total over the avatar which glows then fades again reddish for damage and bluish for life gain. --- .../main/java/mage/client/components/HoverButton.java | 2 +- .../src/main/java/mage/client/game/PlayerPanelExt.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 5292a148d84..863cb49109c 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -52,7 +52,7 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private Color centerTextColor = Color.YELLOW; + private Color centerTextColor = new Color(200, 190, 0, 180); private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 9f0c9767113..ed4b0d8a978 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -202,10 +202,11 @@ public class PlayerPanelExt extends javax.swing.JPanel { if (playerLife > pastLife) { if (faderGainLife == null && doGainFade) { doGainFade = false; - faderGainLife = new Timer(25, new ActionListener() { + faderGainLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { gainX++; - avatar.setCenterColor(new Color(2 * gainX, 190, 255, 250 - gainX)); + int alpha = Math.max(250 - gainX, 180); + avatar.setCenterColor(new Color(2 * gainX, 190, 255, alpha)); avatar.repaint(); if (gainX >= 100) { avatar.setCenterColor(new Color(200, 190, 0, 180)); @@ -227,10 +228,11 @@ public class PlayerPanelExt extends javax.swing.JPanel { } else if (playerLife < pastLife) { if (faderLoseLife == null && doLoseFade) { doLoseFade = false; - faderLoseLife = new Timer(25, new ActionListener() { + faderLoseLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { loseX++; - avatar.setCenterColor(new Color(250 - loseX / 2, 140 + loseX / 2, 0, 250 - loseX)); + int alpha = Math.max(250 - loseX, 180); + avatar.setCenterColor(new Color(250 - loseX / 2, 140 + loseX / 2, 0, alpha)); avatar.repaint(); if (loseX >= 100) { avatar.setCenterColor(new Color(200, 190, 0, 180)); From 4c67d96feb44f831223785586ed8ab6eb90d9b7a Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 28 Mar 2018 09:49:18 -0500 Subject: [PATCH 39/65] - Added a requested card Retribution. --- Mage.Sets/src/mage/cards/r/Retribution.java | 152 +++++++ Mage.Sets/src/mage/sets/Homelands.java | 375 +++++++++--------- Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 3 files changed, 341 insertions(+), 187 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/r/Retribution.java diff --git a/Mage.Sets/src/mage/cards/r/Retribution.java b/Mage.Sets/src/mage/cards/r/Retribution.java new file mode 100644 index 00000000000..7c17d79433c --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/Retribution.java @@ -0,0 +1,152 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.r; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author jeffwadsworth + */ +public class Retribution extends CardImpl { + + public Retribution(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}"); + + // Choose two target creatures an opponent controls. That player chooses and sacrifices one of those creatures. Put a -1/-1 counter on the other. + this.getSpellAbility().addEffect(new RetributionEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanentOpponentSameController(2, 2, StaticFilters.FILTER_PERMANENT_CREATURE, false)); + + } + + public Retribution(final Retribution card) { + super(card); + } + + @Override + public Retribution copy() { + return new Retribution(this); + } +} + +class RetributionEffect extends OneShotEffect { + + public RetributionEffect() { + super(Outcome.Detriment); + this.staticText = "Choose two target creatures an opponent controls. That player chooses and sacrifices one of those creatures. Put a -1/-1 counter on the other"; + } + + public RetributionEffect(final RetributionEffect effect) { + super(effect); + } + + @Override + public RetributionEffect copy() { + return new RetributionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject != null) { + boolean sacrificeDone = false; + int count = 0; + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent creature = game.getPermanent(targetId); + if (creature != null) { + Player controllerOfCreature = game.getPlayer(creature.getControllerId()); + if ((count == 0 + && controllerOfCreature.chooseUse(Outcome.Sacrifice, "Sacrifice " + creature.getLogName() + '?', source, game)) + || (count == 1 + && !sacrificeDone)) { + creature.sacrifice(source.getId(), game); + sacrificeDone = true; + } else { + creature.addCounters(CounterType.M1M1.createInstance(), source, game); + } + count++; + } + } + return true; + } + return false; + } +} + +class TargetCreaturePermanentOpponentSameController extends TargetCreaturePermanent { + + public TargetCreaturePermanentOpponentSameController(int minNumTargets, int maxNumTargets, FilterCreaturePermanent filter, boolean notTarget) { + super(minNumTargets, maxNumTargets, filter, notTarget); + } + + public TargetCreaturePermanentOpponentSameController(final TargetCreaturePermanentOpponentSameController target) { + super(target); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + if (super.canTarget(controllerId, id, source, game)) { + Permanent firstTargetPermanent = game.getPermanent(id); + if (firstTargetPermanent != null + && game.getOpponents(controllerId).contains(firstTargetPermanent.getControllerId())) { + for (Object object : getTargets()) { + UUID targetId = (UUID) object; + Permanent targetPermanent = game.getPermanent(targetId); + if (targetPermanent != null) { + if (!firstTargetPermanent.getId().equals(targetPermanent.getId())) { + if (!firstTargetPermanent.getControllerId().equals(targetPermanent.getOwnerId())) { + return false; + } + } + } + } + return true; + } + } + return false; + } + + @Override + public TargetCreaturePermanentOpponentSameController copy() { + return new TargetCreaturePermanentOpponentSameController(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index b38a7f5e8de..36f90535259 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -1,187 +1,188 @@ -/* - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package mage.sets; - -import mage.cards.ExpansionSet; -import mage.cards.a.AbbeyMatron; -import mage.cards.a.AlibansTower; -import mage.cards.c.CemeteryGate; -import mage.cards.d.DrySpell; -import mage.cards.d.DwarvenTrader; -import mage.cards.f.FeastOfTheUnicorn; -import mage.cards.f.FolkOfAnHavva; -import mage.cards.m.MesaFalcon; -import mage.cards.r.ReefPirates; -import mage.cards.s.SengirBats; -import mage.cards.t.Torture; -import mage.cards.w.WillowFaerie; -import mage.constants.Rarity; -import mage.constants.SetType; - -/** - * - * @author North - */ -public class Homelands extends ExpansionSet { - - private static final Homelands instance = new Homelands(); - - public static Homelands getInstance() { - return instance; - } - - private Homelands() { - super("Homelands", "HML", ExpansionSet.buildDate(1995, 9, 1), SetType.EXPANSION); - this.hasBasicLands = false; - this.hasBoosters = true; - this.numBoosterLands = 1; - this.numBoosterCommon = 10; - this.numBoosterUncommon = 3; - this.numBoosterRare = 1; - this.ratioBoosterMythic = 0; - cards.add(new SetCardInfo("Aether Storm", 26, Rarity.UNCOMMON, mage.cards.a.AetherStorm.class)); - cards.add(new SetCardInfo("Abbey Gargoyles", 101, Rarity.UNCOMMON, mage.cards.a.AbbeyGargoyles.class)); - cards.add(new SetCardInfo("Abbey Matron", 102, Rarity.COMMON, AbbeyMatron.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Abbey Matron", 103, Rarity.COMMON, AbbeyMatron.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Aliban's Tower", 76, Rarity.COMMON, AlibansTower.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Aliban's Tower", 77, Rarity.COMMON, AlibansTower.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ambush", 78, Rarity.COMMON, mage.cards.a.Ambush.class)); - cards.add(new SetCardInfo("Ambush Party", 79, Rarity.COMMON, mage.cards.a.AmbushParty.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ambush Party", 80, Rarity.COMMON, mage.cards.a.AmbushParty.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anaba Ancestor", 81, Rarity.RARE, mage.cards.a.AnabaAncestor.class)); - cards.add(new SetCardInfo("Anaba Bodyguard", 82, Rarity.COMMON, mage.cards.a.AnabaBodyguard.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anaba Bodyguard", 83, Rarity.COMMON, mage.cards.a.AnabaBodyguard.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anaba Shaman", 84, Rarity.COMMON, mage.cards.a.AnabaShaman.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anaba Shaman", 85, Rarity.COMMON, mage.cards.a.AnabaShaman.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anaba Spirit Crafter", 86, Rarity.RARE, mage.cards.a.AnabaSpiritCrafter.class)); - cards.add(new SetCardInfo("An-Havva Constable", 51, Rarity.RARE, mage.cards.a.AnHavvaConstable.class)); - cards.add(new SetCardInfo("An-Havva Inn", 52, Rarity.UNCOMMON, mage.cards.a.AnHavvaInn.class)); - cards.add(new SetCardInfo("An-Havva Township", 136, Rarity.UNCOMMON, mage.cards.a.AnHavvaTownship.class)); - cards.add(new SetCardInfo("An-Zerrin Ruins", 87, Rarity.RARE, mage.cards.a.AnZerrinRuins.class)); - cards.add(new SetCardInfo("Apocalypse Chime", 126, Rarity.RARE, mage.cards.a.ApocalypseChime.class)); - cards.add(new SetCardInfo("Autumn Willow", 53, Rarity.RARE, mage.cards.a.AutumnWillow.class)); - cards.add(new SetCardInfo("Aysen Abbey", 137, Rarity.UNCOMMON, mage.cards.a.AysenAbbey.class)); - cards.add(new SetCardInfo("Aysen Bureaucrats", 104, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Aysen Bureaucrats", 105, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Aysen Crusader", 106, Rarity.RARE, mage.cards.a.AysenCrusader.class)); - cards.add(new SetCardInfo("Aysen Highway", 107, Rarity.RARE, mage.cards.a.AysenHighway.class)); - cards.add(new SetCardInfo("Baki's Curse", 27, Rarity.RARE, mage.cards.b.BakisCurse.class)); - cards.add(new SetCardInfo("Baron Sengir", 1, Rarity.RARE, mage.cards.b.BaronSengir.class)); - cards.add(new SetCardInfo("Beast Walkers", 108, Rarity.RARE, mage.cards.b.BeastWalkers.class)); - cards.add(new SetCardInfo("Black Carriage", 2, Rarity.RARE, mage.cards.b.BlackCarriage.class)); - cards.add(new SetCardInfo("Broken Visage", 3, Rarity.RARE, mage.cards.b.BrokenVisage.class)); - cards.add(new SetCardInfo("Carapace", 54, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Carapace", 55, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Castle Sengir", 138, Rarity.UNCOMMON, mage.cards.c.CastleSengir.class)); - cards.add(new SetCardInfo("Cemetery Gate", 4, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Cemetery Gate", 5, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Chain Stasis", 28, Rarity.RARE, mage.cards.c.ChainStasis.class)); - cards.add(new SetCardInfo("Chandler", 88, Rarity.COMMON, mage.cards.c.Chandler.class)); - cards.add(new SetCardInfo("Clockwork Gnomes", 127, Rarity.COMMON, mage.cards.c.ClockworkGnomes.class)); - cards.add(new SetCardInfo("Clockwork Swarm", 129, Rarity.COMMON, mage.cards.c.ClockworkSwarm.class)); - cards.add(new SetCardInfo("Coral Reef", 29, Rarity.COMMON, mage.cards.c.CoralReef.class)); - cards.add(new SetCardInfo("Dark Maze", 30, Rarity.COMMON, mage.cards.d.DarkMaze.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Dark Maze", 31, Rarity.COMMON, mage.cards.d.DarkMaze.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Daughter of Autumn", 56, Rarity.RARE, mage.cards.d.DaughterOfAutumn.class)); - cards.add(new SetCardInfo("Death Speakers", 109, Rarity.UNCOMMON, mage.cards.d.DeathSpeakers.class)); - cards.add(new SetCardInfo("Didgeridoo", 130, Rarity.RARE, mage.cards.d.Didgeridoo.class)); - cards.add(new SetCardInfo("Drudge Spell", 6, Rarity.UNCOMMON, mage.cards.d.DrudgeSpell.class)); - cards.add(new SetCardInfo("Dry Spell", 7, Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Dry Spell", 8, Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Dwarven Pony", 89, Rarity.RARE, mage.cards.d.DwarvenPony.class)); - cards.add(new SetCardInfo("Dwarven Trader", 91, Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Dwarven Trader", 92, Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ebony Rhino", 131, Rarity.COMMON, mage.cards.e.EbonyRhino.class)); - cards.add(new SetCardInfo("Eron the Relentless", 93, Rarity.UNCOMMON, mage.cards.e.EronTheRelentless.class)); - cards.add(new SetCardInfo("Evaporate", 94, Rarity.UNCOMMON, mage.cards.e.Evaporate.class)); - cards.add(new SetCardInfo("Faerie Noble", 57, Rarity.RARE, mage.cards.f.FaerieNoble.class)); - cards.add(new SetCardInfo("Feast of the Unicorn", 9, Rarity.COMMON, FeastOfTheUnicorn.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Feast of the Unicorn", 10, Rarity.COMMON, FeastOfTheUnicorn.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Feroz's Ban", 132, Rarity.RARE, mage.cards.f.FerozsBan.class)); - cards.add(new SetCardInfo("Folk of An-Havva", 58, Rarity.COMMON, FolkOfAnHavva.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Folk of An-Havva", 59, Rarity.COMMON, FolkOfAnHavva.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forget", 32, Rarity.RARE, mage.cards.f.Forget.class)); - cards.add(new SetCardInfo("Ghost Hounds", 12, Rarity.UNCOMMON, mage.cards.g.GhostHounds.class)); - cards.add(new SetCardInfo("Grandmother Sengir", 13, Rarity.RARE, mage.cards.g.GrandmotherSengir.class)); - cards.add(new SetCardInfo("Hazduhr the Abbot", 110, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); - cards.add(new SetCardInfo("Headstone", 15, Rarity.COMMON, mage.cards.h.Headstone.class)); - cards.add(new SetCardInfo("Hungry Mist", 60, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Hungry Mist", 61, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ihsan's Shade", 16, Rarity.UNCOMMON, mage.cards.i.IhsansShade.class)); - cards.add(new SetCardInfo("Irini Sengir", 17, Rarity.UNCOMMON, mage.cards.i.IriniSengir.class)); - cards.add(new SetCardInfo("Jinx", 36, Rarity.COMMON, mage.cards.j.Jinx.class)); - cards.add(new SetCardInfo("Joven", 97, Rarity.COMMON, mage.cards.j.Joven.class)); - cards.add(new SetCardInfo("Joven's Tools", 133, Rarity.UNCOMMON, mage.cards.j.JovensTools.class)); - cards.add(new SetCardInfo("Koskun Falls", 18, Rarity.RARE, mage.cards.k.KoskunFalls.class)); - cards.add(new SetCardInfo("Koskun Keep", 139, Rarity.UNCOMMON, mage.cards.k.KoskunKeep.class)); - cards.add(new SetCardInfo("Labyrinth Minotaur", 38, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); - cards.add(new SetCardInfo("Leaping Lizard", 63, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); - cards.add(new SetCardInfo("Leeches", 111, Rarity.RARE, mage.cards.l.Leeches.class)); - cards.add(new SetCardInfo("Mammoth Harness", 64, Rarity.RARE, mage.cards.m.MammothHarness.class)); - cards.add(new SetCardInfo("Marjhan", 39, Rarity.RARE, mage.cards.m.Marjhan.class)); - cards.add(new SetCardInfo("Memory Lapse", 40, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Memory Lapse", 41, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Merchant Scroll", 42, Rarity.COMMON, mage.cards.m.MerchantScroll.class)); - cards.add(new SetCardInfo("Mesa Falcon", 112, Rarity.COMMON, MesaFalcon.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mesa Falcon", 113, Rarity.COMMON, MesaFalcon.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mystic Decree", 43, Rarity.RARE, mage.cards.m.MysticDecree.class)); - cards.add(new SetCardInfo("Narwhal", 44, Rarity.RARE, mage.cards.n.Narwhal.class)); - cards.add(new SetCardInfo("Primal Order", 65, Rarity.RARE, mage.cards.p.PrimalOrder.class)); - cards.add(new SetCardInfo("Rashka the Slayer", 115, Rarity.RARE, mage.cards.r.RashkaTheSlayer.class)); - cards.add(new SetCardInfo("Reef Pirates", 45, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Reef Pirates", 46, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Renewal", 66, Rarity.COMMON, mage.cards.r.Renewal.class)); - cards.add(new SetCardInfo("Reveka, Wizard Savant", 47, Rarity.RARE, mage.cards.r.RevekaWizardSavant.class)); - cards.add(new SetCardInfo("Roots", 68, Rarity.UNCOMMON, mage.cards.r.Roots.class)); - cards.add(new SetCardInfo("Root Spider", 67, Rarity.UNCOMMON, mage.cards.r.RootSpider.class)); - cards.add(new SetCardInfo("Roterothopter", 134, Rarity.COMMON, mage.cards.r.Roterothopter.class)); - cards.add(new SetCardInfo("Sea Sprite", 48, Rarity.UNCOMMON, mage.cards.s.SeaSprite.class)); - cards.add(new SetCardInfo("Sengir Autocrat", 19, Rarity.UNCOMMON, mage.cards.s.SengirAutocrat.class)); - cards.add(new SetCardInfo("Sengir Bats", 20, Rarity.COMMON, SengirBats.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sengir Bats", 21, Rarity.COMMON, SengirBats.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Serra Aviary", 118, Rarity.RARE, mage.cards.s.SerraAviary.class)); - cards.add(new SetCardInfo("Serra Paladin", 121, Rarity.COMMON, mage.cards.s.SerraPaladin.class)); - cards.add(new SetCardInfo("Serrated Arrows", 135, Rarity.COMMON, mage.cards.s.SerratedArrows.class)); - cards.add(new SetCardInfo("Shrink", 70, Rarity.COMMON, mage.cards.s.Shrink.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Shrink", 71, Rarity.COMMON, mage.cards.s.Shrink.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Soraya the Falconer", 122, Rarity.RARE, mage.cards.s.SorayaTheFalconer.class)); - cards.add(new SetCardInfo("Spectral Bears", 72, Rarity.UNCOMMON, mage.cards.s.SpectralBears.class)); - cards.add(new SetCardInfo("Torture", 23, Rarity.COMMON, Torture.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Torture", 24, Rarity.COMMON, Torture.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Trade Caravan", 123, Rarity.COMMON, mage.cards.t.TradeCaravan.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Trade Caravan", 124, Rarity.COMMON, mage.cards.t.TradeCaravan.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Truce", 125, Rarity.RARE, mage.cards.t.Truce.class)); - cards.add(new SetCardInfo("Veldrane of Sengir", 25, Rarity.RARE, mage.cards.v.VeldraneOfSengir.class)); - cards.add(new SetCardInfo("Wall of Kelp", 50, Rarity.RARE, mage.cards.w.WallOfKelp.class)); - cards.add(new SetCardInfo("Willow Faerie", 73, Rarity.COMMON, WillowFaerie.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Willow Faerie", 74, Rarity.COMMON, WillowFaerie.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Willow Priestess", 75, Rarity.RARE, mage.cards.w.WillowPriestess.class)); - cards.add(new SetCardInfo("Winter Sky", 100, Rarity.RARE, mage.cards.w.WinterSky.class)); - cards.add(new SetCardInfo("Wizards' School", 140, Rarity.UNCOMMON, mage.cards.w.WizardsSchool.class)); - } -} +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.cards.a.AbbeyMatron; +import mage.cards.a.AlibansTower; +import mage.cards.c.CemeteryGate; +import mage.cards.d.DrySpell; +import mage.cards.d.DwarvenTrader; +import mage.cards.f.FeastOfTheUnicorn; +import mage.cards.f.FolkOfAnHavva; +import mage.cards.m.MesaFalcon; +import mage.cards.r.ReefPirates; +import mage.cards.s.SengirBats; +import mage.cards.t.Torture; +import mage.cards.w.WillowFaerie; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * + * @author North + */ +public class Homelands extends ExpansionSet { + + private static final Homelands instance = new Homelands(); + + public static Homelands getInstance() { + return instance; + } + + private Homelands() { + super("Homelands", "HML", ExpansionSet.buildDate(1995, 9, 1), SetType.EXPANSION); + this.hasBasicLands = false; + this.hasBoosters = true; + this.numBoosterLands = 1; + this.numBoosterCommon = 10; + this.numBoosterUncommon = 3; + this.numBoosterRare = 1; + this.ratioBoosterMythic = 0; + cards.add(new SetCardInfo("Aether Storm", 26, Rarity.UNCOMMON, mage.cards.a.AetherStorm.class)); + cards.add(new SetCardInfo("Abbey Gargoyles", 101, Rarity.UNCOMMON, mage.cards.a.AbbeyGargoyles.class)); + cards.add(new SetCardInfo("Abbey Matron", 102, Rarity.COMMON, AbbeyMatron.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abbey Matron", 103, Rarity.COMMON, AbbeyMatron.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aliban's Tower", 76, Rarity.COMMON, AlibansTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aliban's Tower", 77, Rarity.COMMON, AlibansTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ambush", 78, Rarity.COMMON, mage.cards.a.Ambush.class)); + cards.add(new SetCardInfo("Ambush Party", 79, Rarity.COMMON, mage.cards.a.AmbushParty.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ambush Party", 80, Rarity.COMMON, mage.cards.a.AmbushParty.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anaba Ancestor", 81, Rarity.RARE, mage.cards.a.AnabaAncestor.class)); + cards.add(new SetCardInfo("Anaba Bodyguard", 82, Rarity.COMMON, mage.cards.a.AnabaBodyguard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anaba Bodyguard", 83, Rarity.COMMON, mage.cards.a.AnabaBodyguard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anaba Shaman", 84, Rarity.COMMON, mage.cards.a.AnabaShaman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anaba Shaman", 85, Rarity.COMMON, mage.cards.a.AnabaShaman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anaba Spirit Crafter", 86, Rarity.RARE, mage.cards.a.AnabaSpiritCrafter.class)); + cards.add(new SetCardInfo("An-Havva Constable", 51, Rarity.RARE, mage.cards.a.AnHavvaConstable.class)); + cards.add(new SetCardInfo("An-Havva Inn", 52, Rarity.UNCOMMON, mage.cards.a.AnHavvaInn.class)); + cards.add(new SetCardInfo("An-Havva Township", 136, Rarity.UNCOMMON, mage.cards.a.AnHavvaTownship.class)); + cards.add(new SetCardInfo("An-Zerrin Ruins", 87, Rarity.RARE, mage.cards.a.AnZerrinRuins.class)); + cards.add(new SetCardInfo("Apocalypse Chime", 126, Rarity.RARE, mage.cards.a.ApocalypseChime.class)); + cards.add(new SetCardInfo("Autumn Willow", 53, Rarity.RARE, mage.cards.a.AutumnWillow.class)); + cards.add(new SetCardInfo("Aysen Abbey", 137, Rarity.UNCOMMON, mage.cards.a.AysenAbbey.class)); + cards.add(new SetCardInfo("Aysen Bureaucrats", 104, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aysen Bureaucrats", 105, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aysen Crusader", 106, Rarity.RARE, mage.cards.a.AysenCrusader.class)); + cards.add(new SetCardInfo("Aysen Highway", 107, Rarity.RARE, mage.cards.a.AysenHighway.class)); + cards.add(new SetCardInfo("Baki's Curse", 27, Rarity.RARE, mage.cards.b.BakisCurse.class)); + cards.add(new SetCardInfo("Baron Sengir", 1, Rarity.RARE, mage.cards.b.BaronSengir.class)); + cards.add(new SetCardInfo("Beast Walkers", 108, Rarity.RARE, mage.cards.b.BeastWalkers.class)); + cards.add(new SetCardInfo("Black Carriage", 2, Rarity.RARE, mage.cards.b.BlackCarriage.class)); + cards.add(new SetCardInfo("Broken Visage", 3, Rarity.RARE, mage.cards.b.BrokenVisage.class)); + cards.add(new SetCardInfo("Carapace", 54, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Carapace", 55, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Castle Sengir", 138, Rarity.UNCOMMON, mage.cards.c.CastleSengir.class)); + cards.add(new SetCardInfo("Cemetery Gate", 4, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cemetery Gate", 5, Rarity.COMMON, CemeteryGate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chain Stasis", 28, Rarity.RARE, mage.cards.c.ChainStasis.class)); + cards.add(new SetCardInfo("Chandler", 88, Rarity.COMMON, mage.cards.c.Chandler.class)); + cards.add(new SetCardInfo("Clockwork Gnomes", 127, Rarity.COMMON, mage.cards.c.ClockworkGnomes.class)); + cards.add(new SetCardInfo("Clockwork Swarm", 129, Rarity.COMMON, mage.cards.c.ClockworkSwarm.class)); + cards.add(new SetCardInfo("Coral Reef", 29, Rarity.COMMON, mage.cards.c.CoralReef.class)); + cards.add(new SetCardInfo("Dark Maze", 30, Rarity.COMMON, mage.cards.d.DarkMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Maze", 31, Rarity.COMMON, mage.cards.d.DarkMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Daughter of Autumn", 56, Rarity.RARE, mage.cards.d.DaughterOfAutumn.class)); + cards.add(new SetCardInfo("Death Speakers", 109, Rarity.UNCOMMON, mage.cards.d.DeathSpeakers.class)); + cards.add(new SetCardInfo("Didgeridoo", 130, Rarity.RARE, mage.cards.d.Didgeridoo.class)); + cards.add(new SetCardInfo("Drudge Spell", 6, Rarity.UNCOMMON, mage.cards.d.DrudgeSpell.class)); + cards.add(new SetCardInfo("Dry Spell", 7, Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dry Spell", 8, Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dwarven Pony", 89, Rarity.RARE, mage.cards.d.DwarvenPony.class)); + cards.add(new SetCardInfo("Dwarven Trader", 91, Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dwarven Trader", 92, Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ebony Rhino", 131, Rarity.COMMON, mage.cards.e.EbonyRhino.class)); + cards.add(new SetCardInfo("Eron the Relentless", 93, Rarity.UNCOMMON, mage.cards.e.EronTheRelentless.class)); + cards.add(new SetCardInfo("Evaporate", 94, Rarity.UNCOMMON, mage.cards.e.Evaporate.class)); + cards.add(new SetCardInfo("Faerie Noble", 57, Rarity.RARE, mage.cards.f.FaerieNoble.class)); + cards.add(new SetCardInfo("Feast of the Unicorn", 9, Rarity.COMMON, FeastOfTheUnicorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Feast of the Unicorn", 10, Rarity.COMMON, FeastOfTheUnicorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Feroz's Ban", 132, Rarity.RARE, mage.cards.f.FerozsBan.class)); + cards.add(new SetCardInfo("Folk of An-Havva", 58, Rarity.COMMON, FolkOfAnHavva.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Folk of An-Havva", 59, Rarity.COMMON, FolkOfAnHavva.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forget", 32, Rarity.RARE, mage.cards.f.Forget.class)); + cards.add(new SetCardInfo("Ghost Hounds", 12, Rarity.UNCOMMON, mage.cards.g.GhostHounds.class)); + cards.add(new SetCardInfo("Grandmother Sengir", 13, Rarity.RARE, mage.cards.g.GrandmotherSengir.class)); + cards.add(new SetCardInfo("Hazduhr the Abbot", 110, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); + cards.add(new SetCardInfo("Headstone", 15, Rarity.COMMON, mage.cards.h.Headstone.class)); + cards.add(new SetCardInfo("Hungry Mist", 60, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hungry Mist", 61, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ihsan's Shade", 16, Rarity.UNCOMMON, mage.cards.i.IhsansShade.class)); + cards.add(new SetCardInfo("Irini Sengir", 17, Rarity.UNCOMMON, mage.cards.i.IriniSengir.class)); + cards.add(new SetCardInfo("Jinx", 36, Rarity.COMMON, mage.cards.j.Jinx.class)); + cards.add(new SetCardInfo("Joven", 97, Rarity.COMMON, mage.cards.j.Joven.class)); + cards.add(new SetCardInfo("Joven's Tools", 133, Rarity.UNCOMMON, mage.cards.j.JovensTools.class)); + cards.add(new SetCardInfo("Koskun Falls", 18, Rarity.RARE, mage.cards.k.KoskunFalls.class)); + cards.add(new SetCardInfo("Koskun Keep", 139, Rarity.UNCOMMON, mage.cards.k.KoskunKeep.class)); + cards.add(new SetCardInfo("Labyrinth Minotaur", 38, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); + cards.add(new SetCardInfo("Leaping Lizard", 63, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); + cards.add(new SetCardInfo("Leeches", 111, Rarity.RARE, mage.cards.l.Leeches.class)); + cards.add(new SetCardInfo("Mammoth Harness", 64, Rarity.RARE, mage.cards.m.MammothHarness.class)); + cards.add(new SetCardInfo("Marjhan", 39, Rarity.RARE, mage.cards.m.Marjhan.class)); + cards.add(new SetCardInfo("Memory Lapse", 40, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memory Lapse", 41, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merchant Scroll", 42, Rarity.COMMON, mage.cards.m.MerchantScroll.class)); + cards.add(new SetCardInfo("Mesa Falcon", 112, Rarity.COMMON, MesaFalcon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mesa Falcon", 113, Rarity.COMMON, MesaFalcon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystic Decree", 43, Rarity.RARE, mage.cards.m.MysticDecree.class)); + cards.add(new SetCardInfo("Narwhal", 44, Rarity.RARE, mage.cards.n.Narwhal.class)); + cards.add(new SetCardInfo("Primal Order", 65, Rarity.RARE, mage.cards.p.PrimalOrder.class)); + cards.add(new SetCardInfo("Rashka the Slayer", 115, Rarity.RARE, mage.cards.r.RashkaTheSlayer.class)); + cards.add(new SetCardInfo("Reef Pirates", 45, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reef Pirates", 46, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Renewal", 66, Rarity.COMMON, mage.cards.r.Renewal.class)); + cards.add(new SetCardInfo("Retribution", 99, Rarity.UNCOMMON, mage.cards.r.Retribution.class)); + cards.add(new SetCardInfo("Reveka, Wizard Savant", 47, Rarity.RARE, mage.cards.r.RevekaWizardSavant.class)); + cards.add(new SetCardInfo("Roots", 68, Rarity.UNCOMMON, mage.cards.r.Roots.class)); + cards.add(new SetCardInfo("Root Spider", 67, Rarity.UNCOMMON, mage.cards.r.RootSpider.class)); + cards.add(new SetCardInfo("Roterothopter", 134, Rarity.COMMON, mage.cards.r.Roterothopter.class)); + cards.add(new SetCardInfo("Sea Sprite", 48, Rarity.UNCOMMON, mage.cards.s.SeaSprite.class)); + cards.add(new SetCardInfo("Sengir Autocrat", 19, Rarity.UNCOMMON, mage.cards.s.SengirAutocrat.class)); + cards.add(new SetCardInfo("Sengir Bats", 20, Rarity.COMMON, SengirBats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sengir Bats", 21, Rarity.COMMON, SengirBats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Serra Aviary", 118, Rarity.RARE, mage.cards.s.SerraAviary.class)); + cards.add(new SetCardInfo("Serra Paladin", 121, Rarity.COMMON, mage.cards.s.SerraPaladin.class)); + cards.add(new SetCardInfo("Serrated Arrows", 135, Rarity.COMMON, mage.cards.s.SerratedArrows.class)); + cards.add(new SetCardInfo("Shrink", 70, Rarity.COMMON, mage.cards.s.Shrink.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shrink", 71, Rarity.COMMON, mage.cards.s.Shrink.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soraya the Falconer", 122, Rarity.RARE, mage.cards.s.SorayaTheFalconer.class)); + cards.add(new SetCardInfo("Spectral Bears", 72, Rarity.UNCOMMON, mage.cards.s.SpectralBears.class)); + cards.add(new SetCardInfo("Torture", 23, Rarity.COMMON, Torture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Torture", 24, Rarity.COMMON, Torture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trade Caravan", 123, Rarity.COMMON, mage.cards.t.TradeCaravan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trade Caravan", 124, Rarity.COMMON, mage.cards.t.TradeCaravan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Truce", 125, Rarity.RARE, mage.cards.t.Truce.class)); + cards.add(new SetCardInfo("Veldrane of Sengir", 25, Rarity.RARE, mage.cards.v.VeldraneOfSengir.class)); + cards.add(new SetCardInfo("Wall of Kelp", 50, Rarity.RARE, mage.cards.w.WallOfKelp.class)); + cards.add(new SetCardInfo("Willow Faerie", 73, Rarity.COMMON, WillowFaerie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Willow Faerie", 74, Rarity.COMMON, WillowFaerie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Willow Priestess", 75, Rarity.RARE, mage.cards.w.WillowPriestess.class)); + cards.add(new SetCardInfo("Winter Sky", 100, Rarity.RARE, mage.cards.w.WinterSky.class)); + cards.add(new SetCardInfo("Wizards' School", 140, Rarity.UNCOMMON, mage.cards.w.WizardsSchool.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index 9fc0fbda65d..3091a05e182 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -215,6 +215,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Red Cliffs Armada", 62, Rarity.COMMON, mage.cards.r.RedCliffsArmada.class)); cards.add(new SetCardInfo("Reinforcements", 28, Rarity.COMMON, mage.cards.r.Reinforcements.class)); cards.add(new SetCardInfo("Reprisal", 29, Rarity.COMMON, mage.cards.r.Reprisal.class)); + cards.add(new SetCardInfo("Retribution", 148, Rarity.UNCOMMON, mage.cards.r.Retribution.class)); cards.add(new SetCardInfo("Righteous Fury", 30, Rarity.RARE, mage.cards.r.RighteousFury.class)); cards.add(new SetCardInfo("Ritual of Subdual", 174, Rarity.RARE, mage.cards.r.RitualOfSubdual.class)); cards.add(new SetCardInfo("Ritual of the Machine", 109, Rarity.RARE, mage.cards.r.RitualOfTheMachine.class)); From aa96caa554e3ce9517fe3594510e13b157519c26 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Thu, 29 Mar 2018 12:54:43 +0200 Subject: [PATCH 40/65] Removed useless else case --- .../src/main/java/mage/client/util/URLHandler.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/util/URLHandler.java b/Mage.Client/src/main/java/mage/client/util/URLHandler.java index 20d05058f60..6208bddbf71 100644 --- a/Mage.Client/src/main/java/mage/client/util/URLHandler.java +++ b/Mage.Client/src/main/java/mage/client/util/URLHandler.java @@ -57,9 +57,7 @@ public class URLHandler { } catch (IOException | URISyntaxException ex) { // do nothing } - } else { - //do nothing - } + } } } }; @@ -76,11 +74,11 @@ public class URLHandler { for (String item : parts) { try { URL url = new URL(item); - // The item is a valid URL + // The item is already a valid URL output = output + "" + url + " "; } catch (MalformedURLException e) { - //The item might still be an URL + //The item might still be a URL if (item.startsWith("www.")) { output = output + "" + item + " "; } else { @@ -103,10 +101,10 @@ public class URLHandler { for (String item : parts) { try { URL url = new URL(item); - // The item is a valid URL + // The item is already a valid URL output = url.toString(); } catch (MalformedURLException e) { - //The item might still be an URL + //The item might still be a URL if (item.startsWith("www.")) { output = "http://" + item; } From a7ffaafcb04d257f4eeff87220880df4a61fe51d Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 29 Mar 2018 13:53:59 +0200 Subject: [PATCH 41/65] made close call safe --- Mage.Client/src/main/java/org/mage/card/arcane/Util.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java index 2645c314dbb..cbc3af717bd 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java @@ -1,5 +1,7 @@ package org.mage.card.arcane; +import mage.util.StreamUtils; + import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -42,7 +44,7 @@ public final class Util { socket = new DatagramSocket(); broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces()); } finally { - socket.close(); + StreamUtils.closeQuietly(socket); } } From fed2f317894b3879fe18f36c4a3821f385c3d0b6 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 29 Mar 2018 13:56:10 +0200 Subject: [PATCH 42/65] set lock calls back to original place as per request --- Mage.Server/src/main/java/mage/server/ChatSession.java | 2 +- Mage.Server/src/main/java/mage/server/UserManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index e73c0410f15..da9b897f7e7 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -90,8 +90,8 @@ public class ChatSession { String userName = clients.get(userId); if (reason != DisconnectReason.LostConnection) { // for lost connection the user will be reconnected or session expire so no removeUserFromAllTablesAndChat of chat yet final Lock w = lock.writeLock(); + w.lock(); try { - w.lock(); clients.remove(userId); } finally { w.unlock(); diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 767f7bb59a5..cd30111745a 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -231,8 +231,8 @@ public enum UserManager { } logger.debug("Users to remove " + toRemove.size()); final Lock w = lock.readLock(); + w.lock(); try { - w.lock(); for (User user : toRemove) { users.remove(user.getId()); } From 4dd34902c145e1148fc48d1fa022a8e8fed2b0af Mon Sep 17 00:00:00 2001 From: Christiaan Date: Thu, 29 Mar 2018 14:14:36 +0200 Subject: [PATCH 43/65] Fixed card count in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 131eda1e52b..afb6bf7edec 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) -XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 328000 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented. +XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 32800 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented. There are public servers where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends. From fc065bbe3cafc9a851c4dd862f8271772f85ec9c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Mar 2018 17:55:22 +0400 Subject: [PATCH 44/65] Little fixes --- Mage.Sets/src/mage/cards/n/NaturesWill.java | 1 + Mage.Sets/src/mage/sets/MastersEdition.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/n/NaturesWill.java b/Mage.Sets/src/mage/cards/n/NaturesWill.java index c6a2e8bbc06..491f83febf4 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesWill.java +++ b/Mage.Sets/src/mage/cards/n/NaturesWill.java @@ -93,6 +93,7 @@ class NaturesWillEffect extends OneShotEffect { land.untap(game); } } + return true; } return false; } diff --git a/Mage.Sets/src/mage/sets/MastersEdition.java b/Mage.Sets/src/mage/sets/MastersEdition.java index cdf94aba0e5..8eafe3bc3c6 100644 --- a/Mage.Sets/src/mage/sets/MastersEdition.java +++ b/Mage.Sets/src/mage/sets/MastersEdition.java @@ -206,7 +206,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Rabid Wombat", 126, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); cards.add(new SetCardInfo("Rainbow Vale", 179, Rarity.RARE, mage.cards.r.RainbowVale.class)); cards.add(new SetCardInfo("Righteous Avengers", 25, Rarity.COMMON, mage.cards.r.RighteousAvengers.class)); - cards.add(new SetCardInfo("Ring of Ma'rûf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class)); + cards.add(new SetCardInfo("Ring of Ma'ruf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class)); cards.add(new SetCardInfo("River Merfolk", 47, Rarity.COMMON, mage.cards.r.RiverMerfolk.class)); cards.add(new SetCardInfo("Roots", 127, Rarity.COMMON, mage.cards.r.Roots.class)); cards.add(new SetCardInfo("Scryb Sprites", 128, Rarity.COMMON, mage.cards.s.ScrybSprites.class)); From 6c98f4802eedca585176b3330bd925e5e1630717 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 00:06:44 +0000 Subject: [PATCH 45/65] Fix for Cabal Slaver Cabal Slaver's ability caused the damaging goblin's controller to discard --- .../DealsDamageToAPlayerAllTriggeredAbility.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java index 4dcca733cda..aa57b85dc1c 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java @@ -47,12 +47,18 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp private final FilterPermanent filter; private final SetTargetPointer setTargetPointer; private final boolean onlyCombat; + private final boolean affectsDefendingPlayer; public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat) { + this(effect, filter, optional, setTargetPointer, onlyCombat, false); + } + + public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat, boolean affectsDefendingPlayer) { super(Zone.BATTLEFIELD, effect, optional); this.setTargetPointer = setTargetPointer; this.filter = filter; this.onlyCombat = onlyCombat; + this.affectsDefendingPlayer = affectsDefendingPlayer; } public DealsDamageToAPlayerAllTriggeredAbility(final DealsDamageToAPlayerAllTriggeredAbility ability) { @@ -60,6 +66,7 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp this.setTargetPointer = ability.setTargetPointer; this.filter = ability.filter; this.onlyCombat = ability.onlyCombat; + this.affectsDefendingPlayer = ability.affectsDefendingPlayer; } @Override @@ -81,6 +88,10 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp for (Effect effect : this.getEffects()) { effect.setValue("damage", event.getAmount()); effect.setValue("sourceId", event.getSourceId()); + if (affectsDefendingPlayer) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + continue; + } switch (setTargetPointer) { case PLAYER: effect.setTargetPointer(new FixedTarget(permanent.getControllerId())); From 1dff6d9c486e1b31e9a80385f5d0ef2346f0805d Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 00:07:36 +0000 Subject: [PATCH 46/65] Fix for Cabal Slaver Cabal Slaver's ability caused the damaging goblin's controller to discard --- Mage.Sets/src/mage/cards/c/CabalSlaver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/c/CabalSlaver.java b/Mage.Sets/src/mage/cards/c/CabalSlaver.java index 3cbe12b1e67..8958f5e40cb 100644 --- a/Mage.Sets/src/mage/cards/c/CabalSlaver.java +++ b/Mage.Sets/src/mage/cards/c/CabalSlaver.java @@ -59,7 +59,7 @@ public class CabalSlaver extends CardImpl { this.toughness = new MageInt(1); // Whenever a Goblin deals combat damage to a player, that player discards a card. - this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.PLAYER, true)); + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.NONE, true, true)); } public CabalSlaver(final CabalSlaver card) { From b69f2c4fd820ddd9a2c61123b674944ecd385ec9 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sat, 31 Mar 2018 17:55:55 +1100 Subject: [PATCH 47/65] Add 'Choose Matching' to deck editor --- .../java/mage/client/cards/DragCardGrid.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index c2435410ac9..258892ee88f 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1083,6 +1083,22 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } repaint(); } + + private void chooseMatching() { + Collection toMatch = dragCardList(); + + for (DragCardGridListener l : listeners) { + for (CardView card : allCards) { + for (CardView aMatch : toMatch) { + if (card.getName().equals(aMatch.getName())) { + card.setSelected(true); + cardViews.get(card.getId()).update(card); + } + } + } + } + repaint(); + } private void showAll() { for (DragCardGridListener l : listeners) { @@ -1704,6 +1720,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg JMenuItem invertSelection = new JMenuItem("Invert Selection"); invertSelection.addActionListener(e2 -> invertSelection()); menu.add(invertSelection); + + JMenuItem chooseMatching = new JMenuItem("Choose Matching"); + chooseMatching.addActionListener(e2 -> chooseMatching()); + menu.add(chooseMatching); // Show 'Duplicate Selection' for FREE_BUILDING if (this.mode == Constants.DeckEditorMode.FREE_BUILDING) { From 7bbfe9f2ae6a91a2874114eb6d3e7375218bb919 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 13:41:38 +0000 Subject: [PATCH 48/65] Growth Spurt text fix --- Mage.Sets/src/mage/cards/g/GrowthSpurt.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GrowthSpurt.java b/Mage.Sets/src/mage/cards/g/GrowthSpurt.java index 1e27dd000e6..1e092f5e751 100644 --- a/Mage.Sets/src/mage/cards/g/GrowthSpurt.java +++ b/Mage.Sets/src/mage/cards/g/GrowthSpurt.java @@ -71,7 +71,7 @@ public class GrowthSpurt extends CardImpl { class GrowthSpurtEffect extends OneShotEffect { GrowthSpurtEffect() { super(Outcome.BoostCreature); - this.staticText = "todo"; //TODO + this.staticText = "Roll a six-sided die. Target creature gets +X/+X until end of turn, where X is the result"; } GrowthSpurtEffect(final GrowthSpurtEffect effect) { From 936bab33030ee11405fd74bca08f44d6aaa3a6c6 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 00:43:45 -0400 Subject: [PATCH 49/65] Add missing mountain to Commander 2016 Entropic Uprising deck Fixes issue #4694 --- .../Commander/Commander 2016/Entropic Uprising (UBRG).dck | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck b/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck index e2ebfff4137..fb309083023 100644 --- a/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck +++ b/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck @@ -80,7 +80,7 @@ 1 [C16:104] Windfall 1 [C16:148] Far Wanderings 1 [C16:46] Thrasios, Triton Hero -1 [C16:346] Mountain +2 [C16:346] Mountain 2 [C16:347] Mountain 1 [C16:348] Mountain 1 [C16:105] Army of the Damned From 20f03cfcb136dde89db4330e8dcfb344ab31f379 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 1 Apr 2018 12:33:59 +0200 Subject: [PATCH 50/65] Some minor changes to Momir code. --- .../sample-decks/Momir Basic/Momir Basic.dck | 31 +++++-------------- .../src/mage/game/MomirDuel.java | 17 ---------- .../src/mage/game/MomirFreeForAllType.java | 4 +-- 3 files changed, 10 insertions(+), 42 deletions(-) diff --git a/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck b/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck index 1295622aa28..7720b6c3a94 100644 --- a/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck +++ b/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck @@ -1,23 +1,8 @@ -NAME:Mormir Basic -3 [BFZ:259] Island -3 [BFZ:261] Swamp -3 [BFZ:250] Plains -3 [BFZ:272] Forest -3 [BFZ:260] Swamp -3 [BFZ:271] Forest -3 [BFZ:270] Forest -3 [BFZ:265] Mountain -2 [BFZ:254] Plains -3 [BFZ:264] Swamp -3 [BFZ:274] Forest -1 [BFZ:252] Plains -3 [BFZ:262] Swamp -3 [BFZ:251] Plains -2 [BFZ:273] Forest -3 [BFZ:258] Island -2 [BFZ:269] Mountain -3 [BFZ:268] Mountain -3 [BFZ:257] Island -3 [BFZ:267] Mountain -3 [BFZ:266] Mountain -2 [BFZ:255] Island +NAME:Mormir Basic +12 [BFZ:250a] Plains +12 [BFZ:260a] Swamp +12 [BFZ:270a] Forest +12 [BFZ:265a] Mountain +12 [BFZ:255a] Island +LAYOUT MAIN:(1,5)(COLOR_IDENTITY,true,5)|([BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a])([BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a])([BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a])([BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a])([BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a]) +LAYOUT SIDEBOARD:(0,0)(COLOR,true,5)| diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java index a5f1ce81cf9..37ed63aa184 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java @@ -28,38 +28,21 @@ package mage.game; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.DiscardCardCost; -import mage.abilities.costs.mana.VariableManaCost; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; -import mage.cards.Card; -import mage.cards.ExpansionSet; -import mage.cards.Sets; -import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; -import mage.constants.CardType; import mage.constants.MultiplayerAttackOption; -import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; -import mage.constants.SetType; -import mage.constants.TimingRule; import mage.constants.Zone; -import mage.game.command.Emblem; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; -import mage.game.permanent.token.EmptyToken; import mage.game.turn.TurnMod; import mage.players.Player; -import mage.util.CardUtil; -import mage.util.RandomUtil; /** * diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java index 5eb6c301702..21f01c65f9d 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java @@ -38,14 +38,14 @@ public class MomirFreeForAllType extends MatchType { public MomirFreeForAllType() { this.name = "Momir Basic Free For All"; this.maxPlayers = 10; - this.minPlayers = 2; + this.minPlayers = 3; this.numTeams = 0; this.useAttackOption = true; this.useRange = true; this.sideboardingAllowed = false; } - protected MomirFreeForAllType(final MomirFreeForAllType matchType){ + protected MomirFreeForAllType(final MomirFreeForAllType matchType) { super(matchType); } From 2ff3d2ca872648e62bdecb9fd591007bcba5b582 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 1 Apr 2018 21:40:16 +1000 Subject: [PATCH 51/65] Add an on-hover highlight effect. --- .../mage/client/components/HoverButton.java | 95 ++++++++++++++++++- .../java/mage/client/game/PlayerPanelExt.java | 72 +------------- 2 files changed, 96 insertions(+), 71 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 863cb49109c..6930e9a9643 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -8,10 +8,13 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.FontRenderContext; import javax.swing.JPanel; +import javax.swing.Timer; import mage.client.util.Command; /** @@ -43,6 +46,7 @@ public class HoverButton extends JPanel implements MouseListener { private Image topTextImageRight; private String centerText; + private boolean wasHovered = false; private boolean isHovered = false; private boolean isSelected = false; private boolean drawSet = false; @@ -52,7 +56,8 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private Color centerTextColor = new Color(200, 190, 0, 180); + private Color centerTextColor = new Color(200, 210, 0, 180); + private Color origCenterTextColor = new Color(200, 210, 0, 180); private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); @@ -64,6 +69,13 @@ public class HoverButton extends JPanel implements MouseListener { private boolean alignTextLeft = false; + Timer faderGainLife = null; + Timer faderLoseLife = null; + private int loseX = 0; + private int gainX = 0; + private boolean doLoseFade = true; + private boolean doGainFade = true; + public HoverButton(String text, Image image, Rectangle size) { this(text, image, image, null, image, size); if (image == null) { @@ -95,6 +107,10 @@ public class HoverButton extends JPanel implements MouseListener { Graphics2D g2d = (Graphics2D) g; if (isEnabled()) { if (isHovered || textAlwaysVisible) { + if (isHovered) { + wasHovered = true; + setCenterColor(Color.YELLOW); + } g.drawImage(hoverImage, 0, 0, imageSize.width, imageSize.height, this); if (text != null) { if (textColor != null) { @@ -109,6 +125,10 @@ public class HoverButton extends JPanel implements MouseListener { g2d.drawString(text, textOffsetX, textOffsetY); } } else { + if (wasHovered) { + wasHovered = false; + setCenterColor(origCenterTextColor); + } g.drawImage(image, 0, 0, imageSize.width, imageSize.height, this); } if (isSelected) { @@ -174,7 +194,7 @@ public class HoverButton extends JPanel implements MouseListener { g2d.drawString(set, 0, 0); } } - + public void setCenterColor(Color c) { centerTextColor = c; } @@ -361,4 +381,75 @@ public class HoverButton extends JPanel implements MouseListener { // Draw the String g.drawString(text, x, y); } + + public void gainLifeDisplay() { + if (faderGainLife == null && doGainFade) { + doGainFade = false; + faderGainLife = new Timer(50, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + gainX++; + int alpha = Math.max(250 - gainX, 180); + setCenterColor(new Color(2 * gainX, 210, 255, alpha)); + repaint(); + if (gainX >= 100) { + setCenterColor(new Color(200, 210, 0, 180)); + gainX = 100; + + if (faderGainLife != null) { + faderGainLife.stop(); + faderGainLife.setRepeats(false); + faderGainLife.setDelay(50000); + } + } + } + }); + gainX = 0; + faderGainLife.setInitialDelay(25); + faderGainLife.setRepeats(true); + faderGainLife.start(); + } + } + + public void loseLifeDisplay() { + if (faderLoseLife == null && doLoseFade) { + doLoseFade = false; + faderLoseLife = new Timer(50, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + loseX++; + int alpha = Math.max(250 - loseX, 180); + setCenterColor(new Color(250 - loseX / 2, 130 + loseX, 0, alpha)); + repaint(); + if (loseX >= 100) { + setCenterColor(new Color(200, 210, 0, 180)); + loseX = 100; + stopLifeDisplay(); + + if (faderLoseLife != null) { + faderLoseLife.stop(); + faderLoseLife.setRepeats(false); + faderLoseLife.setDelay(50000); + } + } + } + }); + loseX = 0; + faderLoseLife.setInitialDelay(25); + faderLoseLife.setRepeats(true); + faderLoseLife.start(); + } + } + + public void stopLifeDisplay() { + + if (faderGainLife != null && gainX >= 100) { + faderGainLife.stop(); + faderGainLife = null; + } + doGainFade = true; + if (faderLoseLife != null && loseX >= 100) { + faderLoseLife.stop(); + faderLoseLife = null; + } + doLoseFade = true; + } } diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index ed4b0d8a978..bc21430a31a 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -38,8 +38,6 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.LinkedHashSet; @@ -55,7 +53,6 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.SwingConstants; -import javax.swing.Timer; import javax.swing.border.Border; import javax.swing.border.LineBorder; import mage.cards.decks.importer.DckDeckImporter; @@ -117,12 +114,6 @@ public class PlayerPanelExt extends javax.swing.JPanel { private String flagName; private String basicTooltipText; private static final Map playerLives = new HashMap<>(); - private int loseX; - private boolean doLoseFade = true; - private int gainX; - private boolean doGainFade = true; - Timer faderGainLife = null; - Timer faderLoseLife = null; private PriorityTimer timer; @@ -200,69 +191,12 @@ public class PlayerPanelExt extends javax.swing.JPanel { if (displayLife) { if (playerLife != pastLife) { if (playerLife > pastLife) { - if (faderGainLife == null && doGainFade) { - doGainFade = false; - faderGainLife = new Timer(50, new ActionListener() { - public void actionPerformed(ActionEvent ae) { - gainX++; - int alpha = Math.max(250 - gainX, 180); - avatar.setCenterColor(new Color(2 * gainX, 190, 255, alpha)); - avatar.repaint(); - if (gainX >= 100) { - avatar.setCenterColor(new Color(200, 190, 0, 180)); - gainX = 100; - - if (faderGainLife != null) { - faderGainLife.stop(); - faderGainLife.setRepeats(false); - faderGainLife.setDelay(50000); - } - } - } - }); - gainX = 0; - faderGainLife.setInitialDelay(25); - faderGainLife.setRepeats(true); - faderGainLife.start(); - } + avatar.gainLifeDisplay(); } else if (playerLife < pastLife) { - if (faderLoseLife == null && doLoseFade) { - doLoseFade = false; - faderLoseLife = new Timer(50, new ActionListener() { - public void actionPerformed(ActionEvent ae) { - loseX++; - int alpha = Math.max(250 - loseX, 180); - avatar.setCenterColor(new Color(250 - loseX / 2, 140 + loseX / 2, 0, alpha)); - avatar.repaint(); - if (loseX >= 100) { - avatar.setCenterColor(new Color(200, 190, 0, 180)); - loseX = 100; - - if (faderLoseLife != null) { - faderLoseLife.stop(); - faderLoseLife.setRepeats(false); - faderLoseLife.setDelay(50000); - } - } - } - }); - loseX = 0; - faderLoseLife.setInitialDelay(25); - faderLoseLife.setRepeats(true); - faderLoseLife.start(); - } + avatar.loseLifeDisplay(); } } else if (playerLife == pastLife) { - if (faderGainLife != null && gainX >= 100) { - faderGainLife.stop(); - faderGainLife = null; - } - doGainFade = true; - if (faderLoseLife != null && loseX >= 100) { - faderLoseLife.stop(); - faderLoseLife = null; - } - doLoseFade = true; + avatar.stopLifeDisplay(); } } From 7698856d296dffcb63a7c70776d19c8df56eb645 Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 2 Apr 2018 01:01:32 +1000 Subject: [PATCH 52/65] Add an on-hover highlight effect. --- .../mage/client/components/HoverButton.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 6930e9a9643..535a22d5cd1 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -8,11 +8,13 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; import javax.swing.JPanel; import javax.swing.Timer; import mage.client.util.Command; @@ -56,8 +58,8 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private Color centerTextColor = new Color(200, 210, 0, 180); - private Color origCenterTextColor = new Color(200, 210, 0, 180); + private Color centerTextColor = new Color(200, 210, 0, 200); + private Color origCenterTextColor = new Color(200, 210, 0, 200); private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); @@ -171,7 +173,7 @@ public class HoverButton extends JPanel implements MouseListener { } else if (val > 99) { fontSize = 34; } - drawCenteredString(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize)); + drawCenteredStringWOutline(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize)); } g2d.setColor(textColor); if (overlayImage != null) { @@ -369,7 +371,7 @@ public class HoverButton extends JPanel implements MouseListener { * @param rect The Rectangle to center the text in. * @param font */ - public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) { + public void drawCenteredStringWOutline(Graphics2D g, String text, Rectangle rect, Font font) { // Get the FontMetrics FontMetrics metrics = g.getFontMetrics(font); // Determine the X coordinate for the text @@ -378,8 +380,21 @@ public class HoverButton extends JPanel implements MouseListener { int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent(); // Set the font g.setFont(font); - // Draw the String - g.drawString(text, x, y); + + GlyphVector gv = font.createGlyphVector(g.getFontRenderContext(), text); + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + g.drawGlyphVector(gv, x, y); + + g.translate(x - 1, y - 1); + for (int i = 0; i < text.length(); i++) { + g.setColor(Color.BLACK); + g.draw(gv.getGlyphOutline(i)); + } + g.translate(-x + 1, -y + 1); + } public void gainLifeDisplay() { @@ -388,11 +403,11 @@ public class HoverButton extends JPanel implements MouseListener { faderGainLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { gainX++; - int alpha = Math.max(250 - gainX, 180); + int alpha = Math.max(250 - gainX, 200); setCenterColor(new Color(2 * gainX, 210, 255, alpha)); repaint(); if (gainX >= 100) { - setCenterColor(new Color(200, 210, 0, 180)); + setCenterColor(new Color(200, 210, 0, 200)); gainX = 100; if (faderGainLife != null) { @@ -416,11 +431,11 @@ public class HoverButton extends JPanel implements MouseListener { faderLoseLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { loseX++; - int alpha = Math.max(250 - loseX, 180); + int alpha = Math.max(250 - loseX, 200); setCenterColor(new Color(250 - loseX / 2, 130 + loseX, 0, alpha)); repaint(); if (loseX >= 100) { - setCenterColor(new Color(200, 210, 0, 180)); + setCenterColor(new Color(200, 210, 0, 200)); loseX = 100; stopLifeDisplay(); From ef44c57a32d81741e2fbdfc24312499181f29863 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 11:08:39 -0400 Subject: [PATCH 53/65] 2 additional Planeswalker decks --- .../Huatli, Dinosaur Knight.dck | 28 +++++++++++++++++++ .../Planeswalk Decks/Nissa, Genesis Mage.dck | 23 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck new file mode 100644 index 00000000000..539358b8f62 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck @@ -0,0 +1,28 @@ +2 [XLN:31] Raptor Companion +1 [XLN:135] Burning Sun's Avatar +2 [XLN:38] Slash of Talons +2 [XLN:36] Shining Aerosaur +1 [XLN:13] Goring Ceratops +3 [XLN:274] Mountain +3 [XLN:273] Mountain +2 [XLN:133] Bonded Horncrest +2 [XLN:275] Mountain +2 [XLN:30] Rallying Roar +3 [XLN:272] Mountain +2 [XLN:28] Pterodon Knight +2 [XLN:149] Lightning Strike +2 [XLN:146] Frenzied Raptor +4 [XLN:289] Stone Quarry +2 [XLN:288] Sun-Blessed Mount +2 [XLN:169] Tilonalli's Knight +1 [XLN:285] Huatli, Dinosaur Knight +2 [XLN:263] Plains +3 [XLN:262] Plains +3 [XLN:287] Huatli's Spurring +4 [XLN:286] Huatli's Snubhorn +2 [XLN:41] Territorial Hammerskull +3 [XLN:261] Plains +3 [XLN:260] Plains +2 [XLN:18] Kinjalli's Caller +LAYOUT MAIN:(1,4)(CARD_TYPE,false,50)|([XLN:13],[XLN:288],[XLN:288],[XLN:135],[XLN:18],[XLN:18],[XLN:286],[XLN:286],[XLN:286],[XLN:286],[XLN:31],[XLN:31],[XLN:169],[XLN:169],[XLN:146],[XLN:146],[XLN:41],[XLN:41],[XLN:133],[XLN:133],[XLN:28],[XLN:28],[XLN:36],[XLN:36])([XLN:287],[XLN:287],[XLN:287],[XLN:38],[XLN:38],[XLN:149],[XLN:149],[XLN:30],[XLN:30])([XLN:289],[XLN:289],[XLN:289],[XLN:289],[XLN:260],[XLN:260],[XLN:260],[XLN:261],[XLN:261],[XLN:261],[XLN:262],[XLN:262],[XLN:262],[XLN:263],[XLN:263],[XLN:272],[XLN:272],[XLN:272],[XLN:273],[XLN:273],[XLN:273],[XLN:274],[XLN:274],[XLN:274],[XLN:275],[XLN:275])([XLN:285]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck new file mode 100644 index 00000000000..c3ccd369b92 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck @@ -0,0 +1,23 @@ +4 [HOU:193] Island +4 [HOU:192] Island +2 [AKH:41] Angler Drake +2 [HOU:29] Aerial Guide +2 [AKH:209] Weaver of Currents +2 [HOU:30] Aven Reedstalker +2 [AKH:219] Spring // Mind +4 [HOU:204] Woodland Stream +3 [HOU:54] Unsummon +3 [HOU:201] Avid Reclaimer +3 [AKH:179] Pouncing Cheetah +1 [HOU:200] Nissa, Genesis Mage +2 [HOU:203] Nissa's Encouragement +2 [HOU:115] Feral Prowler +4 [HOU:202] Brambleweft Behemoth +1 [AKH:196] Bounty of the Luxa +7 [HOU:199] Forest +1 [HOU:154] Reason // Believe +2 [HOU:143] River Hoopoe +2 [HOU:110] Ambuscade +7 [HOU:198] Forest +LAYOUT MAIN:(1,7)(CARD_TYPE,false,50)|([HOU:115],[HOU:115],[HOU:143],[HOU:143],[HOU:201],[HOU:201],[HOU:201],[AKH:179],[AKH:179],[AKH:179],[HOU:29],[HOU:29],[AKH:209],[AKH:209],[HOU:30],[HOU:30],[HOU:202],[HOU:202],[HOU:202],[HOU:202],[AKH:41],[AKH:41])([AKH:196])([AKH:219],[AKH:219])([HOU:54],[HOU:54],[HOU:54],[HOU:110],[HOU:110])([HOU:204],[HOU:204],[HOU:204],[HOU:204],[HOU:192],[HOU:192],[HOU:192],[HOU:192],[HOU:193],[HOU:193],[HOU:193],[HOU:193],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199])([HOU:200])([HOU:154],[HOU:203],[HOU:203]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| From 56b124c5246753b79c6273201d4a6ca271591654 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 01:09:22 +0000 Subject: [PATCH 54/65] Implemented Mine, Mine, Mine! For some reason, making MineMineMineDontLoseEffect replace EventType.EMPTY_DRAW still resulted in a game loss --- Mage.Sets/src/mage/cards/m/MineMineMine.java | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MineMineMine.java diff --git a/Mage.Sets/src/mage/cards/m/MineMineMine.java b/Mage.Sets/src/mage/cards/m/MineMineMine.java new file mode 100644 index 00000000000..e5b7648e21b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MineMineMine.java @@ -0,0 +1,180 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect.HandSizeModification; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class MineMineMine extends CardImpl { + + public MineMineMine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{G}{G}"); + + // When Mine, Mine, Mine enters the battlefield, each player puts his or her library into his or her hand. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MineMineMineDrawEffect())); + + // Players have no maximum hand size and don't lose the game for drawing from an empty library. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, HandSizeModification.SET, TargetController.ANY) + .setText("Players have no maximum hand size and don't lose the game for drawing from an empty library"))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MineMineMineDontLoseEffect())); + + // Each player can't cast more than one spell each turn. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantCastMoreThanOneSpellEffect(TargetController.ANY))); + + // When Mine, Mine, Mine leaves the battlefield, each player shuffles his or her hand and graveyard into his or her library. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new MineMineMineShuffleEffect(), false)); + } + + public MineMineMine(final MineMineMine card) { + super(card); + } + + @Override + public MineMineMine copy() { + return new MineMineMine(this); + } +} + +class MineMineMineDrawEffect extends OneShotEffect { + + MineMineMineDrawEffect() { + super(Outcome.DrawCard); + this.staticText = "each player puts his or her library into his or her hand"; + } + + MineMineMineDrawEffect(final MineMineMineDrawEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + CardsImpl libraryCards = new CardsImpl(); + libraryCards.addAll(player.getLibrary().getCards(game)); + player.moveCards(libraryCards, Zone.HAND, source, game); + } + } + return true; + } + + @Override + public MineMineMineDrawEffect copy() { + return new MineMineMineDrawEffect(this); + } +} + +class MineMineMineDontLoseEffect extends ReplacementEffectImpl { + + MineMineMineDontLoseEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral); + } + + MineMineMineDontLoseEffect(final MineMineMineDontLoseEffect effect) { + super(effect); + } + + @Override + public MineMineMineDontLoseEffect copy() { + return new MineMineMineDontLoseEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DRAW_CARD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null && player.getLibrary().getCards(game).isEmpty()) { + return true; + } + return false; + } +} + +class MineMineMineShuffleEffect extends OneShotEffect { + + public MineMineMineShuffleEffect() { + super(Outcome.Neutral); + staticText = "each player shuffles his or her hand and graveyard into his or her library"; + } + + public MineMineMineShuffleEffect(final MineMineMineShuffleEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.moveCards(player.getHand(), Zone.LIBRARY, source, game); + player.moveCards(player.getGraveyard(), Zone.LIBRARY, source, game); + player.shuffleLibrary(source, game); + } + } + return true; + } + + @Override + public MineMineMineShuffleEffect copy() { + return new MineMineMineShuffleEffect(this); + } +} From ee2c475c874eb7eef2247d5b6f0971321fc33ccd Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 01:09:56 +0000 Subject: [PATCH 55/65] Implemented Mine, Mine, Mine! --- Mage.Sets/src/mage/sets/Unglued.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index f4de694b14f..4518e0b8e78 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -33,6 +33,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Jack-in-the-Mox", 75, Rarity.RARE, mage.cards.j.JackInTheMox.class)); cards.add(new SetCardInfo("Jumbo Imp", 34, Rarity.UNCOMMON, mage.cards.j.JumboImp.class)); cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class)); + cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class)); cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); From 97d0f328892cfe2baf22d978520bab9b4112eb1d Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 22:27:33 -0400 Subject: [PATCH 56/65] Update Feline Ferocity to include missing card Added missing "Stalking Leonin" that was implemented after deck was built. --- .../sample-decks/Commander/Commander 2017/FelineFerocity.dck | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck b/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck index 989a2561e45..14b8ff2111d 100644 --- a/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck +++ b/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck @@ -66,6 +66,7 @@ 1 [C17:158] Soul's Majesty 1 [C17:73] Spirit of the Hearth 1 [C17:224] Staff of Nin +1 [C17:7] Stalking Leonin 1 [C17:281] Stirring Wildwood 1 [C17:75] Sunspear Shikari 1 [C17:226] Swiftfoot Boots From 471d49892f67f82947073e06617b4e907cd9f9b4 Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 2 Apr 2018 19:31:05 +1000 Subject: [PATCH 57/65] Attempt to allow players to fix the game if the active/choosing/person with priority has left or has run down the clock --- .../main/java/mage/server/ChatManager.java | 21 +++++ .../java/mage/server/game/GameController.java | 82 ++++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index ed017b14229..d25448c3cfe 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -244,6 +244,27 @@ public enum ChatManager { } return true; } + if (command.startsWith("FIX")) { + message += "
" + GameManager.instance.getChatId(chatId); + ChatSession session = chatSessions.get(chatId); + if (session != null && session.getInfo() != null) { + String gameId = session.getInfo(); + if (gameId.startsWith("Game ")) { + UUID id = java.util.UUID.fromString(gameId.substring(5, gameId.length())); + for (Entry entry : GameManager.instance.getGameController().entrySet()) { + if (entry.getKey().equals(id)) { + GameController controller = entry.getValue(); + if (controller != null) { + message += controller.attemptToFixGame(); + chatSessions.get(chatId).broadcastInfoToUser(user, message); + } + } + } + + } + } + return true; + } if (command.startsWith("CARD ")) { Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase(Locale.ENGLISH)); if (matchPattern.find()) { diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 8bce119961f..3b18789ecf9 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.zip.GZIPOutputStream; import mage.MageException; import mage.abilities.Ability; +import mage.abilities.common.PassAbility; import mage.cards.Card; import mage.cards.Cards; import mage.cards.decks.Deck; @@ -57,6 +58,7 @@ import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; +import mage.game.turn.Phase; import mage.interfaces.Action; import mage.players.Player; import mage.server.*; @@ -1150,13 +1152,13 @@ public class GameController implements GameCallback { sb.append(state.getPlayerList()); sb.append("
getPlayers: "); sb.append(state.getPlayers()); - sb.append("
Player with Priority is: "); + sb.append("
Player with Priority is: "); if (state.getPriorityPlayerId() != null) { sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); } else { sb.append("noone!"); } - sb.append("
getRevealed: "); + sb.append("

getRevealed: "); sb.append(state.getRevealed()); sb.append("
getSpecialActions: "); sb.append(state.getSpecialActions()); @@ -1187,4 +1189,80 @@ public class GameController implements GameCallback { return sb.toString(); } + public String attemptToFixGame() { + if (game == null) { + return ""; + } + GameState state = game.getState(); + if (state == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("
Game State:
"); + sb.append(state); + boolean fixedAlready = false; + + sb.append("
Active player is: "); + sb.append(game.getPlayer(state.getActivePlayerId()).getName()); + PassAbility pass = new PassAbility(); + if (game.getPlayer(state.getActivePlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null) { + currentPhase.getStep().skipStep(game, state.getActivePlayerId()); + sb.append("
Forcibly passing the phase!"); + fixedAlready = true; + } else { + sb.append("
Current phase null"); + } + sb.append("
Active player has left"); + } + + sb.append("
getChoosingPlayerId: "); + if (state.getChoosingPlayerId() != null) { + if (game.getPlayer(state.getChoosingPlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null && !fixedAlready) { + currentPhase.getStep().endStep(game, state.getActivePlayerId()); + fixedAlready = true; + sb.append("
Forcibly passing the phase!"); + } else if (currentPhase == null) { + sb.append("
Current phase null"); + } + sb.append("
Choosing player has left"); + } + } + + sb.append("
Player with Priority is: "); + if (state.getPriorityPlayerId() != null) { + if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null && !fixedAlready) { + currentPhase.getStep().skipStep(game, state.getActivePlayerId()); + fixedAlready = true; + sb.append("
Forcibly passing the phase!"); + } + } + sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); + sb.append("
"); + } + + sb.append("
Future Timeout:"); + if (futureTimeout != null) { + sb.append("Cancelled?="); + sb.append(futureTimeout.isCancelled()); + sb.append(",,,Done?="); + sb.append(futureTimeout.isDone()); + sb.append(",,,GetDelay?="); + sb.append((int) futureTimeout.getDelay(TimeUnit.SECONDS)); + if ((int) futureTimeout.getDelay(TimeUnit.SECONDS) < 25) { + game.endTurn(pass); + sb.append("
Forcibly passing the turn!"); + } + } else { + sb.append("Not using future Timeout!"); + } + sb.append("
"); + return sb.toString(); + } + } From 1e2e53073c570c9613f967622d464da908284971 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 2 Apr 2018 15:30:57 +0200 Subject: [PATCH 58/65] * Deep Analysis - Fixed that its flashback costs did not work with mana casting cost modification effects (fixes #4677). --- Mage.Sets/src/mage/cards/d/DeepAnalysis.java | 13 +++----- .../src/mage/cards/m/MizzixOfTheIzmagnus.java | 12 +++---- .../abilities/keywords/FlashbackTest.java | 32 +++++++++++++++++++ .../modification/MizzixOfTheIzmagnusTest.java | 32 +++++++++++++++++++ .../main/java/mage/filter/StaticFilters.java | 8 ++++- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java index 7d186198370..2ad4b705318 100644 --- a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java +++ b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java @@ -28,9 +28,6 @@ package mage.cards.d; import java.util.UUID; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.CostsImpl; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DrawCardTargetEffect; @@ -48,18 +45,16 @@ import mage.target.TargetPlayer; public class DeepAnalysis extends CardImpl { public DeepAnalysis(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); // Target player draws two cards. this.getSpellAbility().addEffect(new DrawCardTargetEffect(2)); this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback-{1}{U}, Pay 3 life. - Costs costs = new CostsImpl<>(); - costs.add(new ManaCostsImpl("{1}{U}")); - costs.add(new PayLifeCost(3)); - this.addAbility(new FlashbackAbility(costs, TimingRule.SORCERY)); + FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.SORCERY); + ability.addCost(new PayLifeCost(3)); + this.addAbility(ability); } public DeepAnalysis(final DeepAnalysis card) { diff --git a/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java b/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java index d1e431b69cf..ce9a178b8d1 100644 --- a/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java +++ b/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java @@ -41,7 +41,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.predicate.Predicate; import mage.game.Game; @@ -62,7 +62,7 @@ public class MizzixOfTheIzmagnus extends CardImpl { } public MizzixOfTheIzmagnus(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.WIZARD); @@ -136,11 +136,11 @@ class MizzixOfTheIzmagnusCostReductionEffect extends CostModificationEffectImpl if (abilityToModify instanceof SpellAbility && abilityToModify.getControllerId().equals(source.getControllerId())) { Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); if (spell != null) { - return new FilterInstantOrSorceryCard().match(spell, source.getSourceId(), source.getControllerId(), game); - } else { - // used at least for flashback ability because Flashback ability doesn't use stack or for getPlayables where spell is not cast yet + return StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(spell, source.getSourceId(), source.getControllerId(), game); + } else if (((SpellAbility) abilityToModify).isCheckPlayableMode()) { + // Spell is not on the stack yet, but possible playable spells are determined Card sourceCard = game.getCard(abilityToModify.getSourceId()); - return sourceCard != null && new FilterInstantOrSorceryCard().match(sourceCard, source.getSourceId(), source.getControllerId(), game); + return sourceCard != null && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(sourceCard, source.getSourceId(), source.getControllerId(), game); } } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index 02bf64326b3..fd574695830 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -30,6 +30,7 @@ package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.TrampleAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -580,4 +581,35 @@ public class FlashbackTest extends CardTestPlayerBase { assertLife(playerA, 20); } + + /** + * Test cost reduction with mixed flashback costs + */ + @Test + public void testReduceMixedFlashbackCosts() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter. + // Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have. + addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2 + + // Target player draws two cards. + // Flashback-{1}{U}, Pay 3 life. + addCard(Zone.HAND, playerA, "Deep Analysis"); // {3}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deep Analysis"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Deep Analysis", 0); + assertExileCount(playerA, "Deep Analysis", 1); + assertHandCount(playerA, 4); + + assertCounterCount(playerA, CounterType.EXPERIENCE, 2); + + assertLife(playerA, 17); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java index 3d5c58d10b8..4aa99bbe7f6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java @@ -120,4 +120,36 @@ public class MizzixOfTheIzmagnusTest extends CardTestPlayerBase { assertLife(playerB, 17); } + + /** + * Test to reduce Flashback costs + */ + @Test + public void testReduceFlashbackCosts() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + // Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter. + // Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have. + addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2 + + // Engulfing Flames deals 1 damage to target creature. It can't be regenerated this turn. + // Flashback {3}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) + addCard(Zone.HAND, playerA, "Engulfing Flames"); // {R} + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");// 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Engulfing Flames", "Silvercoat Lion"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Engulfing Flames", 0); + assertExileCount(playerA, "Engulfing Flames", 1); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertCounterCount(playerA, CounterType.EXPERIENCE, 1); + + } } diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 27a367e88cb..97546761d2e 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -74,6 +74,12 @@ public final class StaticFilters { FILTER_CARD_A_NON_LAND.setLockedFilter(true); } + public static final FilterInstantOrSorceryCard FILTER_CARD_INSTANT_OR_SORCERY = new FilterInstantOrSorceryCard(); + + static { + FILTER_CARD_INSTANT_OR_SORCERY.setLockedFilter(true); + } + public static final FilterPermanent FILTER_PERMANENT = new FilterPermanent(); static { @@ -236,7 +242,7 @@ public final class StaticFilters { static { FILTER_BASIC_LAND_CARD.setLockedFilter(true); } - + // Used for sacrifice targets that don't need the "you control" text public static final FilterControlledLandPermanent FILTER_CONTROLLED_LAND_SHORT_TEXT = new FilterControlledLandPermanent("a land"); From e88a6a1fadd136f56fee2e69442239dac6f85c18 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 19:17:41 +0000 Subject: [PATCH 59/65] Implemented Brutal Suppression --- .../src/mage/cards/b/BrutalSuppression.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BrutalSuppression.java diff --git a/Mage.Sets/src/mage/cards/b/BrutalSuppression.java b/Mage.Sets/src/mage/cards/b/BrutalSuppression.java new file mode 100644 index 00000000000..5ed3b44b9f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrutalSuppression.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.AbilityType; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author L_J + */ +public class BrutalSuppression extends CardImpl { + + public BrutalSuppression(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); + + // Activated abilities of nontoken Rebels cost an additional "Sacrifice a land" to activate. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BrutalSuppressionAdditionalCostEffect())); + } + + public BrutalSuppression(final BrutalSuppression card) { + super(card); + } + + @Override + public BrutalSuppression copy() { + return new BrutalSuppression(this); + } +} + +class BrutalSuppressionAdditionalCostEffect extends CostModificationEffectImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("a land"); + static{ + filter.add(new CardTypePredicate(CardType.LAND)); + } + + private static final FilterPermanent filter2 = new FilterPermanent("nontoken Rebels"); + static{ + filter2.add(new SubtypePredicate(SubType.REBEL)); + filter.add(Predicates.not(new TokenPredicate())); + } + + BrutalSuppressionAdditionalCostEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); + this.staticText = "Activated abilities of nontoken Rebels cost an additional \"Sacrifice a land\" to activate"; + } + + BrutalSuppressionAdditionalCostEffect(BrutalSuppressionAdditionalCostEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true); + target.setRequired(false); + abilityToModify.addCost(new SacrificeTargetCost(target)); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify.getAbilityType() == AbilityType.ACTIVATED || abilityToModify.getAbilityType() == AbilityType.MANA) { + Permanent rebelPermanent = game.getPermanent(abilityToModify.getSourceId()); + if (rebelPermanent != null) { + return filter2.match(rebelPermanent, game); + } + } + return false; + } + + @Override + public BrutalSuppressionAdditionalCostEffect copy() { + return new BrutalSuppressionAdditionalCostEffect(this); + } +} From b2d4db1d2718238b1d6c4781c04df5c61fb0b391 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 19:18:06 +0000 Subject: [PATCH 60/65] Implemented Brutal Suppression --- Mage.Sets/src/mage/sets/Prophecy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Prophecy.java b/Mage.Sets/src/mage/sets/Prophecy.java index 25bd32e66ce..6708779181c 100644 --- a/Mage.Sets/src/mage/sets/Prophecy.java +++ b/Mage.Sets/src/mage/sets/Prophecy.java @@ -69,6 +69,7 @@ public class Prophecy extends ExpansionSet { cards.add(new SetCardInfo("Bog Elemental", 57, Rarity.RARE, mage.cards.b.BogElemental.class)); cards.add(new SetCardInfo("Bog Glider", 58, Rarity.COMMON, mage.cards.b.BogGlider.class)); cards.add(new SetCardInfo("Branded Brawlers", 84, Rarity.COMMON, mage.cards.b.BrandedBrawlers.class)); + cards.add(new SetCardInfo("Brutal Suppression", 85, Rarity.UNCOMMON, mage.cards.b.BrutalSuppression.class)); cards.add(new SetCardInfo("Calming Verse", 110, Rarity.COMMON, mage.cards.c.CalmingVerse.class)); cards.add(new SetCardInfo("Celestial Convergence", 5, Rarity.RARE, mage.cards.c.CelestialConvergence.class)); cards.add(new SetCardInfo("Chilling Apparition", 59, Rarity.UNCOMMON, mage.cards.c.ChillingApparition.class)); From 26c9c80d683c24f6134a4d65ea56d79770cac01d Mon Sep 17 00:00:00 2001 From: AdamJAKing Date: Mon, 2 Apr 2018 21:49:18 +0100 Subject: [PATCH 61/65] Card hint window now disappears for all cards --- Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 230728563ce..a124d63fa05 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -625,9 +625,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, if (gameCard.hideInfo()) { return; } - if (this.contains(e.getPoint())) { - return; - } + if (tooltipShowing) { synchronized (this) { if (tooltipShowing) { From 20a805e739ab88ad0816f63eba039f35f67691d8 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:23:02 +0000 Subject: [PATCH 62/65] Implemented Elvish Impersonators --- .../src/mage/cards/e/ElvishImpersonators.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ElvishImpersonators.java diff --git a/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java new file mode 100644 index 00000000000..6fa6244ad58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class ElvishImpersonators extends CardImpl { + + public ElvishImpersonators(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); + this.subtype.add(SubType.ELF); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result. + this.addAbility(new AsEntersBattlefieldAbility(new ElvishImpersonatorsEffect())); + } + + public ElvishImpersonators(final ElvishImpersonators card) { + super(card); + } + + @Override + public ElvishImpersonators copy() { + return new ElvishImpersonators(this); + } +} + +class ElvishImpersonatorsEffect extends OneShotEffect { + + public ElvishImpersonatorsEffect() { + super(Outcome.Neutral); + staticText = "roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result"; + } + + public ElvishImpersonatorsEffect(final ElvishImpersonatorsEffect effect) { + super(effect); + } + + @Override + public ElvishImpersonatorsEffect copy() { + return new ElvishImpersonatorsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int firstRoll = controller.rollDice(game, 6); + int secondRoll = controller.rollDice(game, 6); + game.addEffect(new SetPowerToughnessSourceEffect(firstRoll, secondRoll, Duration.WhileOnBattlefield, SubLayer.SetPT_7b), source); + return true; + } + return false; + } +} From aecfc347bbb7839a80b0f87ed7ba7243ac1e40a0 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:26:56 +0000 Subject: [PATCH 63/65] Implemented Once More With Feeling --- .../src/mage/cards/o/OnceMoreWithFeeling.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java diff --git a/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java b/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java new file mode 100644 index 00000000000..915d5840885 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.o; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardAllEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.SetPlayerLifeAllEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class OnceMoreWithFeeling extends CardImpl { + + public OnceMoreWithFeeling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{W}{W}{W}"); + + // Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library, then draws seven cards. Each player's life total becomes 10. Exile Once More with Feeling. + this.getSpellAbility().addEffect(new OnceMoreWithFeelingEffect()); + Effect effect = new DrawCardAllEffect(7); + effect.setText(", then draws seven cards"); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new SetPlayerLifeAllEffect(10)); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + + // DCI ruling — A deck can have only one card named Once More with Feeling. + // (according to rule 112.6m, this shouldn't do anything) + this.getSpellAbility().addEffect(new InfoEffect("
DCI ruling — A deck can have only one card named {this}")); + } + + public OnceMoreWithFeeling(final OnceMoreWithFeeling card) { + super(card); + } + + @Override + public OnceMoreWithFeeling copy() { + return new OnceMoreWithFeeling(this); + } +} + +class OnceMoreWithFeelingEffect extends OneShotEffect { + + public OnceMoreWithFeelingEffect() { + super(Outcome.Detriment); + staticText = "Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library"; + } + + public OnceMoreWithFeelingEffect(final OnceMoreWithFeelingEffect effect) { + super(effect); + } + + @Override + public OnceMoreWithFeelingEffect copy() { + return new OnceMoreWithFeelingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { + permanent.moveToExile(null, "", source.getSourceId(), game); + } + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + for (UUID cid : player.getGraveyard().copy()) { + Card c = game.getCard(cid); + if (c != null) { + c.moveToExile(null, null, source.getSourceId(), game); + } + } + player.moveCards(player.getHand(), Zone.LIBRARY, source, game); + player.shuffleLibrary(source, game); + } + } + return true; + } +} From 8a432934262f699ccfdb1050cb31ee6aa12c9e07 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:27:50 +0000 Subject: [PATCH 64/65] Implemented Elvish Impersonators & Once More With Feeling --- Mage.Sets/src/mage/sets/Unglued.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index 4518e0b8e78..48aa475c3b4 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -23,6 +23,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Chicken Egg", 41, Rarity.COMMON, mage.cards.c.ChickenEgg.class)); cards.add(new SetCardInfo("Chicken a la King", 17, Rarity.RARE, mage.cards.c.ChickenALaKing.class)); + cards.add(new SetCardInfo("Elvish Impersonators", 56, Rarity.COMMON, mage.cards.e.ElvishImpersonators.class)); cards.add(new SetCardInfo("Forest", 88, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Fowl Play", 24, Rarity.COMMON, mage.cards.f.FowlPlay.class)); cards.add(new SetCardInfo("Goblin Tutor", 45, Rarity.UNCOMMON, mage.cards.g.GoblinTutor.class)); @@ -35,6 +36,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class)); cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class)); cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Once More With Feeling", 11, Rarity.RARE, mage.cards.o.OnceMoreWithFeeling.class)); cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Poultrygeist", 37, Rarity.COMMON, mage.cards.p.Poultrygeist.class)); From 7885fe42a05c9b635cfbf86781afa9e943899aa9 Mon Sep 17 00:00:00 2001 From: Christiaan Date: Tue, 3 Apr 2018 16:31:42 +0200 Subject: [PATCH 65/65] Fixed subtype of Elvish Impersonator --- Mage.Sets/src/mage/cards/e/ElvishImpersonators.java | 2 +- Mage/src/main/java/mage/constants/SubType.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java index 6fa6244ad58..a07b413373e 100644 --- a/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java +++ b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java @@ -51,7 +51,7 @@ public class ElvishImpersonators extends CardImpl { public ElvishImpersonators(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); - this.subtype.add(SubType.ELF); + this.subtype.add(SubType.ELVES); this.power = new MageInt(0); this.toughness = new MageInt(0); diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 5f716c44b7b..42c07015a36 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -123,6 +123,7 @@ public enum SubType { ELEMENTAL("Elemental", SubTypeSet.CreatureType), ELEPHANT("Elephant", SubTypeSet.CreatureType), ELF("Elf", SubTypeSet.CreatureType), + ELVES("Elves", SubTypeSet.CreatureType), ELK("Elk", SubTypeSet.CreatureType), EYE("Eye", SubTypeSet.CreatureType), EWOK("Ewok", SubTypeSet.CreatureType, true), // Star Wars