diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 00000000000..7994d6c1c06 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,44 @@ +on: + push: + branches: + - 'master' + +concurrency: + group: "release" + cancel-in-progress: true + +jobs: + example-docker-compose: + runs-on: node-debian + container: + image: maven:3-eclipse-temurin-11 + steps: + - name: Install prerequisites + run: | + apt-get update + apt-get -y install git nodejs + + - uses: actions/checkout@v3 + + - name: Build Mage + run: | + mvn -T 12 clean install -DskipTests + + - name: Build Client + run: | + cd Mage.Client && mvn package assembly:single + + - name: Build Server + run: | + cd Mage.Server && mvn package assembly:single + + - uses: forgejo/upload-artifact@v4 + with: + name: client.zip + path: ./Mage.Client/target/mage-client.zip + + - uses: forgejo/upload-artifact@v4 + with: + name: server.zip + path: ./Mage.Server/target/mage-server.zip + diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 189b112f4b0..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'weekly' - - package-ecosystem: 'maven' - directory: '/' - schedule: - interval: 'weekly' diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 9c214fa0542..00000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,23 +0,0 @@ -dev: - - changed-files: - - any-glob-to-any-file: [ '*', 'Utils/**', '/.github/**' ] - -engine: - - changed-files: - - any-glob-to-any-file: [ 'Mage/**' ] - -client: - - changed-files: - - any-glob-to-any-file: [ 'Mage.Client/**', 'Mage.Common/**', 'Mage.Plugins/**' ] - -server: - - changed-files: - - any-glob-to-any-file: [ 'Mage.Server*/**' ] - -tests: - - changed-files: - - any-glob-to-any-file: [ 'Mage.Verify/**', 'Mage.Tests/**', 'Mage.Reports/**' ] - -cards: - - changed-files: - - any-glob-to-any-file: [ 'Mage.Sets/**' ] \ No newline at end of file diff --git a/.github/workflows/labeler-auto.yml b/.github/workflows/labeler-auto.yml deleted file mode 100644 index 2f88b117f98..00000000000 --- a/.github/workflows/labeler-auto.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: "Pull Request Labeler (auto)" -on: - - pull_request_target - -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - id: label-the-PR - uses: actions/labeler@v5 - with: - configuration-path: '.github/labeler.yml' \ No newline at end of file diff --git a/.github/workflows/labeler-manual.yml b/.github/workflows/labeler-manual.yml deleted file mode 100644 index 846436ad507..00000000000 --- a/.github/workflows/labeler-manual.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Pull Request Labeler (manual)" -on: - workflow_dispatch: - inputs: - oldPRs: - # no multi lines support, so call by single PR only - description: 'PR number to process' - required: true - type: string - default: '123' - -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - id: label-the-PR - uses: actions/labeler@v5 - with: - configuration-path: '.github/labeler.yml' - pr-number: | - ${{ github.event.inputs.oldPRs }} \ No newline at end of file diff --git a/.github/workflows/mtg-fetch-cards.yml b/.github/workflows/mtg-fetch-cards.yml deleted file mode 100644 index 71fa97a1976..00000000000 --- a/.github/workflows/mtg-fetch-cards.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Mtg Card Fetch Bot - -on: - issue_comment: - types: [created] - issues: - types: [opened] - pull_request_review: - types: [submitted] - pull_request_review_comment: - types: [created] - -jobs: - fetch-card-references: - name: Fetch MTG Card - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: ldeluigi/mtg-fetch-action@v1 diff --git a/.gitignore b/.gitignore index d35b250f77b..7ecdf2fd4b8 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ Utils/*implemented.txt # build tools mage-bundle.zip .env +.classpath diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..013572a57bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM eclipse-temurin:11 + +# Set XMage config defaults +ENV LANG=C.UTF-8 \ + XMAGE_DOCKER_SERVER_ADDRESS="0.0.0.0" \ + XMAGE_DOCKER_PORT="17171" \ + XMAGE_DOCKER_SEONDARY_BIND_PORT="17179" \ + XMAGE_DOCKER_MAX_SECONDS_IDLE="600" \ + XMAGE_DOCKER_AUTHENTICATION_ACTIVATED="false" \ + XMAGE_DOCKER_SERVER_NAME="mage-server" \ + XMAGE_DOCKER_MAILGUN_API_KEY="" \ + XMAGE_DOCKER_MAILGUN_DOMAIN="" \ + XMAGE_DOCKER_MAIL_SMTP_HOST="" \ + XMAGE_DOCKER_MAIL_SMTP_PORT="" \ + XMAGE_DOCKER_MAIL_USER="" \ + XMAGE_DOCKER_MAIL_PASSWORD="" \ + XMAGE_DOCKER_MAIL_FROM_ADDRESS="" \ + XMAGE_DOCKER_MAX_GAME_THREADS="10" \ + XMAGE_DOCKER_MAX_AI_OPPONENTS="15" \ + XMAGE_DOCKER_JAVA_OPTS="-Xmx1024m -XX:MaxPermSize=384m -Dlog4j.configuration=file:./config/log4j.properties" +# Install dependencies +RUN set -ex && \ + apt update && \ + apt install -y curl ca-certificates bash jq unzip + +# Download latest xmage +WORKDIR /xmage + +RUN < { - showWhatsNewDialog(false); - }); } /** diff --git a/Mage.Client/src/main/java/mage/client/components/tray/MageTray.java b/Mage.Client/src/main/java/mage/client/components/tray/MageTray.java index 4bd3166cbe9..56d38168f74 100644 --- a/Mage.Client/src/main/java/mage/client/components/tray/MageTray.java +++ b/Mage.Client/src/main/java/mage/client/components/tray/MageTray.java @@ -19,7 +19,7 @@ public enum MageTray { private Image flashedImage; private TrayIcon trayIcon; - private int state = 0; + private int state = 3; public void install() { if (!SystemTray.isSupported()) { diff --git a/Mage.Client/src/main/java/mage/client/constants/Constants.java b/Mage.Client/src/main/java/mage/client/constants/Constants.java index 8b051f6c985..509a4a22ce8 100644 --- a/Mage.Client/src/main/java/mage/client/constants/Constants.java +++ b/Mage.Client/src/main/java/mage/client/constants/Constants.java @@ -2,9 +2,15 @@ package mage.client.constants; import javax.swing.*; import javax.swing.border.Border; + +import com.google.common.collect.ImmutableList; + +import javafx.util.Pair; + import java.awt.*; import java.io.File; + /** * @author BetaSteward_at_googlemail.com */ @@ -14,6 +20,36 @@ public final class Constants { throw new AssertionError(); } + public static final ImmutableList> foulMagicsSets = ImmutableList.of( + new Pair("Set 2 - Phyrexians, Eldrazi, Asians - Oh my!", new String[] { + "* March of the Machine Block", + "* Phyrexia: All Will Be One Block", + "* The Brothers' War Block", + "* Dominaria United Block", + "* Kamigawa: Neon Dynasty Block", + "* Theros Beyond Death Block", + "* Strixhaven: School of Mages Block", + "* The Lost Caverns of Ixalan Block", + "* Ikoria: Lair of Behemoths Block", + "* Adventures in the Forgotten Realms Block", + "Modern Horizons 3", + "The Lord of the Rings: Tales of Middle-earth", + "Double Masters 2022", + "Rise of the Eldrazi", + "Modern Horizons 2" + }), + new Pair("Set 1 - In Da Beegeening", new String[] { + "* Foundations Block", + "* Guilds of Ravnica Block", + "* Return to Ravnica Block", + }), + new Pair("Set 0.5 - Foundationally Gaming", new String[] { + "* Foundations Block", + }) + ); + + + public static final int FRAME_MAX_HEIGHT = 367; public static final int FRAME_MAX_WIDTH = 256; public static final int ART_MAX_HEIGHT = 168; @@ -144,5 +180,6 @@ public final class Constants { } } + } diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index d445663c0c5..d60bede3765 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -135,7 +135,7 @@ public class DeckGeneratorDialog { c.ipadx = 30; c.insets = new Insets(5, 10, 0, 10); c.weightx = 0.90; - cbDeckSize = new JComboBox<>(new String[]{"40", "60"}); + cbDeckSize = new JComboBox<>(new String[]{"40", "60", "100"}); cbDeckSize.setSelectedIndex(0); cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT); mainPanel.add(cbDeckSize, c); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form index 88895a22e1b..18a8b90bda1 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form @@ -222,6 +222,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index cc2c4cc9657..b2c7bb741b3 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -9,6 +9,7 @@ import mage.cards.decks.PennyDreadfulLegalityUtil; import mage.cards.repository.*; import mage.client.MageFrame; import mage.client.cards.*; +import mage.client.constants.Constants; import mage.client.constants.Constants.SortBy; import mage.client.dialog.PreferencesDialog; import mage.client.deckeditor.table.TableModel; @@ -31,6 +32,8 @@ import mage.view.CardsView; import org.apache.log4j.Logger; import org.mage.card.arcane.ManaSymbolsCellRenderer; +import javafx.util.Pair; + import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; @@ -237,26 +240,44 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene if (limited) { List> predicates = new ArrayList<>(); + List> exclusion = new ArrayList<>(); if (this.tbGreen.isSelected()) { predicates.add(new ColorPredicate(ObjectColor.GREEN)); + } else { + exclusion.add(new ColorPredicate(ObjectColor.GREEN)); } if (this.tbRed.isSelected()) { predicates.add(new ColorPredicate(ObjectColor.RED)); + } else { + exclusion.add(new ColorPredicate(ObjectColor.RED)); } if (this.tbBlack.isSelected()) { predicates.add(new ColorPredicate(ObjectColor.BLACK)); + } else { + exclusion.add(new ColorPredicate(ObjectColor.BLACK)); } if (this.tbBlue.isSelected()) { predicates.add(new ColorPredicate(ObjectColor.BLUE)); + } else { + exclusion.add(new ColorPredicate(ObjectColor.BLUE)); } if (this.tbWhite.isSelected()) { predicates.add(new ColorPredicate(ObjectColor.WHITE)); + } else { + exclusion.add(new ColorPredicate(ObjectColor.WHITE)); } + if (this.tbColorless.isSelected()) { predicates.add(ColorlessPredicate.instance); + } else { + exclusion.add(ColorlessPredicate.instance); + } + if (this.tbLimitColors.isSelected()) { + filter.add(Predicates.and(Predicates.not(Predicates.or(exclusion)), Predicates.or(predicates))); + } else { + filter.add(Predicates.or(predicates)); } - filter.add(Predicates.or(predicates)); predicates.clear(); if (this.tbLand.isSelected()) { @@ -346,6 +367,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene criteria.red(this.tbRed.isSelected()); criteria.white(this.tbWhite.isSelected()); criteria.colorless(this.tbColorless.isSelected()); + criteria.limitColors(this.tbLimitColors.isSelected()); // if you add new type filter then sync it with CardType if (this.tbLand.isSelected()) { @@ -559,9 +581,12 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene tbBlack = new javax.swing.JToggleButton(); tbWhite = new javax.swing.JToggleButton(); tbColorless = new javax.swing.JToggleButton(); + tbLimitColors = new javax.swing.JToggleButton(); jSeparator1 = new javax.swing.JToolBar.Separator(); cbExpansionSet = new javax.swing.JComboBox<>(); btnExpansionSearch = new javax.swing.JButton(); + cbFoulMagicPresets = new javax.swing.JComboBox<>(); + btnFoulMagicPreset = new javax.swing.JButton(); jSeparator2 = new javax.swing.JToolBar.Separator(); chkPennyDreadful = new javax.swing.JCheckBox(); btnBooster = new javax.swing.JButton(); @@ -701,6 +726,23 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } }); tbColor.add(tbColorless); + + tbLimitColors.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/lock.png"))); // NOI18N + tbLimitColors.setSelected(false); + tbLimitColors.setToolTipText("Limit results to ONLY these colors"); + tbLimitColors.setActionCommand("LimitColors"); + tbLimitColors.setFocusable(false); + tbLimitColors.setPreferredSize(new java.awt.Dimension(28, 28)); + tbLimitColors.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbLimitColors.setSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/lock.png"))); // NOI18N + tbLimitColors.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbLimitColors.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbLimitColorsActionPerformed(evt); + } + }); + tbColor.add(tbLimitColors); + tbColor.add(jSeparator1); reloadSetsCombobox(); @@ -741,6 +783,31 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } }); tbColor.add(btnExpansionSearch); + + List setNames = new LinkedList(); + for (Pair pair : Constants.foulMagicsSets) { + setNames.add(pair.getKey()); + } + + DefaultComboBoxModel presetModel = new DefaultComboBoxModel<>(setNames.toArray()); + cbFoulMagicPresets.setModel(presetModel); + + tbColor.add(cbFoulMagicPresets); + + btnFoulMagicPreset.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/brick.png"))); // NOI18N + btnFoulMagicPreset.setToolTipText("Set to Foul Magic preset"); + btnFoulMagicPreset.setAlignmentX(1.0F); + btnFoulMagicPreset.setFocusable(false); + btnFoulMagicPreset.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnFoulMagicPreset.setPreferredSize(new java.awt.Dimension(24, 24)); + btnFoulMagicPreset.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnFoulMagicPreset.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFoulMagicPresetSet(evt); + } + }); + tbColor.add(btnFoulMagicPreset); + tbColor.add(jSeparator2); chkPennyDreadful.setText("Penny Dreadful Only"); @@ -1421,7 +1488,11 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private void tbColorlessActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbColorlessActionPerformed filterCardsColor(evt.getModifiers(), evt.getActionCommand()); }//GEN-LAST:event_tbColorlessActionPerformed - + + private void tbLimitColorsActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbColorlessActionPerformed + filterCards(); + } + private void tbCreaturesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbCreaturesActionPerformed filterCardsType(evt.getModifiers(), evt.getActionCommand()); }//GEN-LAST:event_tbCreaturesActionPerformed @@ -1465,6 +1536,33 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private void chkUniqueActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkRulesActionPerformed // TODO add your handling code here: }//GEN-LAST:event_chkRulesActionPerformed + + private void btnFoulMagicPresetSet(java.awt.event.ActionEvent evt) { + reloadSetsCombobox(); + if (cbExpansionSet.getItemAt(0).startsWith(MULTI_SETS_SELECTION_TEXT)) { + cbExpansionSet.removeItemAt(0); + } + + listCodeSelected.uncheckAll(); + String[] selectedFormats = Constants.foulMagicsSets.get(this.cbFoulMagicPresets.getSelectedIndex()).getValue(); + if (selectedFormats.length == 1) { + this.cbExpansionSet.setSelectedItem(selectedFormats[0]); + filterCards(); + return; + } + + List formats = ConstructedFormats.getTypes(false); + for (int i = 0; i < formats.size(); i++) { + if (Arrays.stream(selectedFormats).anyMatch(formats.get(i)::equals)) { + listCodeSelected.setChecked(i - 1, true); + } + } + + String message = String.format("%s: %s", MULTI_SETS_SELECTION_TEXT, "[Foul Magics]"); + cbExpansionSet.insertItemAt(message, 0); + cbExpansionSet.setSelectedIndex(0); + filterCards(); + } private void btnExpansionSearchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExpansionSearchActionPerformed // search and check multiple items @@ -1505,6 +1603,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene isSetsFilterLoading = false; } + // update data filterCards(); }); @@ -1575,12 +1674,14 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JButton btnBooster; private javax.swing.JButton btnClear; private javax.swing.JButton btnExpansionSearch; + private javax.swing.JButton btnFoulMagicPreset; private javax.swing.JLabel cardCount; private javax.swing.JLabel cardCountLabel; private javax.swing.JPanel cardSelectorBottomPanel; private javax.swing.JScrollPane cardSelectorScrollPane; private javax.swing.JComboBox cbExpansionSet; private javax.swing.JComboBox cbSortBy; + private javax.swing.JComboBox cbFoulMagicPresets; private javax.swing.JCheckBox chkNames; private javax.swing.JCheckBox chkPennyDreadful; private javax.swing.JCheckBox chkPiles; @@ -1607,6 +1708,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToggleButton tbBlue; private javax.swing.JToolBar tbColor; private javax.swing.JToggleButton tbColorless; + private javax.swing.JToggleButton tbLimitColors; private javax.swing.JToggleButton tbCommon; private javax.swing.JToggleButton tbCreatures; private javax.swing.JToggleButton tbEnchantments; 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 3d10a77241e..6ea0279ddf2 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -329,6 +329,7 @@ public class ConnectDialog extends MageDialog { }); btnFindBeta.setText("BETA"); + btnFindBeta.setEnabled(false); btnFindBeta.setToolTipText("Connect to BETA server, AI disabled (use any username without registration)"); btnFindBeta.setAlignmentY(0.0F); btnFindBeta.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); diff --git a/Mage.Client/src/main/java/mage/client/dialog/DownloadImagesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/DownloadImagesDialog.java index d0ca1c614bc..c2b8c07fb38 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/DownloadImagesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/DownloadImagesDialog.java @@ -222,6 +222,7 @@ public class DownloadImagesDialog extends MageDialog { comboSets = new javax.swing.JComboBox<>(); fillerMode1 = new javax.swing.Box.Filler(new java.awt.Dimension(5, 0), new java.awt.Dimension(5, 0), new java.awt.Dimension(5, 32767)); buttonSearchSet = new javax.swing.JButton(); + panelRedownload = new javax.swing.JPanel(); checkboxRedownload = new javax.swing.JCheckBox(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 5), new java.awt.Dimension(0, 3), new java.awt.Dimension(32767, 5)); @@ -354,8 +355,9 @@ public class DownloadImagesDialog extends MageDialog { buttonSearchSetActionPerformed(evt); } }); + panelModeSelect.add(buttonSearchSet); - + panelModeInner.add(panelModeSelect); panelMode.add(panelModeInner); @@ -426,7 +428,7 @@ public class DownloadImagesDialog extends MageDialog { private void buttonSearchSetActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSearchSetActionPerformed FastSearchUtil.showFastSearchForStringComboBox(comboSets, FastSearchUtil.DEFAULT_EXPANSION_SEARCH_MESSAGE, 400, 500); }//GEN-LAST:event_buttonSearchSetActionPerformed - + private void buttonStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStopActionPerformed // TODO implement stop feature for cancel button }//GEN-LAST:event_buttonStopActionPerformed diff --git a/Mage.Client/src/main/resources/buttons/brick.png b/Mage.Client/src/main/resources/buttons/brick.png new file mode 100644 index 00000000000..0b250c0b7b3 Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/brick.png differ diff --git a/Mage.Client/src/main/resources/buttons/lock.png b/Mage.Client/src/main/resources/buttons/lock.png new file mode 100644 index 00000000000..ab5fdd91bab Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/lock.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_128.png b/Mage.Client/src/main/resources/buttons/search_128.png index 574748a87a0..04779d90a17 100644 Binary files a/Mage.Client/src/main/resources/buttons/search_128.png and b/Mage.Client/src/main/resources/buttons/search_128.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_24.png b/Mage.Client/src/main/resources/buttons/search_24.png index a3cd50658eb..cd59d5f3c46 100644 Binary files a/Mage.Client/src/main/resources/buttons/search_24.png and b/Mage.Client/src/main/resources/buttons/search_24.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_32.png b/Mage.Client/src/main/resources/buttons/search_32.png index d78217ac92f..502f3692488 100644 Binary files a/Mage.Client/src/main/resources/buttons/search_32.png and b/Mage.Client/src/main/resources/buttons/search_32.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_64.png b/Mage.Client/src/main/resources/buttons/search_64.png index d2b766cfe12..0fe192f7a63 100644 Binary files a/Mage.Client/src/main/resources/buttons/search_64.png and b/Mage.Client/src/main/resources/buttons/search_64.png differ diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index 3f1eba60fb0..7c5e6401d7c 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -47,7 +47,7 @@ socketWriteTimeout="10000" maxGameThreads="10" maxSecondsIdle="300" - minUserNameLength="3" + minUserNameLength="1" maxUserNameLength="14" invalidUserNamePattern="[^a-z0-9_]" minPasswordLength="8" @@ -63,6 +63,8 @@ mailUser="" mailPassword="" mailFromAddress="" + httpAuth="false" + authUrl="" /> diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config-example/config.xml similarity index 99% rename from Mage.Server/release/config/config.xml rename to Mage.Server/release/config-example/config.xml index 1f77ab3d9b7..e112b43486b 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config-example/config.xml @@ -59,6 +59,8 @@ mailUser="" mailPassword="" mailFromAddress="" + httpAuth="false" + authUrl="" /> diff --git a/Mage.Server/release/config/init.example.txt b/Mage.Server/release/config-example/init.example.txt similarity index 100% rename from Mage.Server/release/config/init.example.txt rename to Mage.Server/release/config-example/init.example.txt diff --git a/Mage.Server/release/config/log4j.properties b/Mage.Server/release/config-example/log4j.properties similarity index 97% rename from Mage.Server/release/config/log4j.properties rename to Mage.Server/release/config-example/log4j.properties index 5ea19fd97fb..4b600b70d28 100644 --- a/Mage.Server/release/config/log4j.properties +++ b/Mage.Server/release/config-example/log4j.properties @@ -1,36 +1,36 @@ -#SAMPLE SERVER CONFIG (you must enable it by command line) - -#default log level and active appenders (dest for logs) -log4j.rootLogger=info, console, logfile - -#custom log level for java classes -log4j.logger.com.j256.ormlite=warn -#log4j.logger.mage.player.ai=warn - -#console log -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n -log4j.appender.console.Threshold=info - -#file log - without rolling -log4j.appender.logfile=org.apache.log4j.FileAppender -log4j.appender.logfile.layout=org.apache.log4j.PatternLayout -log4j.appender.logfile.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n -log4j.appender.logfile.File=mageserver.log - -#file log - rolling by index -log4j.appender.logfileByIndex=org.apache.log4j.RollingFileAppender -log4j.appender.logfileByIndex.layout=org.apache.log4j.PatternLayout -log4j.appender.logfileByIndex.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n -log4j.appender.logfileByIndex.File=mageserver.log -log4j.appender.logfileByIndex.MaxFileSize=10MB -log4j.appender.logfileByIndex.MaxBackupIndex=5 -log4j.appender.logfileByIndex.append=true - -#file log - rolling by dayly -log4j.appender.logfileByDayly=org.apache.log4j.DailyRollingFileAppender -log4j.appender.logfileByDayly.layout=org.apache.log4j.PatternLayout -log4j.appender.logfileByDayly.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n -log4j.appender.logfileByDayly.File=mageserver.log +#SAMPLE SERVER CONFIG (you must enable it by command line) + +#default log level and active appenders (dest for logs) +log4j.rootLogger=info, console, logfile + +#custom log level for java classes +log4j.logger.com.j256.ormlite=warn +#log4j.logger.mage.player.ai=warn + +#console log +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n +log4j.appender.console.Threshold=info + +#file log - without rolling +log4j.appender.logfile=org.apache.log4j.FileAppender +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n +log4j.appender.logfile.File=mageserver.log + +#file log - rolling by index +log4j.appender.logfileByIndex=org.apache.log4j.RollingFileAppender +log4j.appender.logfileByIndex.layout=org.apache.log4j.PatternLayout +log4j.appender.logfileByIndex.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n +log4j.appender.logfileByIndex.File=mageserver.log +log4j.appender.logfileByIndex.MaxFileSize=10MB +log4j.appender.logfileByIndex.MaxBackupIndex=5 +log4j.appender.logfileByIndex.append=true + +#file log - rolling by dayly +log4j.appender.logfileByDayly=org.apache.log4j.DailyRollingFileAppender +log4j.appender.logfileByDayly.layout=org.apache.log4j.PatternLayout +log4j.appender.logfileByDayly.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M %n +log4j.appender.logfileByDayly.File=mageserver.log log4j.appender.logfileByDayly.DatePattern='.'yyyy-MM-dd \ No newline at end of file diff --git a/Mage.Server/release/config/readme.txt b/Mage.Server/release/config-example/readme.txt similarity index 100% rename from Mage.Server/release/config/readme.txt rename to Mage.Server/release/config-example/readme.txt diff --git a/Mage.Server/release/config/security.policy b/Mage.Server/release/config-example/security.policy similarity index 96% rename from Mage.Server/release/config/security.policy rename to Mage.Server/release/config-example/security.policy index dda47ba9183..5d74bde76d8 100644 --- a/Mage.Server/release/config/security.policy +++ b/Mage.Server/release/config-example/security.policy @@ -1,3 +1,3 @@ -grant { - permission java.security.AllPermission; +grant { + permission java.security.AllPermission; }; \ No newline at end of file diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUser.java b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java index 43e0573b5bb..6f1aa97458d 100644 --- a/Mage.Server/src/main/java/mage/server/AuthorizedUser.java +++ b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java @@ -64,6 +64,7 @@ public class AuthorizedUser { public boolean doCredentialsMatch(String name, String password) { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(this.hashAlgorithm); matcher.setHashIterations(this.hashIterations); + AuthenticationToken token = new UsernamePasswordToken(name, password); AuthenticationInfo info = new SimpleAuthenticationInfo(this.name, ByteSource.Util.bytes(Base64.decode(this.password)), diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index 9503aacb383..7b15d08a0d6 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -368,9 +368,10 @@ public final class Main { if (throwable instanceof ClientDisconnectedException) { // client called a disconnect command (full disconnect without tables keep) // no need to keep session + // the above i think is a lie logger.info("CLIENT DISCONNECTED - " + sessionInfo); logger.debug("- cause: client called disconnect command"); - managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.DisconnectedByUser, true); + managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } else if (throwable == null) { // lease timeout (ping), so server lost connection with a client // must keep tables diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index a7dde925fdf..ac6d5b8accc 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -18,6 +18,17 @@ import org.jboss.remoting.callback.Callback; import org.jboss.remoting.callback.HandleCallbackException; import org.jboss.remoting.callback.InvokerCallbackHandler; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -152,8 +163,8 @@ public class Session { if (userName.length() > config.getMaxUserNameLength()) { return "User name may not be longer than " + config.getMaxUserNameLength() + " characters"; } - if (userName.length() <= 3) { - return "User name is too short (3 characters or fewer)"; + if (userName.length() <= 1) { + return "User name is too short (1 characters or fewer)"; } if (userName.length() >= 500) { return "User name is too long (500 characters or more)"; @@ -242,6 +253,7 @@ public class Session { // find auth user AuthorizedUser authorizedUser = null; + if (managerFactory.configSettings().isAuthenticationActivated()) { authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName); String errorMsg = "Wrong username or password. You must register your account first."; @@ -267,6 +279,51 @@ public class Session { } } } + + if (managerFactory.configSettings().isHttpAuth()) { + try { + JsonObject body = new JsonObject(); + body.addProperty("token", password); + body.addProperty("username", userName); + + String json = body.toString(); + + URL url = new URL(managerFactory.configSettings().getAuthUrl()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Content-Length", Integer.toString(json.length())); + conn.setRequestProperty("User-Agent", "Tainted-Mage/1.0"); + + conn.setDoOutput(true); + + + OutputStream os = conn.getOutputStream(); + os.write(json.getBytes()); + os.flush(); + os.close(); + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String resp = in.readLine(); + in.close(); + JsonElement response = JsonParser.parseString(resp); + if (response.isJsonObject() && response.getAsJsonObject().has("success") && response.getAsJsonObject().get("success").getAsBoolean()) { + // s'all good, man + } else { + return "Failed to authenticate"; + } + } else { + return "Failed to authenticate: " + Integer.toString(responseCode); + } + } catch (Exception e) { + return "Error with external authentication. Please try again later."; + } + } // create new user instance (auth or anon) boolean isReconnection = false; diff --git a/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java b/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java index be6d6c6c23d..17e8af29697 100644 --- a/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java +++ b/Mage.Server/src/main/java/mage/server/managers/ConfigSettings.java @@ -69,4 +69,8 @@ public interface ConfigSettings { List getDraftCubes(); List getDeckTypes(); + + boolean isHttpAuth(); + + String getAuthUrl(); } diff --git a/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java b/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java index 93137c076b4..8ba9dc9963f 100644 --- a/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java +++ b/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java @@ -44,6 +44,7 @@ public enum UserStatsRepository { TableUtils.createTableIfNotExists(connectionSource, UserStats.class); statsDao = DaoManager.createDao(connectionSource, UserStats.class); + statsDao.executeRaw("PRAGMA journal_mode=WAL;"); } catch (SQLException ex) { Logger.getLogger(UserStatsRepository.class).error("Error creating user_stats repository - ", ex); } diff --git a/Mage.Server/src/main/java/mage/server/util/Config.xsd b/Mage.Server/src/main/java/mage/server/util/Config.xsd index bc0ab04a678..695b01eb9c0 100644 --- a/Mage.Server/src/main/java/mage/server/util/Config.xsd +++ b/Mage.Server/src/main/java/mage/server/util/Config.xsd @@ -19,18 +19,21 @@ - - - - - + + + + + - - - - - + + + + + + + + diff --git a/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java b/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java index 20be1a64256..ecd9055277a 100644 --- a/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java +++ b/Mage.Server/src/main/java/mage/server/util/ConfigWrapper.java @@ -142,5 +142,13 @@ public class ConfigWrapper implements ConfigSettings { public List getDeckTypes() { return config.getDeckTypes().getDeckType(); } + + public boolean isHttpAuth() { + return config.getServer().isHttpAuth(); + } + + public String getAuthUrl() { + return config.getServer().getAuthUrl(); + } } diff --git a/Mage.Server/src/main/xml-resources/jaxb/Config/Config.xsd b/Mage.Server/src/main/xml-resources/jaxb/Config/Config.xsd index ac33721e7cb..07b96737586 100644 --- a/Mage.Server/src/main/xml-resources/jaxb/Config/Config.xsd +++ b/Mage.Server/src/main/xml-resources/jaxb/Config/Config.xsd @@ -44,6 +44,8 @@ + + diff --git a/Mage/src/main/java/mage/cards/repository/CardCriteria.java b/Mage/src/main/java/mage/cards/repository/CardCriteria.java index 286f619846a..4f299d3a7b8 100644 --- a/Mage/src/main/java/mage/cards/repository/CardCriteria.java +++ b/Mage/src/main/java/mage/cards/repository/CardCriteria.java @@ -3,10 +3,13 @@ package mage.cards.repository; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.SelectArg; import com.j256.ormlite.stmt.Where; + +import mage.MageObject; import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.SubType; import mage.constants.SuperType; +import mage.filter.predicate.Predicate; import java.sql.SQLException; import java.util.ArrayList; @@ -39,6 +42,7 @@ public class CardCriteria { private boolean red; private boolean white; private boolean colorless; + private boolean limitColors; private Integer manaValue; private String sortBy; private Long start; @@ -68,6 +72,12 @@ public class CardCriteria { this.minCardNumber = Integer.MIN_VALUE; this.maxCardNumber = Integer.MAX_VALUE; } + + public CardCriteria limitColors(boolean limitColors) { + this.limitColors = limitColors; + return this; + } + public CardCriteria black(boolean black) { this.black = black; @@ -311,35 +321,56 @@ public class CardCriteria { clausesCount++; } + List exclusion = new ArrayList<>(); + int colorClauses = 0; if (black) { where.eq("black", true); colorClauses++; + } else { + exclusion.add("black"); } if (blue) { where.eq("blue", true); colorClauses++; + } else { + exclusion.add("blue"); } if (green) { where.eq("green", true); colorClauses++; + } else { + exclusion.add("green"); } if (red) { where.eq("red", true); colorClauses++; + } else { + exclusion.add("red"); } if (white) { where.eq("white", true); colorClauses++; + } else { + exclusion.add("white"); } if (colorless) { where.eq("black", false).eq("blue", false).eq("green", false).eq("red", false).eq("white", false); where.and(5); colorClauses++; } + if (colorClauses > 0) { where.or(colorClauses); clausesCount++; + + if (this.limitColors) { + for (String color : exclusion) { + where.not(); + where.eq(color, true); + clausesCount++; + } + } } if (minCardNumber != Integer.MIN_VALUE) {