Merge origin/master

This commit is contained in:
fireshoes 2016-01-24 22:26:07 -06:00
commit 6925bd6d7d
89 changed files with 1584 additions and 683 deletions

1
.gitignore vendored
View file

@ -82,6 +82,7 @@ Mage.Server.Plugins/Mage.Draft.8PlayerBooster/target
*.classpath
*.iml
hs_err*.log
/submitted
/Mage.Server/config/ai.please.cast.this.txt

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<groupId>org.mage</groupId>

View file

@ -1,6 +1,6 @@
XMage.de 1 (Europe/Germany) fast :xmage.de:17171
woogerworks (North America/USA) :xmage.woogerworks.info:17171
XMage.info 1 (Europe/France) new network code -> see forum :176.31.186.181:17171
XMage Testserver (Europe/France) 1.4.8v0 :176.31.186.181:17171
XMage BR (South America/Brazil) :ec2-54-233-67-0.sa-east-1.compute.amazonaws.com:17171
Seedds Server (Asia) :115.29.203.80:17171
localhost -> connect to your local server (must be started):localhost:17171

View file

@ -26,7 +26,7 @@
* or implied, of BetaSteward_at_googlemail.com.
*/
/*
/*
* TableWaitingDialog.java
*
* Created on Dec 16, 2009, 10:27:44 AM
@ -68,7 +68,7 @@ public class TableWaitingDialog extends MageDialog {
private Session session;
private final TableWaitModel tableWaitModel;
private UpdateSeatsTask updateTask;
private static final int[] defaultColumnsWidth = {20, 50, 100, 100};
private static final int[] defaultColumnsWidth = {20, 50, 100, 100, 100};
/**
* Creates new form TableWaitingDialog
@ -268,10 +268,8 @@ public class TableWaitingDialog extends MageDialog {
if (session.startMatch(roomId, tableId)) {
closeDialog();
}
} else {
if (session.startTournament(roomId, tableId)) {
closeDialog();
}
} else if (session.startTournament(roomId, tableId)) {
closeDialog();
}
}//GEN-LAST:event_btnStartActionPerformed
@ -319,7 +317,7 @@ public class TableWaitingDialog extends MageDialog {
class TableWaitModel extends AbstractTableModel {
private final String[] columnNames = new String[]{"Seat", "Loc", "Player Name", "Player Type"};
private final String[] columnNames = new String[]{"Seat", "Loc", "Player Name", "Player Type", "History"};
private SeatView[] seats = new SeatView[0];
public void loadData(TableView table) {
@ -353,6 +351,8 @@ class TableWaitModel extends AbstractTableModel {
return seats[arg0].getPlayerName();
case 3:
return seats[arg0].getPlayerType();
case 4:
return seats[arg0].getHistory();
}
}
return "";

View file

@ -26,7 +26,7 @@
* or implied, of BetaSteward_at_googlemail.com.
*/
/*
/*
* PlayerPanel.java
*
* Created on Nov 18, 2009, 3:01:31 PM
@ -288,7 +288,8 @@ public class PlayerPanelExt extends javax.swing.JPanel {
basicTooltipText = "<HTML>Name: " + player.getName()
+ "<br/>Country: " + countryname
+ "<br/>Deck hash code: " + player.getDeckHashCode()
+ "<br/>Wins: " + player.getWins() + " of " + player.getWinsNeeded() + " (to win the match)";
+ "<br/>This match wins: " + player.getWins() + " of " + player.getWinsNeeded() + " (to win the match)"
+ (player.getUserData() == null ? "" : "<br/>History: " + player.getUserData().getHistory());
}
// Extend tooltip
StringBuilder tooltipText = new StringBuilder(basicTooltipText);

View file

@ -26,7 +26,7 @@
* or implied, of BetaSteward_at_googlemail.com.
*/
/*
/*
* ChatPanel.java
*
* Created on 15-Dec-2009, 11:04:31 PM
@ -61,7 +61,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
private final List<String> players = new ArrayList<>();
private final UserTableModel userTableModel;
private static final int[] defaultColumnsWidth = {20, 100, 100, 100, 80, 80};
private static final int[] DEFAULT_COLUMNS_WIDTH = {20, 100, 100, 80, 80};
/*
@ -78,7 +78,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
jTablePlayers.setForeground(Color.white);
jTablePlayers.setRowSorter(new MageTableRowSorter(userTableModel));
TableUtil.setColumnWidthAndOrder(jTablePlayers, defaultColumnsWidth, KEY_USERS_COLUMNS_WIDTH, KEY_USERS_COLUMNS_ORDER);
TableUtil.setColumnWidthAndOrder(jTablePlayers, DEFAULT_COLUMNS_WIDTH, KEY_USERS_COLUMNS_WIDTH, KEY_USERS_COLUMNS_ORDER);
jTablePlayers.setDefaultRenderer(Icon.class, new CountryCellRenderer());
jScrollPaneTalk.setSystemMessagesPane(colorPaneSystem);
@ -118,7 +118,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
class UserTableModel extends AbstractTableModel {
private final String[] columnNames = new String[]{"Loc", "Players", "History", "Info", "Games", "Connection"};
private final String[] columnNames = new String[]{"Loc", "Players", "History", "Games", "Connection"};
private UsersView[] players = new UsersView[0];
public void loadData(Collection<RoomUsersView> roomUserInfoList) throws MageRemoteException {
@ -128,7 +128,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
TableColumnModel tcm = th.getColumnModel();
tcm.getColumn(jTablePlayers.convertColumnIndexToView(1)).setHeaderValue("Players (" + this.players.length + ")");
tcm.getColumn(jTablePlayers.convertColumnIndexToView(4)).setHeaderValue(
tcm.getColumn(jTablePlayers.convertColumnIndexToView(3)).setHeaderValue(
"Games " + roomUserInfo.getNumberActiveGames()
+ (roomUserInfo.getNumberActiveGames() != roomUserInfo.getNumberGameThreads() ? " (T:" + roomUserInfo.getNumberGameThreads() : " (")
+ " limit: " + roomUserInfo.getNumberMaxGames() + ")");
@ -156,10 +156,8 @@ public class PlayersChatPanel extends javax.swing.JPanel {
case 2:
return players[arg0].getHistory();
case 3:
return players[arg0].getInfoState();
case 4:
return players[arg0].getInfoGames();
case 5:
case 4:
return players[arg0].getInfoPing();
}
return "";

View file

@ -1,12 +1,10 @@
/**
* DownloadJob.java
*
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -21,25 +19,26 @@ import org.mage.plugins.card.dl.beans.properties.Property;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
import org.mage.plugins.card.utils.CardImageUtils;
/**
* The class DownloadJob.
*
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class DownloadJob extends AbstractLaternaBean {
public static enum State {
NEW, WORKING, FINISHED, ABORTED;
}
private final String name;
private final Source source;
private final Destination destination;
private final Property<State> state = properties.property("state", State.NEW);
private final Property<String> message = properties.property("message");
private final Property<Exception> error = properties.property("error");
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
private final String name;
private final Source source;
private final Destination destination;
private final Property<State> state = properties.property("state", State.NEW);
private final Property<String> message = properties.property("message");
private final Property<Exception> error = properties.property("error");
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
public DownloadJob(String name, Source source, Destination destination) {
this.name = name;
@ -48,7 +47,9 @@ public class DownloadJob extends AbstractLaternaBean {
}
/**
* Sets the job's state. If the state is {@link State#ABORTED}, it instead sets the error to "ABORTED"
* Sets the job's state. If the state is {@link State#ABORTED}, it instead
* sets the error to "ABORTED"
*
* @param state
*/
public void setState(State state) {
@ -60,8 +61,9 @@ public class DownloadJob extends AbstractLaternaBean {
}
/**
* Sets the job's state to {@link State#ABORTED} and the error message to the given message. Logs a warning
* with the given message.
* Sets the job's state to {@link State#ABORTED} and the error message to
* the given message. Logs a warning with the given message.
*
* @param message
*/
public void setError(String message) {
@ -69,8 +71,9 @@ public class DownloadJob extends AbstractLaternaBean {
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given exception.
* Sets the job's state to {@link State#ABORTED} and the error to the given
* exception. Logs a warning with the given exception.
*
* @param error
*/
public void setError(Exception error) {
@ -78,14 +81,15 @@ public class DownloadJob extends AbstractLaternaBean {
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given message and exception.
* Sets the job's state to {@link State#ABORTED} and the error to the given
* exception. Logs a warning with the given message and exception.
*
* @param message
* @param error
*/
public void setError(String message, Exception error) {
if (message == null) {
message = "Download of " + this.getName() + "from " + this.getSource().toString() + " caused error: " + error.toString();
}
// log.warn(message, error);
@ -97,6 +101,7 @@ public class DownloadJob extends AbstractLaternaBean {
/**
* Sets the job's message.
*
* @param message
*/
public void setMessage(String message) {
@ -119,7 +124,6 @@ public class DownloadJob extends AbstractLaternaBean {
return message.getValue();
}
public String getName() {
return name;
}
@ -163,9 +167,9 @@ public class DownloadJob extends AbstractLaternaBean {
@Override
public String toString() {
return proxy != null ? proxy.type().toString()+" " :"" + url;
return proxy != null ? proxy.type().toString() + " " : "" + url;
}
};
}
@ -189,11 +193,11 @@ public class DownloadJob extends AbstractLaternaBean {
public int length() throws IOException {
return getConnection().getContentLength();
}
@Override
public String toString() {
return proxy != null ? proxy.type().toString()+" " :"" + url;
}
return proxy != null ? proxy.type().toString() + " " : "" + url;
}
};
}
@ -213,6 +217,14 @@ public class DownloadJob extends AbstractLaternaBean {
return new FileOutputStream(file);
}
@Override
public boolean isValid() throws IOException {
if (file.isFile()) {
return file.length() > 0;
}
return false;
}
@Override
public boolean exists() {
return file.isFile();
@ -228,16 +240,20 @@ public class DownloadJob extends AbstractLaternaBean {
}
public interface Source {
InputStream open() throws IOException;
int length() throws IOException;
}
public interface Destination {
OutputStream open() throws IOException;
boolean exists() throws IOException;
boolean isValid() throws IOException;
void delete() throws IOException;
}
}

View file

@ -1,9 +1,8 @@
/**
* Downloader.java
*
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.BufferedInputStream;
@ -29,10 +28,9 @@ import org.mage.plugins.card.dl.DownloadJob.Source;
import org.mage.plugins.card.dl.DownloadJob.State;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
/**
* The class Downloader.
*
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
@ -40,16 +38,16 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
private static final Logger logger = Logger.getLogger(Downloader.class);
private final List<DownloadJob> jobs = properties.list("jobs");
private final List<DownloadJob> jobs = properties.list("jobs");
private final Channel<DownloadJob> channel = new MemoryChannel<>();
private final ExecutorService pool = Executors.newCachedThreadPool();
private final List<Fiber> fibers = new ArrayList<>();
private final ExecutorService pool = Executors.newCachedThreadPool();
private final List<Fiber> fibers = new ArrayList<>();
public Downloader() {
PoolFiberFactory f = new PoolFiberFactory(pool);
//subscribe multiple fibers for parallel execution
for(int i = 0, numThreads = 10; i < numThreads; i++) {
for (int i = 0, numThreads = 10; i < numThreads; i++) {
Fiber fiber = f.create();
fiber.start();
fibers.add(fiber);
@ -59,15 +57,15 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
@Override
public void dispose() {
for(DownloadJob j:getJobs()) {
switch(j.getState()) {
for (DownloadJob j : getJobs()) {
switch (j.getState()) {
case NEW:
case WORKING:
j.setState(State.ABORTED);
}
}
for(Fiber f:fibers) {
for (Fiber f : fibers) {
f.dispose();
}
pool.shutdown();
@ -84,10 +82,10 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
}
public void add(DownloadJob job) {
if(job.getState() == State.WORKING) {
if (job.getState() == State.WORKING) {
throw new IllegalArgumentException("Job already running");
}
if(job.getState() == State.FINISHED) {
if (job.getState() == State.FINISHED) {
throw new IllegalArgumentException("Job already finished");
}
job.setState(State.NEW);
@ -100,15 +98,17 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
}
/**
* Performs the download job: Transfers data from {@link Source} to {@link Destination} and updates the
* download job's state to reflect the progress.
* Performs the download job: Transfers data from {@link Source} to
* {@link Destination} and updates the download job's state to reflect the
* progress.
*/
private class DownloadCallback implements Callback<DownloadJob> {
@Override
public void onMessage(DownloadJob job) {
//the job won't be processed by multiple threads
synchronized(job) {
if(job.getState() != State.NEW) {
synchronized (job) {
if (job.getState() != State.NEW) {
return;
}
job.setState(State.WORKING);
@ -118,10 +118,17 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
Destination dst = job.getDestination();
BoundedRangeModel progress = job.getProgress();
if(dst.exists()) {
if (dst.isValid()) {
progress.setMaximum(1);
progress.setValue(1);
} else {
if (dst.exists()) {
try {
dst.delete();
} catch (IOException ex1) {
logger.warn("While deleting not valid file", ex1);
}
}
progress.setMaximum(src.length());
InputStream is = new BufferedInputStream(src.open());
try {
@ -129,45 +136,45 @@ public class Downloader extends AbstractLaternaBean implements Disposable {
try {
byte[] buf = new byte[8 * 1024];
int total = 0;
for(int len; (len = is.read(buf)) != -1;) {
if(job.getState() == State.ABORTED) {
for (int len; (len = is.read(buf)) != -1;) {
if (job.getState() == State.ABORTED) {
throw new IOException("Job was aborted");
}
progress.setValue(total += len);
os.write(buf, 0, len);
}
} catch(IOException ex) {
} catch (IOException ex) {
try {
dst.delete();
} catch(IOException ex1) {
} catch (IOException ex1) {
logger.warn("While deleting", ex1);
}
throw ex;
} finally {
try {
os.close();
} catch(IOException ex) {
} catch (IOException ex) {
logger.warn("While closing", ex);
}
}
} finally {
try {
is.close();
} catch(IOException ex) {
} catch (IOException ex) {
logger.warn("While closing", ex);
}
}
}
job.setState(State.FINISHED);
} catch(ConnectException ex) {
} catch (ConnectException ex) {
String message;
if (ex.getMessage() != null) {
message = ex.getMessage();
} else {
message = "Unknown error";
}
logger.warn("Error resource download " + job.getName() +" from "+ job.getSource().toString() + ": " + message);
} catch(IOException ex) {
logger.warn("Error resource download " + job.getName() + " from " + job.getSource().toString() + ": " + message);
} catch (IOException ex) {
job.setError(ex);
}
}

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-common</artifactId>

View file

@ -40,8 +40,8 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
*/
public final static int MAGE_VERSION_MAJOR = 1;
public final static int MAGE_VERSION_MINOR = 4;
public final static int MAGE_VERSION_PATCH = 7;
public final static String MAGE_VERSION_MINOR_PATCH = "v1";
public final static int MAGE_VERSION_PATCH = 8;
public final static String MAGE_VERSION_MINOR_PATCH = "v0";
public final static String MAGE_VERSION_INFO = "";
private final int major;

View file

@ -44,6 +44,7 @@ public class SeatView implements Serializable {
private UUID playerId;
private final String playerName;
private final String playerType;
private final String history;
public SeatView(Seat seat) {
if (seat.getPlayer() != null) {
@ -51,13 +52,16 @@ public class SeatView implements Serializable {
this.playerName = seat.getPlayer().getName();
if (seat.getPlayer().getUserData() == null) {
this.flagName = UserData.getDefaultFlagName();
this.history = "";
} else {
this.flagName = seat.getPlayer().getUserData().getFlagName();
this.history = seat.getPlayer().getUserData().getHistory();
}
} else {
// Empty seat
this.playerName = "";
this.flagName = "";
this.history = "";
}
this.playerType = seat.getPlayerType();
}
@ -78,4 +82,8 @@ public class SeatView implements Serializable {
return flagName;
}
public String getHistory() {
return history;
}
}

View file

@ -24,7 +24,7 @@
* 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.view;
import java.io.Serializable;
@ -40,15 +40,13 @@ public class UsersView implements Serializable {
private final String flagName;
private final String userName;
private final String history;
private final String infoState;
private final String infoGames;
private final String infoPing;
public UsersView(String flagName, String userName, String history, String infoState, String infoGames, String infoPing) {
public UsersView(String flagName, String userName, String history, String infoGames, String infoPing) {
this.flagName = flagName;
this.history = history;
this.userName = userName;
this.infoState = infoState;
this.infoGames = infoGames;
this.infoPing = infoPing;
}
@ -65,10 +63,6 @@ public class UsersView implements Serializable {
return history;
}
public String getInfoState() {
return infoState;
}
public String getInfoGames() {
return infoGames;
}

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-counter-plugin</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-plugins</artifactId>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<groupId>org.mage</groupId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-deck-constructed</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-deck-limited</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-commanderduel</artifactId>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-commanderfreeforall</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-freeforall</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-momirduel</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-tinyleadersduel</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-game-twoplayerduel</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-ai-draftbot</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-ai-ma</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-ai</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-ai-mcts</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-aiminimax</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-player-human</artifactId>

View file

@ -358,15 +358,13 @@ public class HumanPlayer extends PlayerImpl {
}
}
}
} else {
if (target.canTarget(response.getUUID(), game)) {
if (target.getTargets().contains(response.getUUID())) { // if already included remove it with
target.remove(response.getUUID());
} else {
target.addTarget(response.getUUID(), null, game);
if (target.doneChosing()) {
return true;
}
} else if (target.canTarget(response.getUUID(), game)) {
if (target.getTargets().contains(response.getUUID())) { // if already included remove it with
target.remove(response.getUUID());
} else {
target.addTarget(response.getUUID(), null, game);
if (target.doneChosing()) {
return true;
}
}
}
@ -530,12 +528,10 @@ public class HumanPlayer extends PlayerImpl {
if (response.getUUID() != null) {
if (target.getTargets().contains(response.getUUID())) { // if already included remove it
target.remove(response.getUUID());
} else {
if (target.canTarget(response.getUUID(), cards, game)) {
target.addTarget(response.getUUID(), source, game);
if (target.doneChosing()) {
return true;
}
} else if (target.canTarget(response.getUUID(), cards, game)) {
target.addTarget(response.getUUID(), source, game);
if (target.doneChosing()) {
return true;
}
}
} else {
@ -1065,10 +1061,8 @@ public class HumanPlayer extends PlayerImpl {
// does not block yet and can block or can block more attackers
if (filter.match(blocker, null, playerId, game)) {
selectCombatGroup(defendingPlayerId, blocker.getId(), game);
} else {
if (filterBlock.match(blocker, null, playerId, game) && game.getStack().isEmpty()) {
removeBlocker = true;
}
} else if (filterBlock.match(blocker, null, playerId, game) && game.getStack().isEmpty()) {
removeBlocker = true;
}
if (removeBlocker) {
@ -1546,4 +1540,9 @@ public class HumanPlayer extends PlayerImpl {
pass(game);
return true;
}
@Override
public String getHistory() {
return "no available";
}
}

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-tournament-boosterdraft</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-tournament-constructed</artifactId>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-server-plugins</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-tournament-sealed</artifactId>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-server-plugins</artifactId>

View file

@ -19,6 +19,17 @@
userNamePattern - pattern for user name validity check
maxAiOpponents - number of allowed AI opponents on the server
saveGameActivated - allow game save and replay options (not working correctly yet)
authenticationActivated - "true" = user have to register to signon "false" = user need not to register
* mail configs only needed if authentication is activated:
* if mailUser = "" mailgun is used otherwise nativ mail server on the system
googleAccount - not supported currently
mailgunApiKey - key from the mailgun domain e.g. = "key-12121111..."
mailgunDomain - domain for the mailgun message sending
mailSmtpHost - hostname to send the mail
mailSmtpPort - port to send the mail
mailUser - username used to send the mail
mailPassword - passworf of the used user to send the mail
mailFromAddress - sender address
-->
<server serverAddress="0.0.0.0"
serverName="mage-server"
@ -37,10 +48,15 @@
maxPasswordLength="100"
maxAiOpponents="15"
saveGameActivated="false"
authenticationActivated="false"
authenticationActivated="true"
googleAccount=""
mailgunApiKey=""
mailgunDomain=""
mailgunApiKey="key-d93e81f19a9c9ed243ebb7cc9381385c"
mailgunDomain="sandbox401a433f30d445309a5e86b6c53f7812.mailgun.org"
mailSmtpHost="smtp.1und1.de"
mailSmtpPort="465"
mailUser="xmageserver@online.de"
mailPassword="24wrsfxv"
mailFromAddress="xmageserver@online.de"
/>
<playerTypes>
<playerType name="Human" jar="mage-player-human.jar" className="mage.player.human.HumanPlayer"/>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-server</artifactId>

View file

@ -1,6 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../Config.xsd">
<!--
serverAddress - ip of the XMage server. Set it to "0.0.0.0" for local host or to the IP the server should use
port - the port the primary server socket is bound to
secondaryBindPort - the port to which the secondary server socket is to be bound. if "-1" is set , an arbitrary port is selected.
backlogSize - the preferred number of unaccepted incoming connections allowed at a given time. The actual number may be greater
than the specified backlog. When the queue is full, further connection requests are rejected. The JBoss default value is 200
numAcceptThreads - the number of threads listening on the ServerSocket. The JBoss default value is 1
maxPoolSize - the maximum number of ServerThreads that can exist at any given time. The JBoss default value is 300
leasePeriod - To turn on server side connection failure detection of remoting clients, it is necessary to satisfy two criteria.
The first is that the client lease period is set and is a value greater than 0. The value is represented in milliseconds.
The client lease period can be set by either the 'clientLeasePeriod' attribute within the Connector configuration or by calling the Connector method
maxGameThreads - Number of games that can be started simultanously on the server
maxSecondsIdle - Number of seconds after that a game is auto conceded by the player that was idle for such a time
minUserNameLength - minmal allowed length of a user name to connect to the server
maxUserNameLength - maximal allowed length of a user name to connect to the server
userNamePattern - pattern for user name validity check
maxAiOpponents - number of allowed AI opponents on the server
saveGameActivated - allow game save and replay options (not working correctly yet)
authenticationActivated - "true" = user have to register to signon "false" = user need not to register
* mail configs only needed if authentication is activated:
* if mailUser = "" mailgun is used otherwise nativ mail server on the system
googleAccount - not supported currently
mailgunApiKey - key from the mailgun domain e.g. = "key-12121111..."
mailgunDomain - domain for the mailgun message sending
mailSmtpHost - hostname to send the mail
mailSmtpPort - port to send the mail
mailUser - username used to send the mail
mailPassword - passworf of the used user to send the mail
mailFromAddress - sender address
-->
<server serverAddress="0.0.0.0"
serverName="mage-server"
port="17171"
@ -18,6 +48,13 @@
saveGameActivated="false"
authenticationActivated="false"
googleAccount=""
mailgunApiKey=""
mailgunDomain=""
mailSmtpHost=""
mailSmtpPort=""
mailUser=""
mailPassword=""
mailFromAddress=""
/>
<playerTypes>
<playerType name="Human" jar="mage-player-human-${project.version}.jar" className="mage.player.human.HumanPlayer"/>

View file

@ -24,8 +24,7 @@
* 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.server;
import java.util.ArrayList;
@ -44,14 +43,15 @@ import org.apache.log4j.Logger;
public class ChatManager {
private static final Logger logger = Logger.getLogger(ChatManager.class);
private static final ChatManager INSTANCE = new ChatManager();
public static ChatManager getInstance() {
return INSTANCE;
}
private ChatManager() {}
private ChatManager() {
}
private final ConcurrentHashMap<UUID, ChatSession> chatSessions = new ConcurrentHashMap<>();
@ -66,16 +66,16 @@ public class ChatManager {
if (chatSession != null) {
chatSession.join(userId);
} else {
logger.trace("Chat to join not found - chatId: " + chatId +" userId: " + userId);
}
logger.trace("Chat to join not found - chatId: " + chatId + " userId: " + userId);
}
}
public void leaveChat(UUID chatId, UUID userId) {
ChatSession chatSession = chatSessions.get(chatId);
if (chatSession != null && chatSession.hasUser(userId)) {
chatSession.kill(userId, DisconnectReason.CleaningUp);
}
}
}
public void destroyChatSession(UUID chatId) {
@ -88,7 +88,7 @@ public class ChatManager {
logger.trace("Chat removed - chatId: " + chatId);
} else {
logger.trace("Chat to destroy does not exist - chatId: " + chatId);
}
}
}
}
}
@ -119,63 +119,56 @@ public class ChatManager {
}
}
private boolean performUserCommand(User user, String message, UUID chatId) {
String command = message.substring(1).trim().toUpperCase(Locale.ENGLISH);
if (command.equals("I") || command.equals("INFO")) {
user.setInfo("");
chatSessions.get(chatId).broadcastInfoToUser(user,message);
return true;
}
if (command.startsWith("I ") || command.startsWith("INFO ")) {
user.setInfo(message.substring(command.startsWith("I ") ? 3 : 6));
chatSessions.get(chatId).broadcastInfoToUser(user,message);
if (command.startsWith("H ") || command.startsWith("HISTORY ")) {
message = UserManager.getInstance().getUserHistory(message.substring(command.startsWith("H ") ? 3 : 9));
chatSessions.get(chatId).broadcastInfoToUser(user, message);
return true;
}
if (command.startsWith("W ") || command.startsWith("WHISPER ")) {
String rest = message.substring(command.startsWith("W ")? 3 : 9);
String rest = message.substring(command.startsWith("W ") ? 3 : 9);
int first = rest.indexOf(" ");
if (first > 1) {
String userToName = rest.substring(0,first);
String userToName = rest.substring(0, first);
rest = rest.substring(first + 1).trim();
User userTo = UserManager.getInstance().getUserByName(userToName);
if (userTo != null) {
if (!chatSessions.get(chatId).broadcastWhisperToUser(user, userTo, rest)) {
message += new StringBuilder("<br/>User ").append(userToName).append(" not found").toString();
chatSessions.get(chatId).broadcastInfoToUser(user,message);
chatSessions.get(chatId).broadcastInfoToUser(user, message);
}
} else {
message += new StringBuilder("<br/>User ").append(userToName).append(" not found").toString();
chatSessions.get(chatId).broadcastInfoToUser(user,message);
chatSessions.get(chatId).broadcastInfoToUser(user, message);
}
return true;
}
}
if (command.equals("L") || command.equals("LIST")) {
message += new StringBuilder("<br/>List of commands:")
.append("<br/>\\info [text] - set a info text to your player")
.append("<br/>\\list - Show a list of commands")
.append("<br/>\\whisper [player name] [text] - whisper to the player with the given name").toString();
chatSessions.get(chatId).broadcastInfoToUser(user,message);
.append("<br/>\\history or \\h [username] - shows the history of a player")
.append("<br/>\\list or \\l - Show a list of commands")
.append("<br/>\\whisper or \\w [player name] [text] - whisper to the player with the given name").toString();
chatSessions.get(chatId).broadcastInfoToUser(user, message);
return true;
}
return false;
}
/**
*
* use mainly for announcing that a user connection was lost or that a user has reconnected
*
*
* use mainly for announcing that a user connection was lost or that a user
* has reconnected
*
* @param userId
* @param message
* @param color
* @param color
*/
public void broadcast(UUID userId, String message, MessageColor color) {
User user = UserManager.getInstance().getUser(userId);
if (user != null) {
for (ChatSession chat: chatSessions.values()) {
for (ChatSession chat : chatSessions.values()) {
if (chat.hasUser(userId)) {
chat.broadcast(user.getName(), message, color);
}
@ -186,16 +179,16 @@ public class ChatManager {
public void sendReconnectMessage(UUID userId) {
User user = UserManager.getInstance().getUser(userId);
if (user != null) {
for (ChatSession chat: chatSessions.values()) {
for (ChatSession chat : chatSessions.values()) {
if (chat.hasUser(userId)) {
chat.broadcast(null, user.getName() + " has reconnected", MessageColor.BLUE, true, MessageType.STATUS);
}
}
}
}
}
}
public void removeUser(UUID userId, DisconnectReason reason) {
for (ChatSession chatSession: chatSessions.values()) {
for (ChatSession chatSession : chatSessions.values()) {
if (chatSession.hasUser(userId)) {
chatSession.kill(userId, reason);
}

View file

@ -99,7 +99,7 @@ public class MageServerImpl implements MageServer {
private static final Logger logger = Logger.getLogger(MageServerImpl.class);
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
private static final SecureRandom RANDOM = new SecureRandom();
private final String adminPassword;
private final boolean testMode;
private final LinkedHashMap<String, String> activeAuthTokens = new LinkedHashMap<String, String>() {
@ -140,9 +140,16 @@ public class MageServerImpl implements MageServer {
}
String authToken = generateAuthToken();
activeAuthTokens.put(email, authToken);
if (!MailgunClient.sendMessage(email, "XMage Password Reset Auth Token",
"Use this auth token to reset your password: " + authToken + "\n" +
"It's valid until the next server restart.")) {
String subject = "XMage Password Reset Auth Token";
String text = "Use this auth token to reset your password: " + authToken + "\n"
+ "It's valid until the next server restart.";
boolean success;
if (!ConfigSettings.getInstance().getMailUser().isEmpty()) {
success = MailClient.sendMessage(email, subject, text);
} else {
success = MailgunClient.sendMessage(email, subject, text);
}
if (!success) {
sendErrorMessageToClient(sessionId, "There was an error inside the server while emailing an auth token");
return false;
}

View file

@ -0,0 +1,53 @@
package mage.server;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import mage.server.util.ConfigSettings;
import org.apache.log4j.Logger;
public class MailClient {
private static final Logger logger = Logger.getLogger(Main.class);
public static boolean sendMessage(String email, String subject, String text) {
if (email.length() == 0) {
logger.info("Email is not sent because the address is empty");
return false;
}
ConfigSettings config = ConfigSettings.getInstance();
Properties properties = System.getProperties();
properties.setProperty("mail.smtps.host", config.getMailSmtpHost());
properties.setProperty("mail.smtps.port", config.getMailSmtpPort());
properties.setProperty("mail.smtps.auth", "true");
properties.setProperty("mail.user", config.getMailUser());
properties.setProperty("mail.password", config.getMailPassword());
Session session = Session.getDefaultInstance(properties);
try{
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(config.getMailFromAddress()));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
message.setSubject(subject);
message.setText(text);
Transport trnsport;
trnsport = session.getTransport("smtps");
trnsport.connect(null, properties.getProperty("mail.password"));
message.saveChanges();
trnsport.sendMessage(message, message.getAllRecipients());
trnsport.close();
return true;
}catch (MessagingException ex) {
logger.error("Error sending message to " + email, ex);
}
return false;
}
}

View file

@ -32,20 +32,10 @@ import java.io.FilenameFilter;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServer;
import mage.cards.repository.CardScanner;
import mage.game.match.MatchType;
import mage.game.result.ResultProtos.MatchPlayerProto;
import mage.game.result.ResultProtos.MatchProto;
import mage.game.result.ResultProtos.TableProto;
import mage.game.result.ResultProtos.TourneyPlayerProto;
import mage.game.result.ResultProtos.TourneyProto;
import mage.game.result.ResultProtos.UserStatsProto;
import mage.game.tournament.TournamentType;
import mage.interfaces.MageServer;
import mage.remote.Connection;
@ -53,9 +43,6 @@ import mage.server.draft.CubeFactory;
import mage.server.game.DeckValidatorFactory;
import mage.server.game.GameFactory;
import mage.server.game.PlayerFactory;
import mage.server.record.TableRecord;
import mage.server.record.TableRecordRepository;
import mage.server.record.UserStats;
import mage.server.record.UserStatsRepository;
import mage.server.tournament.TournamentFactory;
import mage.server.util.ConfigSettings;
@ -102,8 +89,6 @@ public class Main {
protected static boolean testMode;
protected static boolean fastDbMode;
private static final ScheduledExecutorService updateUserStatsTaskExecutor = Executors.newSingleThreadScheduledExecutor();
/**
* @param args the command line arguments
*/
@ -132,6 +117,10 @@ public class Main {
}
logger.info("Done.");
logger.info("Updating user stats DB...");
UserStatsRepository.instance.updateUserStats();
logger.info("Done.");
deleteSavedGames();
ConfigSettings config = ConfigSettings.getInstance();
for (GamePlugin plugin : config.getGameTypes()) {
@ -167,7 +156,12 @@ public class Main {
logger.info("Config - auth. activated : " + (config.isAuthenticationActivated() ? "true" : "false"));
logger.info("Config - mailgun api key : " + config.getMailgunApiKey());
logger.info("Config - mailgun domain : " + config.getMailgunDomain());
//logger.info("Config - google account : " + config.getGoogleAccount());
logger.info("Config - mail smtp Host : " + config.getMailSmtpHost());
logger.info("Config - mail smtpPort : " + config.getMailSmtpPort());
logger.info("Config - mail user : " + config.getMailUser());
logger.info("Config - mail passw. len.: " + config.getMailPassword().length());
logger.info("Config - mail from addre.: " + config.getMailFromAddress());
logger.info("Config - google account : " + config.getGoogleAccount());
Connection connection = new Connection("&maxPoolSize=" + config.getMaxPoolSize());
connection.setHost(config.getServerAddress());
@ -190,13 +184,6 @@ public class Main {
} catch (Exception ex) {
logger.fatal("Failed to start server - " + connection.toString(), ex);
}
updateUserStatsTaskExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
updateUserStats();
}
}, 60, 60, TimeUnit.SECONDS);
}
static void initStatistics() {
@ -391,78 +378,4 @@ public class Main {
public static boolean isTestMode() {
return testMode;
}
private static void updateUserStats() {
long latestEndTimeMs = UserStatsRepository.instance.getLatestEndTimeMs();
List<TableRecord> records = TableRecordRepository.instance.getAfter(latestEndTimeMs);
for (TableRecord record : records) {
TableProto table = record.getProto();
if (table.getControllerName().equals("System")) {
// This is a sub table within a tournament, so it's already handled by the main
// tournament table.
continue;
}
if (table.hasMatch()) {
MatchProto match = table.getMatch();
for (MatchPlayerProto player : match.getPlayersList()) {
UserStats userStats = UserStatsRepository.instance.getUser(player.getName());
UserStatsProto proto = userStats != null ? userStats.getProto() :
UserStatsProto.newBuilder().setName(player.getName()).build();
UserStatsProto.Builder builder = UserStatsProto.newBuilder(proto)
.setMatches(proto.getMatches() + 1);
switch (player.getQuit()) {
case IDLE_TIMEOUT:
builder.setMatchesIdleTimeout(proto.getMatchesIdleTimeout() + 1);
break;
case TIMER_TIMEOUT:
builder.setMatchesTimerTimeout(proto.getMatchesTimerTimeout() + 1);
break;
case QUIT:
builder.setMatchesQuit(proto.getMatchesQuit() + 1);
break;
}
if (userStats == null) {
UserStatsRepository.instance.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
UserStatsRepository.instance.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
// UserStats for this player is updated, so refresh it.
User user = UserManager.getInstance().getUserByName(player.getName());
if (user != null) {
user.resetUserStats();
}
}
} else if (table.hasTourney()) {
TourneyProto tourney = table.getTourney();
for (TourneyPlayerProto player : tourney.getPlayersList()) {
UserStats userStats = UserStatsRepository.instance.getUser(player.getName());
UserStatsProto proto = userStats != null ? userStats.getProto() :
UserStatsProto.newBuilder().setName(player.getName()).build();
UserStatsProto.Builder builder = UserStatsProto.newBuilder(proto)
.setTourneys(proto.getTourneys() + 1);
switch (player.getQuit()) {
case DURING_ROUND:
builder.setTourneysQuitDuringRound(proto.getTourneysQuitDuringRound() + 1);
break;
case DURING_DRAFTING:
builder.setTourneysQuitDuringDrafting(proto.getTourneysQuitDuringDrafting() + 1);
break;
case DURING_CONSTRUCTION:
builder.setTourneysQuitDuringConstruction(proto.getTourneysQuitDuringConstruction() + 1);
break;
}
if (userStats == null) {
UserStatsRepository.instance.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
UserStatsRepository.instance.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
// UserStats for this player is updated, so refresh it.
User user = UserManager.getInstance().getUserByName(player.getName());
if (user != null) {
user.resetUserStats();
}
}
}
}
}
}

View file

@ -99,8 +99,15 @@ public class Session {
return returnMessage;
}
AuthorizedUserRepository.instance.add(userName, password, email);
if (MailgunClient.sendMessage(email, "XMage Registration Completed",
"You are successfully registered as " + userName + ".")) {
String subject = "XMage Registration Completed";
String text = "You are successfully registered as " + userName + ".";
boolean success;
if (!ConfigSettings.getInstance().getMailUser().isEmpty()) {
success = MailClient.sendMessage(email, subject, text);
} else {
success = MailgunClient.sendMessage(email, subject, text);
}
if (success) {
logger.info("Sent a registration confirmation email to " + email + " for " + userName);
} else {
logger.error("Failed sending a registration confirmation email to " + email + " for " + userName);

View file

@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit;
import mage.cards.decks.Deck;
import mage.constants.ManaType;
import mage.game.Table;
import mage.game.result.ResultProtos;
import mage.game.tournament.TournamentPlayer;
import mage.interfaces.callback.ClientCallback;
import mage.players.net.UserData;
@ -61,7 +62,7 @@ import org.apache.log4j.Logger;
*/
public class User {
private static final Logger logger = Logger.getLogger(User.class);
private static final Logger LOGGER = Logger.getLogger(User.class);
public enum UserState {
@ -81,7 +82,6 @@ public class User {
private final Map<UUID, Deck> sideboarding;
private final List<UUID> watchedGames;
private String sessionId;
private String info = "";
private String pingInfo = "";
private Date lastActivity;
private UserState userState;
@ -106,6 +106,7 @@ public class User {
this.watchedGames = new ArrayList<>();
this.tablesToDelete = new ArrayList<>();
this.sessionId = "";
this.userStats = null;
}
public String getName() {
@ -129,15 +130,15 @@ public class User {
if (sessionId.isEmpty()) {
userState = UserState.Disconnected;
lostConnection();
logger.trace("USER - lost connection: " + userName + " id: " + userId);
LOGGER.trace("USER - lost connection: " + userName + " id: " + userId);
} else if (userState == UserState.Created) {
userState = UserState.Connected;
logger.trace("USER - created: " + userName + " id: " + userId);
LOGGER.trace("USER - created: " + userName + " id: " + userId);
} else {
userState = UserState.Reconnected;
reconnect();
logger.trace("USER - reconnected: " + userName + " id: " + userId);
LOGGER.trace("USER - reconnected: " + userName + " id: " + userId);
}
}
@ -273,12 +274,13 @@ public class User {
public boolean isExpired(Date expired) {
if (lastActivity.before(expired)) {
logger.trace(userName + " is expired!");
LOGGER.trace(userName + " is expired!");
userState = UserState.Expired;
return true;
}
logger.trace(new StringBuilder("isExpired: User ").append(userName).append(" lastActivity: ").append(lastActivity).append(" expired: ").append(expired).toString());
return false; /*userState == UserState.Disconnected && */
LOGGER.trace(new StringBuilder("isExpired: User ").append(userName).append(" lastActivity: ").append(lastActivity).append(" expired: ").append(expired).toString());
return false;
/*userState == UserState.Disconnected && */
}
@ -360,35 +362,35 @@ public class User {
}
public void remove(DisconnectReason reason) {
logger.trace("REMOVE " + getName() + " Draft sessions " + draftSessions.size());
LOGGER.trace("REMOVE " + getName() + " Draft sessions " + draftSessions.size());
for (DraftSession draftSession : draftSessions.values()) {
draftSession.setKilled();
}
draftSessions.clear();
logger.trace("REMOVE " + getName() + " Tournament sessions " + userTournaments.size());
LOGGER.trace("REMOVE " + getName() + " Tournament sessions " + userTournaments.size());
for (UUID tournamentId : userTournaments.values()) {
TournamentManager.getInstance().quit(tournamentId, getId());
}
userTournaments.clear();
logger.trace("REMOVE " + getName() + " Tables " + tables.size());
LOGGER.trace("REMOVE " + getName() + " Tables " + tables.size());
for (Entry<UUID, Table> entry : tables.entrySet()) {
logger.debug("-- leave tableId: " + entry.getValue().getId());
LOGGER.debug("-- leave tableId: " + entry.getValue().getId());
TableManager.getInstance().leaveTable(userId, entry.getValue().getId());
}
tables.clear();
logger.trace("REMOVE " + getName() + " Game sessions: " + gameSessions.size());
LOGGER.trace("REMOVE " + getName() + " Game sessions: " + gameSessions.size());
for (GameSessionPlayer gameSessionPlayer : gameSessions.values()) {
logger.debug("-- kill game session of gameId: " + gameSessionPlayer.getGameId());
LOGGER.debug("-- kill game session of gameId: " + gameSessionPlayer.getGameId());
GameManager.getInstance().quitMatch(gameSessionPlayer.getGameId(), userId);
gameSessionPlayer.quitGame();
}
gameSessions.clear();
logger.trace("REMOVE " + getName() + " watched Games " + watchedGames.size());
LOGGER.trace("REMOVE " + getName() + " watched Games " + watchedGames.size());
for (UUID gameId : watchedGames) {
GameManager.getInstance().stopWatching(gameId, userId);
}
watchedGames.clear();
logger.trace("REMOVE " + getName() + " Chats ");
LOGGER.trace("REMOVE " + getName() + " Chats ");
ChatManager.getInstance().removeUser(userId, reason);
}
@ -397,6 +399,12 @@ public class User {
this.userData.update(userData);
} else {
this.userData = userData;
this.userStats = UserStatsRepository.instance.getUser(this.userName);
if (userStats != null) {
this.userData.setHistory(userStatsToString(userStats.getProto()));
} else {
this.userData.setHistory("<new player>");
}
}
}
@ -446,11 +454,11 @@ public class User {
}
} else {
// can happen if tournamet has just ended
logger.debug(getName() + " tournament player missing - tableId:" + table.getId(), null);
LOGGER.debug(getName() + " tournament player missing - tableId:" + table.getId(), null);
tablesToDelete.add(tableEntry.getKey());
}
} else {
logger.error(getName() + " tournament key missing - tableId: " + table.getId(), null);
LOGGER.error(getName() + " tournament key missing - tableId: " + table.getId(), null);
}
} else {
switch (table.getState()) {
@ -500,14 +508,6 @@ public class User {
return sb.toString();
}
public String getInfo() {
return info;
}
public void setInfo(String Info) {
this.info = Info;
}
public void addGameWatchInfo(UUID gameId) {
watchedGames.add(gameId);
}
@ -540,5 +540,84 @@ public class User {
// resetUserStats loads UserStats from DB.
public void resetUserStats() {
this.userStats = UserStatsRepository.instance.getUser(this.userName);
if (userData != null) {
userData.setHistory(userStatsToString(userStats.getProto()));
}
}
public String getHistory() {
if (userData != null) {
return userData.getHistory();
}
return "<not available>";
}
public static String userStatsToString(ResultProtos.UserStatsProto proto) {
List<StringBuilder> builders = new ArrayList<>();
if (proto.getMatches() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Matches:");
builder.append(proto.getMatches());
List<String> quit = new ArrayList<>();
if (proto.getMatchesIdleTimeout() > 0) {
quit.add("I:" + Integer.toString(proto.getMatchesIdleTimeout()));
}
if (proto.getMatchesTimerTimeout() > 0) {
quit.add("T:" + Integer.toString(proto.getMatchesTimerTimeout()));
}
if (proto.getMatchesQuit() > 0) {
quit.add("Q:" + Integer.toString(proto.getMatchesQuit()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
if (proto.getTourneys() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Tourneys:");
builder.append(proto.getTourneys());
List<String> quit = new ArrayList<>();
if (proto.getTourneysQuitDuringDrafting() > 0) {
quit.add("D:" + Integer.toString(proto.getTourneysQuitDuringDrafting()));
}
if (proto.getTourneysQuitDuringConstruction() > 0) {
quit.add("C:" + Integer.toString(proto.getTourneysQuitDuringConstruction()));
}
if (proto.getTourneysQuitDuringRound() > 0) {
quit.add("R:" + Integer.toString(proto.getTourneysQuitDuringRound()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
return joinBuilders(builders);
}
private static String joinBuilders(List<StringBuilder> builders) {
if (builders.isEmpty()) {
return null;
}
StringBuilder builder = builders.get(0);
for (int i = 1; i < builders.size(); ++i) {
builder.append(" ");
builder.append(builders.get(i));
}
return builder.toString();
}
private static void joinStrings(StringBuilder joined, List<String> strings, String separator) {
for (int i = 0; i < strings.size(); ++i) {
if (i > 0) {
joined.append(separator);
}
joined.append(strings.get(i));
}
}
}

View file

@ -1,16 +1,16 @@
/*
* Copyright 2011 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
@ -20,7 +20,7 @@
* 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.
@ -38,6 +38,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import mage.server.User.UserState;
import mage.server.record.UserStats;
import mage.server.record.UserStatsRepository;
import mage.server.util.ThreadExecutor;
import org.apache.log4j.Logger;
@ -45,7 +47,7 @@ import org.apache.log4j.Logger;
*
* manages users - if a user is disconnected and 10 minutes have passed with no
* activity the user is removed
*
*
* @author BetaSteward_at_googlemail.com
*/
public class UserManager {
@ -56,7 +58,7 @@ public class UserManager {
private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, User> usersByName = new ConcurrentHashMap<>();
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
private static final UserManager INSTANCE = new UserManager();
@ -64,8 +66,8 @@ public class UserManager {
public static UserManager getInstance() {
return INSTANCE;
}
private UserManager() {
private UserManager() {
expireExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
@ -113,7 +115,7 @@ public class UserManager {
public void disconnect(UUID userId, DisconnectReason reason) {
if (userId != null) {
User user = users.get(userId);
if (user != null) {
if (user != null) {
user.setSessionId(""); // Session will be set again with new id if user reconnects
}
ChatManager.getInstance().removeUser(userId, reason);
@ -123,44 +125,44 @@ public class UserManager {
public boolean isAdmin(UUID userId) {
if (userId != null) {
User user = users.get(userId);
if (user != null) {
if (user != null) {
return user.getName().equals("Admin");
}
}
return false;
}
public void removeUser(final UUID userId, final DisconnectReason reason) {
public void removeUser(final UUID userId, final DisconnectReason reason) {
if (userId != null) {
final User user = users.get(userId);
if (user != null) {
callExecutor.execute(
new Runnable() {
@Override
public void run() {
try {
logger.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId);
user.remove(reason);
logger.debug("USER REMOVE END - " + user.getName());
} catch (Exception ex) {
handleException(ex);
} finally {
users.remove(userId);
usersByName.remove(user.getName());
}
new Runnable() {
@Override
public void run() {
try {
logger.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId);
user.remove(reason);
logger.debug("USER REMOVE END - " + user.getName());
} catch (Exception ex) {
handleException(ex);
} finally {
users.remove(userId);
usersByName.remove(user.getName());
}
}
}
);
} else {
logger.warn("Trying to remove userId: " + userId + " - but it does not exist.");
}
}
}
}
public boolean extendUserSession(UUID userId, String pingInfo) {
if (userId != null) {
User user = users.get(userId);
if (user != null) {
if (user != null) {
user.updateLastActivity(pingInfo);
return true;
}
@ -169,7 +171,8 @@ public class UserManager {
}
/**
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes the user can reconnect)
* Is the connection lost for more than 3 minutes, the user will be removed
* (within 3 minutes the user can reconnect)
*/
private void checkExpired() {
Calendar calendar = Calendar.getInstance();
@ -185,13 +188,39 @@ public class UserManager {
public void handleException(Exception ex) {
if (ex != null) {
logger.fatal("User manager exception " + (ex.getMessage() == null ? "null":ex.getMessage()));
logger.fatal("User manager exception " + (ex.getMessage() == null ? "null" : ex.getMessage()));
if (ex.getCause() != null) {
logger.debug("- Cause: " + (ex.getCause().getMessage() == null ? "null":ex.getCause().getMessage()));
logger.debug("- Cause: " + (ex.getCause().getMessage() == null ? "null" : ex.getCause().getMessage()));
}
ex.printStackTrace();
}else {
} else {
logger.fatal("User manager exception - null");
}
}
public String getUserHistory(String userName) {
User user = getUserByName(userName);
if (user == null) {
UserStats userStats = UserStatsRepository.instance.getUser(userName);
if (userStats == null) {
return "User " + userName + " not found";
}
return User.userStatsToString(userStats.getProto());
}
return "History of user " + userName + ": " + user.getUserData().getHistory();
}
public void updateUserHistory() {
callExecutor.execute(new Runnable() {
@Override
public void run() {
for (String updatedUser : UserStatsRepository.instance.updateUserStats()) {
User user = getUserByName(updatedUser);
if (user != null) {
user.resetUserStats();
}
}
}
});
}
}

View file

@ -43,13 +43,11 @@ import mage.constants.TableState;
import mage.game.GameException;
import mage.game.Table;
import mage.game.match.MatchOptions;
import mage.game.result.ResultProtos.UserStatsProto;
import mage.game.tournament.TournamentOptions;
import mage.server.RoomImpl;
import mage.server.TableManager;
import mage.server.User;
import mage.server.UserManager;
import mage.server.record.UserStats;
import mage.server.tournament.TournamentManager;
import mage.server.util.ConfigSettings;
import mage.server.util.ThreadExecutor;
@ -93,74 +91,6 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
return tableView;
}
private static void joinStrings(StringBuilder joined, List<String> strings, String separator) {
for (int i = 0; i < strings.size(); ++i) {
if (i > 0) {
joined.append(separator);
}
joined.append(strings.get(i));
}
}
private static String joinBuilders(List<StringBuilder> builders) {
if (builders.isEmpty()) {
return null;
}
StringBuilder builder = builders.get(0);
for (int i = 1; i < builders.size(); ++i) {
builder.append(" ");
builder.append(builders.get(i));
}
return builder.toString();
}
private static String userStatsToString(UserStatsProto proto) {
List<StringBuilder> builders = new ArrayList<>();
if (proto.getMatches() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Matches:");
builder.append(proto.getMatches());
List<String> quit = new ArrayList<>();
if (proto.getMatchesIdleTimeout() > 0) {
quit.add("I:" + Integer.toString(proto.getMatchesIdleTimeout()));
}
if (proto.getMatchesTimerTimeout() > 0) {
quit.add("T:" + Integer.toString(proto.getMatchesTimerTimeout()));
}
if (proto.getMatchesQuit() > 0) {
quit.add("Q:" + Integer.toString(proto.getMatchesQuit()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
if (proto.getTourneys() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Tourneys:");
builder.append(proto.getTourneys());
List<String> quit = new ArrayList<>();
if (proto.getTourneysQuitDuringDrafting() > 0) {
quit.add("D:" + Integer.toString(proto.getTourneysQuitDuringDrafting()));
}
if (proto.getTourneysQuitDuringConstruction() > 0) {
quit.add("C:" + Integer.toString(proto.getTourneysQuitDuringConstruction()));
}
if (proto.getTourneysQuitDuringRound() > 0) {
quit.add("R:" + Integer.toString(proto.getTourneysQuitDuringRound()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
return joinBuilders(builders);
}
private void update() {
ArrayList<TableView> tableList = new ArrayList<>();
ArrayList<MatchView> matchList = new ArrayList<>();
@ -183,20 +113,14 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
matchView = matchList;
List<UsersView> users = new ArrayList<>();
for (User user : UserManager.getInstance().getUsers()) {
String history = null;
UserStats stats = user.getUserStats();
if (stats != null) {
history = userStatsToString(stats.getProto());
}
try {
users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), history, user.getInfo(), user.getGameInfo(), user.getPingInfo()));
users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), user.getHistory(), user.getGameInfo(), user.getPingInfo()));
} catch (Exception ex) {
logger.fatal("User update exception: " + user.getName() + " - " + ex.toString(), ex);
users.add(new UsersView(
(user.getUserData() != null && user.getUserData().getFlagName() != null) ? user.getUserData().getFlagName() : "world",
user.getName() != null ? user.getName() : "<no name>",
history != null ? history : "<no history>",
user.getInfo() != null ? user.getInfo() : "<no info>",
user.getHistory() != null ? user.getHistory() : "<no history>",
"[exception]",
user.getPingInfo() != null ? user.getPingInfo() : "<no ping>"));
}

View file

@ -3,6 +3,7 @@ package mage.server.record;
import mage.game.Table;
import mage.game.Table.TableRecorder;
import mage.game.result.ResultProtos.TableProto;
import mage.server.UserManager;
import org.apache.log4j.Logger;
public class TableRecorderImpl implements TableRecorder {
@ -17,5 +18,6 @@ public class TableRecorderImpl implements TableRecorder {
public void record(Table table) {
TableProto proto = table.toProto();
TableRecordRepository.instance.add(new TableRecord(proto, proto.getEndTimeMs()));
UserManager.getInstance().updateUserHistory();
}
}

View file

@ -9,8 +9,11 @@ import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import mage.cards.repository.RepositoryUtil;
import mage.game.result.ResultProtos;
import org.apache.log4j.Logger;
public enum UserStatsRepository {
@ -98,6 +101,79 @@ public enum UserStatsRepository {
return 0;
}
// updateUserStats reads tables finished after the last DB update and reflects it to the DB.
// It returns the list of user names that are upated.
public List<String> updateUserStats() {
HashSet<String> updatedUsers = new HashSet();
// Lock the DB so that no other updateUserStats runs at the same time.
synchronized(this) {
long latestEndTimeMs = this.getLatestEndTimeMs();
List<TableRecord> records = TableRecordRepository.instance.getAfter(latestEndTimeMs);
for (TableRecord record : records) {
ResultProtos.TableProto table = record.getProto();
if (table.getControllerName().equals("System")) {
// This is a sub table within a tournament, so it's already handled by the main
// tournament table.
continue;
}
if (table.hasMatch()) {
ResultProtos.MatchProto match = table.getMatch();
for (ResultProtos.MatchPlayerProto player : match.getPlayersList()) {
UserStats userStats = this.getUser(player.getName());
ResultProtos.UserStatsProto proto = userStats != null ? userStats.getProto()
: ResultProtos.UserStatsProto.newBuilder().setName(player.getName()).build();
ResultProtos.UserStatsProto.Builder builder = ResultProtos.UserStatsProto.newBuilder(proto)
.setMatches(proto.getMatches() + 1);
switch (player.getQuit()) {
case IDLE_TIMEOUT:
builder.setMatchesIdleTimeout(proto.getMatchesIdleTimeout() + 1);
break;
case TIMER_TIMEOUT:
builder.setMatchesTimerTimeout(proto.getMatchesTimerTimeout() + 1);
break;
case QUIT:
builder.setMatchesQuit(proto.getMatchesQuit() + 1);
break;
}
if (userStats == null) {
this.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
this.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
updatedUsers.add(player.getName());
}
} else if (table.hasTourney()) {
ResultProtos.TourneyProto tourney = table.getTourney();
for (ResultProtos.TourneyPlayerProto player : tourney.getPlayersList()) {
UserStats userStats = this.getUser(player.getName());
ResultProtos.UserStatsProto proto = userStats != null ? userStats.getProto()
: ResultProtos.UserStatsProto.newBuilder().setName(player.getName()).build();
ResultProtos.UserStatsProto.Builder builder = ResultProtos.UserStatsProto.newBuilder(proto)
.setTourneys(proto.getTourneys() + 1);
switch (player.getQuit()) {
case DURING_ROUND:
builder.setTourneysQuitDuringRound(proto.getTourneysQuitDuringRound() + 1);
break;
case DURING_DRAFTING:
builder.setTourneysQuitDuringDrafting(proto.getTourneysQuitDuringDrafting() + 1);
break;
case DURING_CONSTRUCTION:
builder.setTourneysQuitDuringConstruction(proto.getTourneysQuitDuringConstruction() + 1);
break;
}
if (userStats == null) {
this.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
this.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
updatedUsers.add(player.getName());
}
}
}
}
return new ArrayList(updatedUsers);
}
public void closeDB() {
try {
if (dao != null && dao.getConnectionSource() != null) {

View file

@ -147,6 +147,26 @@ public class ConfigSettings {
return config.getServer().getMailgunDomain();
}
public String getMailSmtpHost() {
return config.getServer().getMailSmtpHost();
}
public String getMailSmtpPort() {
return config.getServer().getMailSmtpPort();
}
public String getMailUser() {
return config.getServer().getMailUser();
}
public String getMailPassword() {
return config.getServer().getMailPassword();
}
public String getMailFromAddress() {
return config.getServer().getMailFromAddress();
}
public List<Plugin> getPlayerTypes() {
return config.getPlayerTypes().getPlayerType();
}

View file

@ -38,6 +38,11 @@
<xs:attribute name="googleAccount" type="xs:string" use="optional"/>
<xs:attribute name="mailgunApiKey" type="xs:string" use="optional"/>
<xs:attribute name="mailgunDomain" type="xs:string" use="optional"/>
<xs:attribute name="mailSmtpHost" type="xs:string" use="optional"/>
<xs:attribute name="mailSmtpPort" type="xs:string" use="optional"/>
<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:complexType>
</xs:element>

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<groupId>org.mage</groupId>

View file

@ -37,6 +37,8 @@ import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@ -54,8 +56,17 @@ public class ConsumeStrength extends CardImpl {
// Target creature gets +2/+2 until end of turn. Another target creature gets -2/-2 until end of turn.
this.getSpellAbility().addEffect(new ConsumeStrengthEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(2));
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature to get +2/+2");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature to get -2/-2");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public ConsumeStrength(final ConsumeStrength card) {
@ -91,7 +102,7 @@ class ConsumeStrengthEffect extends ContinuousEffectImpl {
permanent.addPower(2);
permanent.addToughness(2);
}
permanent = game.getPermanent(source.getTargets().get(0).getTargets().get(1));
permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (permanent != null) {
permanent.addPower(-2);
permanent.addToughness(-2);

View file

@ -49,7 +49,6 @@ public class DreamFracture extends CardImpl {
super(ownerId, 19, "Dream Fracture", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{1}{U}{U}");
this.expansionSetCode = "EVE";
// Counter target spell. Its controller draws a card.
this.getSpellAbility().addEffect(new DreamFractureEffect());
this.getSpellAbility().addTarget(new TargetSpell());
@ -73,7 +72,7 @@ class DreamFractureEffect extends OneShotEffect {
public DreamFractureEffect() {
super(Outcome.Neutral);
this.staticText = "Counter target spell. Its controller draws a card";
this.staticText = "Counter target spell. Its controller draws a card";
}
public DreamFractureEffect(final DreamFractureEffect effect) {
@ -102,4 +101,4 @@ class DreamFractureEffect extends OneShotEffect {
}
return countered;
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.fifthedition;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 387;
this.expansionSetCode = "5ED";
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.fourthedition;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 351;
this.expansionSetCode = "4ED";
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -56,7 +56,7 @@ public class Bioshift extends CardImpl {
// Move any number of +1/+1 counters from target creature onto another target creature with the same controller.
getSpellAbility().addEffect(new MoveCounterFromTargetToTargetEffect());
getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (you take counters from)")));
getSpellAbility().addTarget(new BioshiftSecondTargetPermanent());
getSpellAbility().addTarget(new BioshiftSecondTargetCreaturePermanent());
}
@ -113,14 +113,15 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect {
}
}
class BioshiftSecondTargetPermanent extends TargetPermanent {
class BioshiftSecondTargetCreaturePermanent extends TargetCreaturePermanent {
BioshiftSecondTargetPermanent() {
super();
this.filter = new FilterCreaturePermanent("another target creature with the same controller (counters go to)");
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature with the same controller (counters go to)");
BioshiftSecondTargetCreaturePermanent() {
super(filter);
}
BioshiftSecondTargetPermanent(final BioshiftSecondTargetPermanent target) {
BioshiftSecondTargetCreaturePermanent(final BioshiftSecondTargetCreaturePermanent target) {
super(target);
}
@ -137,7 +138,7 @@ class BioshiftSecondTargetPermanent extends TargetPermanent {
}
@Override
public BioshiftSecondTargetPermanent copy() {
return new BioshiftSecondTargetPermanent(this);
public BioshiftSecondTargetCreaturePermanent copy() {
return new BioshiftSecondTargetCreaturePermanent(this);
}
}

View file

@ -37,6 +37,8 @@ import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@ -53,7 +55,17 @@ public class Schismotivate extends CardImpl {
// Target creature gets +4/+0 until end of turn. Another target creature gets -4/-0 until end of turn.
this.getSpellAbility().addEffect(new SchismotivateEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(2));
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature (gets +4/+0 until end of turn)");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets -4/-0 until end of turn)");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public Schismotivate(final Schismotivate card) {
@ -88,7 +100,7 @@ class SchismotivateEffect extends ContinuousEffectImpl {
if (permanent != null) {
permanent.addPower(4);
}
permanent = game.getPermanent(source.getTargets().get(0).getTargets().get(1));
permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (permanent != null) {
permanent.addPower(-4);
}

View file

@ -0,0 +1,121 @@
/*
* 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.legions;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.DiesTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.repository.CardRepository;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class ElvishSoultiller extends CardImpl {
public ElvishSoultiller(UUID ownerId) {
super(ownerId, 124, "Elvish Soultiller", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{G}{G}");
this.expansionSetCode = "LGN";
this.subtype.add("Elf");
this.subtype.add("Mutant");
this.power = new MageInt(5);
this.toughness = new MageInt(4);
// When Elvish Soultiller dies, choose a creature type. Shuffle all creature cards of that type from your graveyard into your library.
addAbility(new DiesTriggeredAbility(new ElvishSoultillerEffect()));
}
public ElvishSoultiller(final ElvishSoultiller card) {
super(card);
}
@Override
public ElvishSoultiller copy() {
return new ElvishSoultiller(this);
}
}
class ElvishSoultillerEffect extends OneShotEffect {
public ElvishSoultillerEffect() {
super(Outcome.Benefit);
this.staticText = "choose a creature type. Shuffle all creature cards of that type from your graveyard into your library";
}
public ElvishSoultillerEffect(final ElvishSoultillerEffect effect) {
super(effect);
}
@Override
public ElvishSoultillerEffect copy() {
return new ElvishSoultillerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject mageObject = game.getObject(source.getSourceId());
if (controller != null && mageObject != null) {
Choice typeChoice = new ChoiceImpl(true);
typeChoice.setMessage("Choose creature type");
typeChoice.setChoices(CardRepository.instance.getCreatureTypes());
while (!controller.choose(outcome, typeChoice, game)) {
if (!controller.canRespond()) {
return false;
}
}
if (!game.isSimulation()) {
game.informPlayers(mageObject.getName() + ": " + controller.getLogName() + " has chosen " + typeChoice.getChoice());
}
Cards cardsToLibrary = new CardsImpl();
FilterCreatureCard filter = new FilterCreatureCard();
filter.add(new SubtypePredicate(typeChoice.getChoice()));
cardsToLibrary.addAll(controller.getGraveyard().getCards(filter, source.getSourceId(), source.getControllerId(), game));
controller.putCardsOnTopOfLibrary(cardsToLibrary, game, source, false);
controller.shuffleLibrary(game);
return true;
}
return false;
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.limitedalpha;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 257;
this.expansionSetCode = "LEA";
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -0,0 +1,149 @@
/*
* 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.limitedbeta;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends CardImpl {
public LibraryOfLeng(UUID ownerId) {
super(ownerId, 259, "Library of Leng", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{1}");
this.expansionSetCode = "LEB";
// You have no maximum hand size.
Effect effect = new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, MaximumHandSizeControllerEffect.HandSizeModification.SET);
addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect));
// If an effect causes you to discard a card, discard it, but you may put it on top of your library instead of into your graveyard.
addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LibraryOfLengEffect()));
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}
class LibraryOfLengEffect extends ReplacementEffectImpl {
private UUID cardId;
private int zoneChangeCounter;
public LibraryOfLengEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "If an effect causes you to discard a card, discard it, but you may put it on top of your library instead of into your graveyard";
}
public LibraryOfLengEffect(final LibraryOfLengEffect effect) {
super(effect);
this.cardId = effect.cardId;
this.zoneChangeCounter = effect.zoneChangeCounter;
}
@Override
public LibraryOfLengEffect copy() {
return new LibraryOfLengEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType().equals(EventType.DISCARD_CARD)
|| event.getType().equals(EventType.ZONE_CHANGE);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType().equals(EventType.DISCARD_CARD)) {
return event.getPlayerId().equals(source.getControllerId());
}
if (event.getType().equals(EventType.ZONE_CHANGE)) {
if (event.getTargetId().equals(cardId) && game.getState().getZoneChangeCounter(event.getTargetId()) == zoneChangeCounter) {
if (((ZoneChangeEvent) event).getFromZone().equals(Zone.HAND) && ((ZoneChangeEvent) event).getToZone().equals(Zone.GRAVEYARD)) {
return true;
}
}
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (event.getType().equals(EventType.DISCARD_CARD)) {
// only save card info
Card card = game.getCard(event.getTargetId());
if (card != null) {
cardId = card.getId();
zoneChangeCounter = game.getState().getZoneChangeCounter(cardId);
}
return false;
}
if (event.getType().equals(EventType.ZONE_CHANGE)) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(event.getTargetId());
if (controller != null && card != null) {
cardId = null;
zoneChangeCounter = 0;
if (controller.chooseUse(outcome, "Put " + card.getIdName() + " on top of your library instead?", source, game)) {
Cards cardsToLibrary = new CardsImpl(card);
controller.putCardsOnTopOfLibrary(cardsToLibrary, game, source, false);
return true;
}
}
}
return false;
}
}

View file

@ -50,7 +50,6 @@ import mage.filter.predicate.permanent.AnotherPredicate;
import mage.filter.predicate.permanent.ControllerPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
* @author nantuko
@ -87,8 +86,8 @@ public class AdaptiveAutomaton extends CardImpl {
}
}
class AdaptiveAutomatonAddSubtypeEffect extends ContinuousEffectImpl {
public AdaptiveAutomatonAddSubtypeEffect() {
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
staticText = "{this} is the chosen type in addition to its other types";

View file

@ -0,0 +1,54 @@
/*
* 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.masterseditioniv;
import java.util.UUID;
import mage.constants.Rarity;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 211;
this.expansionSetCode = "ME4";
this.rarity = Rarity.COMMON;
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -35,6 +35,8 @@ import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
@ -53,8 +55,23 @@ public class IncrementalGrowth extends CardImpl {
// Put a +1/+1 counter on target creature, two +1/+1 counters on another target creature, and three +1/+1 counters on a third target creature.
this.getSpellAbility().addEffect(new IncrementalGrowthEffect());
Target target = new TargetCreaturePermanent(3,3);
this.getSpellAbility().addTarget(target);
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature (gets a +1/+1 counter)");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets two +1/+1 counter)");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
FilterCreaturePermanent filter3 = new FilterCreaturePermanent("another creature (gets three +1/+1 counters)");
filter3.add(new AnotherTargetPredicate(3));
TargetCreaturePermanent target3 = new TargetCreaturePermanent(filter3);
target3.setTargetTag(3);
this.getSpellAbility().addTarget(target3);
}
public IncrementalGrowth(final IncrementalGrowth card) {
@ -86,9 +103,9 @@ class IncrementalGrowthEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
int i = 0;
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
for (Target target : source.getTargets()) {
i++;
Permanent creature = game.getPermanent(targetId);
Permanent creature = game.getPermanent(target.getFirstTarget());
if (creature != null) {
creature.addCounters(CounterType.P1P1.createInstance(i), game);
}

View file

@ -28,15 +28,20 @@
package mage.sets.newphyrexia;
import java.util.UUID;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.SecondTargetPointer;
/**
*
@ -48,16 +53,20 @@ public class LeechingBite extends CardImpl {
super(ownerId, 113, "Leeching Bite", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{G}");
this.expansionSetCode = "NPH";
// Target creature gets +1/+1 until end of turn. Another target creature gets -1/-1 until end of turn.
Effect effect = new BoostTargetEffect(1, 1, Duration.EndOfTurn);
effect.setText("Target creature gets +1/+1 until end of turn");
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (getting the +1/+1 counter)")));
effect = new BoostTargetEffect(-1, -1, Duration.EndOfTurn);
effect.setText("Another target creature gets -1/-1 until end of turn");
effect.setTargetPointer(new SecondTargetPointer());
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (getting the -1/-1 counter)")));
this.getSpellAbility().addEffect(new LeechingBiteEffect());
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature to get +1/+1");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature to get -1/-1");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public LeechingBite(final LeechingBite card) {
@ -69,3 +78,35 @@ public class LeechingBite extends CardImpl {
return new LeechingBite(this);
}
}
class LeechingBiteEffect extends ContinuousEffectImpl {
public LeechingBiteEffect() {
super(Duration.EndOfTurn, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.BoostCreature);
this.staticText = "Target creature gets +1/+1 until end of turn. Another target creature gets -1/-1 until end of turn";
}
public LeechingBiteEffect(final LeechingBiteEffect effect) {
super(effect);
}
@Override
public LeechingBiteEffect copy() {
return new LeechingBiteEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (permanent != null) {
permanent.addPower(1);
permanent.addToughness(1);
}
permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (permanent != null) {
permanent.addPower(-1);
permanent.addToughness(-1);
}
return true;
}
}

View file

@ -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.sets.planarchaos;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetSpell;
/**
*
* @author LevelX2
*/
public class DismalFailure extends CardImpl {
public DismalFailure(UUID ownerId) {
super(ownerId, 39, "Dismal Failure", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{2}{U}{U}");
this.expansionSetCode = "PLC";
// Counter target spell. Its controller discards a card.
this.getSpellAbility().addEffect(new DismalFailureEffect());
this.getSpellAbility().addTarget(new TargetSpell());
}
public DismalFailure(final DismalFailure card) {
super(card);
}
@Override
public DismalFailure copy() {
return new DismalFailure(this);
}
}
class DismalFailureEffect extends OneShotEffect {
public DismalFailureEffect() {
super(Outcome.Neutral);
this.staticText = "Counter target spell. Its controller discards a card";
}
public DismalFailureEffect(final DismalFailureEffect effect) {
super(effect);
}
@Override
public DismalFailureEffect copy() {
return new DismalFailureEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
UUID targetId = source.getFirstTarget();
Player controller = null;
boolean countered = false;
if (targetId != null) {
controller = game.getPlayer(game.getControllerId(targetId));
}
if (targetId != null
&& game.getStack().counter(targetId, source.getSourceId(), game)) {
countered = true;
}
if (controller != null) {
controller.discard(1, false, source, game);
}
return countered;
}
}

View file

@ -37,6 +37,8 @@ import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@ -53,7 +55,17 @@ public class StealStrength extends CardImpl {
// Target creature gets +1/+1 until end of turn. Another target creature gets -1/-1 until end of turn.
this.getSpellAbility().addEffect(new StealStrengthEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(2));
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature (gets +1/+1 until end of turn)");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets -1/-1 until end of turn)");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public StealStrength(final StealStrength card) {
@ -89,7 +101,7 @@ class StealStrengthEffect extends ContinuousEffectImpl {
permanent.addPower(1);
permanent.addToughness(1);
}
permanent = game.getPermanent(source.getTargets().get(0).getTargets().get(1));
permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (permanent != null) {
permanent.addPower(-1);
permanent.addToughness(-1);

View file

@ -37,6 +37,8 @@ import mage.constants.SubLayer;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.CardImpl;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@ -54,7 +56,17 @@ public class RitesOfReaping extends CardImpl {
// Target creature gets +3/+3 until end of turn. Another target creature gets -3/-3 until end of turn.
this.getSpellAbility().addEffect(new RitesOfReapingEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(2));
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature (gets +3/+3 until end of turn)");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets -3/-3 until end of turn)");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public RitesOfReaping(final RitesOfReaping card) {
@ -90,7 +102,7 @@ class RitesOfReapingEffect extends ContinuousEffectImpl {
permanent.addPower(3);
permanent.addToughness(3);
}
permanent = game.getPermanent(source.getTargets().get(0).getTargets().get(1));
permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (permanent != null) {
permanent.addPower(-3);
permanent.addToughness(-3);

View file

@ -0,0 +1,52 @@
/*
* 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.revisededition;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 261;
this.expansionSetCode = "3ED";
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -31,9 +31,7 @@ import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.discard.DiscardTargetEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
@ -42,7 +40,6 @@ import mage.constants.Rarity;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPlayer;
import mage.target.targetpointer.FixedTarget;
/**
*
@ -94,14 +91,17 @@ class CragganwickCrematorEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player you = game.getPlayer(source.getControllerId());
Player targetedPlayer = game.getPlayer(source.getFirstTarget());
if (you != null && targetedPlayer != null) {
Card discardedCard = targetedPlayer.discardOne(true, source, game);
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card discardedCard = controller.discardOne(true, source, game);
if (discardedCard != null
&& discardedCard.getCardType().contains(CardType.CREATURE)) {
int damage = discardedCard.getPower().getValue();
targetedPlayer.damage(damage, source.getSourceId(), game, false, true);
Player targetedPlayer = game.getPlayer(source.getFirstTarget());
if (targetedPlayer != null) {
int damage = discardedCard.getPower().getValue();
targetedPlayer.damage(damage, source.getSourceId(), game, false, true);
}
}
return true;
}

View file

@ -36,6 +36,7 @@ import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.counters.Counter;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent;
@ -47,7 +48,7 @@ import mage.target.common.TargetCreaturePermanent;
public class FateTransfer extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("target creature to move all counters from");
private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("target creature to move all counters to");
private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another target creature to move all counters to");
public FateTransfer(UUID ownerId) {
super(ownerId, 161, "Fate Transfer", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U/B}");
@ -55,8 +56,15 @@ public class FateTransfer extends CardImpl {
// Move all counters from target creature onto another target creature.
this.getSpellAbility().addEffect(new FateTransferEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter2));
TargetCreaturePermanent fromTarget = new TargetCreaturePermanent(filter);
fromTarget.setTargetTag(1);
this.getSpellAbility().addTarget(fromTarget);
TargetCreaturePermanent toTarget = new TargetCreaturePermanent(filter2);
filter2.add(new AnotherTargetPredicate(2));
toTarget.setTargetTag(2);
this.getSpellAbility().addTarget(toTarget);
}

View file

@ -35,6 +35,8 @@ import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
@ -53,8 +55,23 @@ public class IncrementalBlight extends CardImpl {
// Put a -1/-1 counter on target creature, two -1/-1 counters on another target creature, and three -1/-1 counters on a third target creature.
this.getSpellAbility().addEffect(new IncrementalBlightEffect());
Target target = new TargetCreaturePermanent(3,3);
this.getSpellAbility().addTarget(target);
FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature (gets a -1/-1 counter)");
TargetCreaturePermanent target1 = new TargetCreaturePermanent(filter1);
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets two -1/-1 counters)");
filter2.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
FilterCreaturePermanent filter3 = new FilterCreaturePermanent("another creature (gets three -1/-1 counters)");
filter3.add(new AnotherTargetPredicate(3));
TargetCreaturePermanent target3 = new TargetCreaturePermanent(filter3);
target3.setTargetTag(3);
this.getSpellAbility().addTarget(target3);
}
public IncrementalBlight(final IncrementalBlight card) {
@ -85,9 +102,9 @@ class IncrementalBlightEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
int i = 0;
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
for (Target target : source.getTargets()) {
i++;
Permanent creature = game.getPermanent(targetId);
Permanent creature = game.getPermanent(target.getFirstTarget());
if (creature != null) {
creature.addCounters(CounterType.M1M1.createInstance(i), game);
}

View file

@ -124,7 +124,7 @@ class RealmRazerEffect extends OneShotEffect {
if (controller != null) {
ExileZone exZone = game.getExile().getExileZone(source.getSourceId());
if (exZone != null) {
return controller.moveCards(exZone.getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null);
return controller.moveCards(exZone.getCards(game), Zone.BATTLEFIELD, source, game, true, false, true, null);
}
return true;
}

View file

@ -0,0 +1,52 @@
/*
* 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.unlimitededition;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class LibraryOfLeng extends mage.sets.limitedbeta.LibraryOfLeng {
public LibraryOfLeng(UUID ownerId) {
super(ownerId);
this.cardNumber = 258;
this.expansionSetCode = "2ED";
}
public LibraryOfLeng(final LibraryOfLeng card) {
super(card);
}
@Override
public LibraryOfLeng copy() {
return new LibraryOfLeng(this);
}
}

View file

@ -37,6 +37,8 @@ import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.constants.Duration;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherTargetPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent;
@ -54,10 +56,18 @@ public class FeralContest extends CardImpl {
// Put a +1/+1 counter on target creature you control.
this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
TargetControlledCreaturePermanent target1 = new TargetControlledCreaturePermanent();
target1.setTargetTag(1);
this.getSpellAbility().addTarget(target1);
// Another target creature blocks it this turn if able.
this.getSpellAbility().addEffect(new FeralContestEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature (must block this turn)");
filter.add(new AnotherTargetPredicate(2));
TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter);
target2.setTargetTag(2);
this.getSpellAbility().addTarget(target2);
}
public FeralContest(final FeralContest card) {

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<groupId>org.mage</groupId>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage-tests</artifactId>

View file

@ -2077,4 +2077,8 @@ public class TestPlayer implements Player {
return AIPlayer;
}
public String getHistory() {
return computerPlayer.getHistory();
}
}

View file

@ -1,9 +1,19 @@
package org.mage.test.serverside.base;
import mage.constants.PhaseStep;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import mage.cards.Card;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.game.Game;
@ -25,19 +35,13 @@ import org.junit.BeforeClass;
import org.mage.test.player.RandomPlayer;
import org.mage.test.player.TestPlayer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Base class for all tests.
*
* @author ayratn
*/
public abstract class MageTestBase {
protected static Logger logger = Logger.getLogger(MageTestBase.class);
public static PluginClassLoader classLoader = new PluginClassLoader();
@ -46,17 +50,17 @@ public abstract class MageTestBase {
protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)(:\\{tapped\\})?");
protected List<Card> handCardsA = new ArrayList<Card>();
protected List<Card> handCardsB = new ArrayList<Card>();
protected List<PermanentCard> battlefieldCardsA = new ArrayList<PermanentCard>();
protected List<PermanentCard> battlefieldCardsB = new ArrayList<PermanentCard>();
protected List<Card> graveyardCardsA = new ArrayList<Card>();
protected List<Card> graveyardCardsB = new ArrayList<Card>();
protected List<Card> libraryCardsA = new ArrayList<Card>();
protected List<Card> libraryCardsB = new ArrayList<Card>();
protected List<Card> handCardsA = new ArrayList<>();
protected List<Card> handCardsB = new ArrayList<>();
protected List<PermanentCard> battlefieldCardsA = new ArrayList<>();
protected List<PermanentCard> battlefieldCardsB = new ArrayList<>();
protected List<Card> graveyardCardsA = new ArrayList<>();
protected List<Card> graveyardCardsB = new ArrayList<>();
protected List<Card> libraryCardsA = new ArrayList<>();
protected List<Card> libraryCardsB = new ArrayList<>();
protected Map<Zone, String> commandsA = new HashMap<Zone, String>();
protected Map<Zone, String> commandsB = new HashMap<Zone, String>();
protected Map<Zone, String> commandsA = new HashMap<>();
protected Map<Zone, String> commandsB = new HashMap<>();
protected TestPlayer playerA;
protected TestPlayer playerB;
@ -67,8 +71,7 @@ public abstract class MageTestBase {
protected static Game currentGame = null;
/**
* Player thats starts the game first.
* By default, it is ComputerA.
* Player thats starts the game first. By default, it is ComputerA.
*/
protected static Player activePlayer = null;
@ -77,6 +80,7 @@ public abstract class MageTestBase {
protected PhaseStep stopAtStep = PhaseStep.UNTAP;
protected enum ParserState {
INIT,
OPTIONS,
EXPECTED
@ -85,18 +89,13 @@ public abstract class MageTestBase {
protected ParserState parserState;
/**
* Expected results of the test.
* Read from test case in {@link String} based format:
* Expected results of the test. Read from test case in {@link String} based
* format:
* <p/>
* Example:
* turn:1
* result:won:ComputerA
* life:ComputerA:20
* life:ComputerB:0
* battlefield:ComputerB:Tine Shrike:0
* graveyard:ComputerB:Tine Shrike:1
* Example: turn:1 result:won:ComputerA life:ComputerA:20 life:ComputerB:0
* battlefield:ComputerB:Tine Shrike:0 graveyard:ComputerB:Tine Shrike:1
*/
protected List<String> expectedResults = new ArrayList<String>();
protected List<String> expectedResults = new ArrayList<>();
protected static final String TESTS_PATH = "tests" + File.separator;
@ -163,8 +162,9 @@ public abstract class MageTestBase {
private static void deleteSavedGames() {
File directory = new File("saved/");
if (!directory.exists())
if (!directory.exists()) {
directory.mkdirs();
}
File[] files = directory.listFiles(
new FilenameFilter() {
@Override
@ -185,7 +185,9 @@ public abstract class MageTestBase {
try {
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line == null || line.isEmpty() || line.startsWith("#")) continue;
if (line == null || line.isEmpty() || line.startsWith("#")) {
continue;
}
if (line.startsWith("$include")) {
includeFrom(line);
continue;

View file

@ -1237,4 +1237,10 @@ public class PlayerStub implements Player {
public boolean addTargets(Ability ability, Game game) {
return false;
}
@Override
public String getHistory() {
return "";
}
}

View file

@ -5,7 +5,7 @@
<parent>
<artifactId>mage-root</artifactId>
<groupId>org.mage</groupId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
</parent>
<artifactId>mage</artifactId>

View file

@ -63,7 +63,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 43;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 44;
private static final long CARD_CONTENT_VERSION = 45;
private final Random random = new Random();
private Dao<CardInfo, Object> cardDao;

View file

@ -24,8 +24,7 @@
* 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.game;
import java.io.Serializable;
@ -38,7 +37,6 @@ import mage.players.Player;
public class Seat implements Serializable {
// private static final Logger logger = Logger.getLogger(Seat.class);
private String playerType;
private Player player;

View file

@ -91,6 +91,7 @@ public class GameEvent implements Serializable {
DRAW_CARD, DREW_CARD,
MIRACLE_CARD_REVEALED,
MADNESS_CARD_EXILED,
DISCARD_CARD,
DISCARDED_CARD,
CYCLE_CARD, CYCLED_CARD,
CLASH, CLASHED,

View file

@ -824,4 +824,6 @@ public interface Player extends MageItem, Copyable<Player> {
* @return
*/
boolean addTargets(Ability ability, Game game);
String getHistory();
}

View file

@ -92,6 +92,14 @@ import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.PlayerAction;
import static mage.constants.PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS;
import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_MY_NEXT_TURN;
import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_NEXT_MAIN_PHASE;
import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_NEXT_TURN;
import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_STACK_RESOLVED;
import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_TURN_END_STEP;
import static mage.constants.PlayerAction.PERMISSION_REQUESTS_ALLOWED_OFF;
import static mage.constants.PlayerAction.PERMISSION_REQUESTS_ALLOWED_ON;
import mage.constants.RangeOfInfluence;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
@ -516,31 +524,29 @@ public abstract class PlayerImpl implements Player, Serializable {
inRange.add(player.getId());
}
}
} else if ((range.getRange() * 2) + 1 >= game.getPlayers().size()) {
for (Player player : game.getPlayers().values()) {
if (!player.hasLeft()) {
inRange.add(player.getId());
}
}
} else {
if ((range.getRange() * 2) + 1 >= game.getPlayers().size()) {
for (Player player : game.getPlayers().values()) {
if (!player.hasLeft()) {
inRange.add(player.getId());
}
inRange.add(playerId);
PlayerList players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getNext(game);
while (player.hasLeft()) {
player = players.getNext(game);
}
} else {
inRange.add(playerId);
PlayerList players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getNext(game);
while (player.hasLeft()) {
player = players.getNext(game);
}
inRange.add(player.getId());
}
players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getPrevious(game);
while (player.hasLeft()) {
player = players.getPrevious(game);
}
inRange.add(player.getId());
inRange.add(player.getId());
}
players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getPrevious(game);
while (player.hasLeft()) {
player = players.getPrevious(game);
}
inRange.add(player.getId());
}
}
}
@ -760,7 +766,8 @@ public abstract class PlayerImpl implements Player, Serializable {
about the discarded card, that cost payment is illegal; the game returns to
the moment before the cost was paid (see rule 717, "Handling Illegal Actions").
*/
if (card != null) {
if (card != null
&& !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null ? null : source.getSourceId(), playerId), source)) {
// write info to game log first so game log infos from triggered or replacement effects follow in the game log
if (!game.isSimulation()) {
game.informPlayers(getLogName() + " discards " + card.getLogName());
@ -2089,7 +2096,8 @@ public abstract class PlayerImpl implements Player, Serializable {
break;
}
}
if (!opponentInGame || // if no more opponent is in game the wins event may no longer be replaced
if (!opponentInGame
|| // if no more opponent is in game the wins event may no longer be replaced
!game.replaceEvent(new GameEvent(GameEvent.EventType.WINS, null, null, playerId))) {
logger.debug("player won -> start: " + this.getName());
if (!this.loses) {
@ -2169,10 +2177,8 @@ public abstract class PlayerImpl implements Player, Serializable {
if (blocker != null && group != null && group.canBlock(blocker, game)) {
group.addBlocker(blockerId, playerId, game);
game.getCombat().addBlockingGroup(blockerId, attackerId, playerId, game);
} else {
if (this.isHuman() && !game.isSimulation()) {
game.informPlayer(this, "You can't block this creature.");
}
} else if (this.isHuman() && !game.isSimulation()) {
game.informPlayer(this, "You can't block this creature.");
}
}
@ -2793,14 +2799,12 @@ public abstract class PlayerImpl implements Player, Serializable {
}
if (targetNum < option.getTargets().size() - 2) {
addTargetOptions(options, newOption, targetNum + 1, game);
} else if (option.getChoices().size() > 0) {
addChoiceOptions(options, newOption, 0, game);
} else if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
if (option.getChoices().size() > 0) {
addChoiceOptions(options, newOption, 0, game);
} else if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
}
options.add(newOption);
}
}
}
@ -2811,12 +2815,10 @@ public abstract class PlayerImpl implements Player, Serializable {
newOption.getChoices().get(choiceNum).setChoice(choice);
if (choiceNum < option.getChoices().size() - 1) {
addChoiceOptions(options, newOption, choiceNum + 1, game);
} else if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
if (option.getCosts().getTargets().size() > 0) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
}
options.add(newOption);
}
}
}
@ -3503,4 +3505,9 @@ public abstract class PlayerImpl implements Player, Serializable {
return true;
}
@Override
public String getHistory() {
return "no available";
}
}

View file

@ -23,6 +23,8 @@ public class UserData implements Serializable {
protected boolean passPriorityActivation;
protected boolean autoOrderTrigger;
protected String history;
public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced,
boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps,
String flagName, boolean askMoveToGraveOrder, boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted,
@ -40,6 +42,7 @@ public class UserData implements Serializable {
this.passPriorityCast = passPriorityCast;
this.passPriorityActivation = passPriorityActivation;
this.autoOrderTrigger = autoOrderTrigger;
this.history = "";
}
public void update(UserData userData) {
@ -166,7 +169,16 @@ public class UserData implements Serializable {
this.autoOrderTrigger = autoOrderTrigger;
}
public void setHistory(String history) {
this.history = history;
}
public String getHistory() {
return history;
}
public static String getDefaultFlagName() {
return "world.png";
}
}

View file

@ -54,6 +54,9 @@ git log 2ad15bbd48d5ae34b0cb5d709895d406b977d104..head --diff-filter=A --name-st
since 1.4.7v0
git log 8b37d0b989ba19f0dfccc81db66f5a21cc71fb94..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt
since 1.4.8v0
git log 804f9e7fc2b481f7f784943409f558a671088372..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt
3. Copy added_cards.txt to trunk\Utils folder
4. Run script:
> perl extract_in_wiki_format.perl

View file

@ -1,141 +0,0 @@
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (malloc) failed to allocate 1048576 bytes for AllocateHeap
# Possible reasons:
# The system is out of physical RAM or swap space
# In 32 bit mode, the process size limit was hit
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Use 64 bit Java on a 64 bit OS
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
# Out of Memory Error (memory/allocation.inline.hpp:61), pid=4700, tid=4912
#
# JRE version: (7.0_71-b14) (build )
# Java VM: Java HotSpot(TM) 64-Bit Server VM (24.71-b01 mixed mode windows-amd64 compressed oops)
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
--------------- T H R E A D ---------------
Current thread (0x000000000223f000): JavaThread "Unknown thread" [_thread_in_vm, id=4912, stack(0x00000000025b0000,0x00000000026b0000)]
Stack: [0x00000000025b0000,0x00000000026b0000]
[error occurred during error reporting (printing stack bounds), id 0xc0000005]
--------------- P R O C E S S ---------------
Java Threads: ( => current thread )
Other Threads:
=>0x000000000223f000 (exited) JavaThread "Unknown thread" [_thread_in_vm, id=4912, stack(0x00000000025b0000,0x00000000026b0000)]
VM state:not at safepoint (normal execution)
VM Mutex/Monitor currently owned by a thread: None
Heap
PSYoungGen total 612352K, used 10506K [0x00000007d5500000, 0x0000000800000000, 0x0000000800000000)
eden space 525312K, 2% used [0x00000007d5500000,0x00000007d5f42948,0x00000007f5600000)
from space 87040K, 0% used [0x00000007fab00000,0x00000007fab00000,0x0000000800000000)
to space 87040K, 0% used [0x00000007f5600000,0x00000007f5600000,0x00000007fab00000)
ParOldGen total 1398272K, used 0K [0x000000077ff80000, 0x00000007d5500000, 0x00000007d5500000)
object space 1398272K, 0% used [0x000000077ff80000,0x000000077ff80000,0x00000007d5500000)
PSPermGen total 21504K, used 694K [0x000000077ad80000, 0x000000077c280000, 0x000000077ff80000)
object space 21504K, 3% used [0x000000077ad80000,0x000000077ae2d9f8,0x000000077c280000)
Card table byte_map: [0x00000000056b0000,0x0000000005ae0000] byte_map_base: 0x0000000001ad9400
Polling page: 0x0000000000240000
Code Cache [0x00000000026b0000, 0x0000000002920000, 0x00000000056b0000)
total_blobs=37 nmethods=0 adapters=20 free_code_cache=48890Kb largest_free_block=50063104
Compilation events (0 events):
No events
GC Heap History (0 events):
No events
Deoptimization events (0 events):
No events
Internal exceptions (0 events):
No events
Events (10 events):
Event: 0.029 loading class 0x00000000022bed40
Event: 0.029 loading class 0x00000000022bed40 done
Event: 0.029 loading class 0x00000000022bed90
Event: 0.029 loading class 0x00000000022bed90 done
Event: 0.029 loading class 0x00000000022bede0
Event: 0.029 loading class 0x00000000022bede0 done
Event: 0.030 loading class 0x00000000022bfb40
Event: 0.030 loading class 0x00000000022bfb40 done
Event: 0.030 loading class 0x00000000022bf760
Event: 0.030 loading class 0x00000000022bf760 done
Dynamic libraries:
0x000000013f320000 - 0x000000013f353000 C:\Program Files\Java\jdk1.7.0_71\bin\java.exe
0x00000000770c0000 - 0x0000000077269000 C:\Windows\SYSTEM32\ntdll.dll
0x0000000076ea0000 - 0x0000000076fbf000 C:\Windows\system32\kernel32.dll
0x000007fefd170000 - 0x000007fefd1dc000 C:\Windows\system32\KERNELBASE.dll
0x000007fefd7c0000 - 0x000007fefd89b000 C:\Windows\system32\ADVAPI32.dll
0x000007feff300000 - 0x000007feff39f000 C:\Windows\system32\msvcrt.dll
0x000007fefe7e0000 - 0x000007fefe7ff000 C:\Windows\SYSTEM32\sechost.dll
0x000007fefd8a0000 - 0x000007fefd9cd000 C:\Windows\system32\RPCRT4.dll
0x0000000076fc0000 - 0x00000000770ba000 C:\Windows\system32\USER32.dll
0x000007fefd230000 - 0x000007fefd297000 C:\Windows\system32\GDI32.dll
0x000007fefef50000 - 0x000007fefef5e000 C:\Windows\system32\LPK.dll
0x000007feff230000 - 0x000007feff2f9000 C:\Windows\system32\USP10.dll
0x000007fefb490000 - 0x000007fefb684000 C:\Windows\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.18837_none_fa3b1e3d17594757\COMCTL32.dll
0x000007fefe760000 - 0x000007fefe7d1000 C:\Windows\system32\SHLWAPI.dll
0x000007feff3a0000 - 0x000007feff3ce000 C:\Windows\system32\IMM32.DLL
0x000007fefe9f0000 - 0x000007fefeaf9000 C:\Windows\system32\MSCTF.dll
0x000000006ab40000 - 0x000000006ac12000 C:\Program Files\Java\jdk1.7.0_71\jre\bin\msvcr100.dll
0x0000000067b10000 - 0x00000000682e3000 C:\Program Files\Java\jdk1.7.0_71\jre\bin\server\jvm.dll
0x000007fefa970000 - 0x000007fefa979000 C:\Windows\system32\WSOCK32.dll
0x000007fefd690000 - 0x000007fefd6dd000 C:\Windows\system32\WS2_32.dll
0x000007fefe9e0000 - 0x000007fefe9e8000 C:\Windows\system32\NSI.dll
0x000007fefaf60000 - 0x000007fefaf9b000 C:\Windows\system32\WINMM.dll
0x0000000077290000 - 0x0000000077297000 C:\Windows\system32\PSAPI.DLL
0x000000006f510000 - 0x000000006f51f000 C:\Program Files\Java\jdk1.7.0_71\jre\bin\verify.dll
0x000000006c820000 - 0x000000006c848000 C:\Program Files\Java\jdk1.7.0_71\jre\bin\java.dll
0x000000006c860000 - 0x000000006c875000 C:\Program Files\Java\jdk1.7.0_71\jre\bin\zip.dll
VM Arguments:
jvm_args: -Xms2048m -Xmx2048m -Dclassworlds.conf=C:\Program Files\NetBeans 8.0.2\java\maven\bin\m2.conf -Dmaven.home=C:\Program Files\NetBeans 8.0.2\java\maven
java_command: org.codehaus.plexus.classworlds.launcher.Launcher -Dmaven.ext.class.path=C:\Users\fireshoes\AppData\Roaming\NetBeans\8.0.2\maven-nblib\netbeans-eventspy.jar -Dfile.encoding=UTF-8 clean install
Launcher Type: SUN_STANDARD
Environment Variables:
JAVA_HOME=C:\Program Files\Java\jdk1.7.0_71
PATH=c:\Program FIles\Apache Software Foundation\apache-maven-3.2.3\bin;C:\ProgramData\Oracle\Java\javapath;C:\Program Files (x86)\AMD APP\bin\x86_64;C:\Program Files (x86)\AMD APP\bin\x86;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Windows Live\Shared;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\Program FIles\Java\jdk1.7.0_71\bin;C:\Strawberry\c\bin;C:\Strawberry\perl\site\bin;C:\Strawberry\perl\bin;C:\Program Files (x86)\Git\cmd;C:\apache-maven-3.3.9-src\apache-maven-3.3.9\apache-maven\src
USERNAME=fireshoes
OS=Windows_NT
PROCESSOR_IDENTIFIER=AMD64 Family 18 Model 1 Stepping 0, AuthenticAMD
--------------- S Y S T E M ---------------
OS: Windows 7 , 64 bit Build 7601 Service Pack 1
CPU:total 4 (4 cores per cpu, 1 threads per core) family 18 model 1 stepping 0, cmov, cx8, fxsr, mmx, sse, sse2, sse3, popcnt, mmxext, 3dnowpref, lzcnt, sse4a, tsc, tscinvbit, tscinv
Memory: 4k page, physical 3559120k(796500k free), swap 7967852k(857396k free)
vm_info: Java HotSpot(TM) 64-Bit Server VM (24.71-b01) for windows-amd64 JRE (1.7.0_71-b14), built on Sep 26 2014 16:16:12 by "java_re" with unknown MS VC++:1600
time: Wed Jan 20 09:21:37 2016
elapsed time: 0 seconds

View file

@ -6,7 +6,7 @@
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.7</version>
<version>1.4.8</version>
<packaging>pom</packaging>
<name>Mage Root</name>
<description>Mage Root POM</description>
@ -83,7 +83,7 @@
</repositories>
<properties>
<mage-version>1.4.7</mage-version>
<mage-version>1.4.8</mage-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>