Merge branch 'master' into master

This commit is contained in:
Failure 2025-02-15 16:32:24 -08:00
commit f741fce710
69 changed files with 433 additions and 164 deletions

View file

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

View file

@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'maven'
directory: '/'
schedule:
interval: 'weekly'

23
.github/labeler.yml vendored
View file

@ -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/**' ]

View file

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

View file

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

View file

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

1
.gitignore vendored
View file

@ -61,3 +61,4 @@ Utils/*implemented.txt
# build tools
mage-bundle.zip
.env
.classpath

36
Dockerfile Normal file
View file

@ -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 <<EOF
wget $(curl -Ls -o /dev/null -w %{url_effective} "https://git.cef.icu/Failure/foul-magics/actions/runs/latest" | sed 's/$/\/artifacts\/server.zip/')
unzip server.zip
unzip mage-server.zip
rm server.zip mage-server.zip
EOF
CMD ["./dockerRun.sh"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -90,7 +90,7 @@ import java.util.stream.Collectors;
*/
public class MageFrame extends javax.swing.JFrame implements MageClient {
private static final String TITLE_NAME = "XMage";
private static final String TITLE_NAME = "XMage (Foul Magics)";
private static final Logger LOGGER = Logger.getLogger(MageFrame.class);
private static final String LITE_MODE_ARG = "-lite";
@ -297,14 +297,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
errorDialog.setLocation(100, 100);
desktopPane.add(errorDialog, errorDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
try {
this.whatsNewDialog = new WhatsNewDialog();
} catch (Throwable e) {
// example: JavaFX is not supported on old MacOS with OpenJDK
// https://bugs.openjdk.java.net/browse/JDK-8202132
LOGGER.error("JavaFX is not supported by your system. What's new page will be disabled.", e);
this.whatsNewDialog = null;
}
PING_SENDER_EXECUTOR.scheduleAtFixedRate(SessionHandler::ping, TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS);
@ -385,11 +377,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
setWindowTitle(); // make sure title is actual on startup
});
// run what's new checks (loading in background)
SwingUtilities.invokeLater(() -> {
showWhatsNewDialog(false);
});
}
/**

View file

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

View file

@ -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<Pair<String, String[]>> foulMagicsSets = ImmutableList.of(
new Pair<String, String[]>("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<String, String[]>("Set 1 - In Da Beegeening", new String[] {
"* Foundations Block",
"* Guilds of Ravnica Block",
"* Return to Ravnica Block",
}),
new Pair<String, String[]>("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 {
}
}
}

View file

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

View file

@ -222,6 +222,46 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnExpansionSearchActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JComboBox" name="cbFoulMagicPresets">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="0"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[120, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[120, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[120, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbFoulMagicPresetSelected"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="FoulMagicPreset"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="btnFoulMagicPreset">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/buttons/brick.png"/>
</Property>
<Property name="toolTipText" type="java.lang.String" value="Set to Foul Magic preset"/>
<Property name="alignmentX" type="float" value="1.0"/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[23, 23]"/>
</Property>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnFoulMagicPresetSet"/>
</Events>
</Component>
<Component class="javax.swing.JToolBar$Separator" name="jSeparator2">
</Component>
<Component class="javax.swing.JCheckBox" name="chkPennyDreadful">

View file

@ -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<Predicate<MageObject>> predicates = new ArrayList<>();
List<Predicate<MageObject>> 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<String> setNames = new LinkedList<String>();
for (Pair<String, String[]> 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<String> 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<String> cbExpansionSet;
private javax.swing.JComboBox<SortBy> cbSortBy;
private javax.swing.JComboBox<SortBy> 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;

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

View file

@ -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=""
/>
<playerTypes>
<playerType name="Human" jar="mage-player-human.jar" className="mage.player.human.HumanPlayer"/>

View file

@ -59,6 +59,8 @@
mailUser=""
mailPassword=""
mailFromAddress=""
httpAuth="false"
authUrl=""
/>
<playerTypes>
<playerType name="Human" jar="mage-player-human-${project.version}.jar" className="mage.player.human.HumanPlayer"/>

View file

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

View file

@ -1,3 +1,3 @@
grant {
permission java.security.AllPermission;
grant {
permission java.security.AllPermission;
};

View file

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

View file

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

View file

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

View file

@ -69,4 +69,8 @@ public interface ConfigSettings {
List<Plugin> getDraftCubes();
List<Plugin> getDeckTypes();
boolean isHttpAuth();
String getAuthUrl();
}

View file

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

View file

@ -19,18 +19,21 @@
<xs:attribute name="serverAddress" type="xs:string" use="required"/>
<xs:attribute name="serverName" type="xs:string" use="required"/>
<xs:attribute name="port" type="xs:positiveInteger" use="required"/>
<xs:attribute name="secondaryBindPort" type="xs:integer" use="required"/>
<xs:attribute name="backlogSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="numAcceptThreads" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxPoolSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="leasePeriod" type="xs:positiveInteger" use="required"/>
<xs:attribute name="secondaryBindPort" type="xs:integer" use="required"/>
<xs:attribute name="backlogSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="numAcceptThreads" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxPoolSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="leasePeriod" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxGameThreads" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxSecondsIdle" type="xs:positiveInteger" use="required"/>
<xs:attribute name="minUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="userNamePattern" type="xs:string" use="required"/>
<xs:attribute name="maxAiOpponents" type="xs:string" use="optional"/>
<xs:attribute name="saveGameActivated" type="xs:boolean" use="optional"/>
<xs:attribute name="minUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="userNamePattern" type="xs:string" use="required"/>
<xs:attribute name="maxAiOpponents" type="xs:string" use="optional"/>
<xs:attribute name="saveGameActivated" type="xs:boolean" use="optional"/>
<xs:attribute name="httpAuth" type="xs:boolean" use="optional" />
<xs:attribute name="authUrl" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>

View file

@ -142,5 +142,13 @@ public class ConfigWrapper implements ConfigSettings {
public List<Plugin> getDeckTypes() {
return config.getDeckTypes().getDeckType();
}
public boolean isHttpAuth() {
return config.getServer().isHttpAuth();
}
public String getAuthUrl() {
return config.getServer().getAuthUrl();
}
}

View file

@ -44,6 +44,8 @@
<xs:attribute name="mailUser" type="xs:string" use="optional"/>
<xs:attribute name="mailPassword" type="xs:string" use="optional"/>
<xs:attribute name="mailFromAddress" type="xs:string" use="optional"/>
<xs:attribute name="httpAuth" type="xs:boolean" use="optional" />
<xs:attribute name="authUrl" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>

View file

@ -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<String> 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) {