diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 8fa1cdebb40..ec8b0a61d9e 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -1022,8 +1022,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { // Show the tables pane if there wasn't already an active pane MagePane topPanebefore = getTopMost(tablesPane); - if (topPanebefore == null) { - setActive(tablesPane); + setActive(tablesPane); + if (topPanebefore != null && topPanebefore != tablesPane) { + setActive(topPanebefore); } } @@ -1189,7 +1190,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static final long serialVersionUID = -9104885239063142218L; private ImagePanel backgroundPane; - private TablesPane tablesPane; + private final TablesPane tablesPane; // private CollectionViewerPane collectionViewerPane; public void setStatusText(String status) { @@ -1319,7 +1320,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { DownloadPictures.startDownload(null, missingCards); break; case CLIENT_DISCONNECT: - SessionHandler.disconnect(false); + if (SessionHandler.isConnected()) { + endTables(); + SessionHandler.disconnect(false); + } tablesPane.clearChat(); showMessage("You have disconnected"); setWindowTitle(); @@ -1347,6 +1351,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { break; case CLIENT_EXIT: if (SessionHandler.isConnected()) { + endTables(); SessionHandler.disconnect(false); } CardRepository.instance.closeDB(); @@ -1374,6 +1379,15 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } } + private void endTables() { + for (UUID gameId : GAMES.keySet()) { + SessionHandler.quitMatch(gameId); + } + for (UUID draftId : DRAFTS.keySet()) { + SessionHandler.quitDraft(draftId); + } + } + public void changeGUISize() { ImageCaches.flush(); setGUISize(); diff --git a/Mage.Common/src/main/java/mage/view/UserView.java b/Mage.Common/src/main/java/mage/view/UserView.java index 1b4a9d082a2..73a8057075d 100644 --- a/Mage.Common/src/main/java/mage/view/UserView.java +++ b/Mage.Common/src/main/java/mage/view/UserView.java @@ -41,6 +41,7 @@ public class UserView implements Serializable { private final String host; private final String sessionId; private final Date timeConnected; + private final Date lastActivity; private final String gameInfo; private final String userState; private final Date muteChatUntil; @@ -48,11 +49,12 @@ public class UserView implements Serializable { private final String email; private final String userIdStr; - public UserView(String userName, String host, String sessionId, Date timeConnected, String gameInfo, String userState, Date muteChatUntil, String clientVersion, String email, String userIdStr) { + public UserView(String userName, String host, String sessionId, Date timeConnected, Date lastActivity, String gameInfo, String userState, Date muteChatUntil, String clientVersion, String email, String userIdStr) { this.userName = userName; this.host = host; this.sessionId = sessionId; this.timeConnected = timeConnected; + this.lastActivity = lastActivity; this.gameInfo = gameInfo; this.userState = userState; this.muteChatUntil = muteChatUntil; @@ -93,6 +95,10 @@ public class UserView implements Serializable { return timeConnected; } + public Date getLastActivity() { + return lastActivity; + } + public String getEmail() { return email; } diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java index 072e5673e97..b2c9768a2bc 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java @@ -33,22 +33,20 @@ */ package mage.server.console; -import mage.remote.Session; -import mage.view.TableView; -import mage.view.UserView; -import org.apache.log4j.Logger; - -import javax.swing.*; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableRowSorter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; - +import javax.swing.*; import static javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN; import static javax.swing.JTable.AUTO_RESIZE_OFF; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableRowSorter; +import mage.remote.Session; +import mage.view.TableView; +import mage.view.UserView; +import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com @@ -359,13 +357,14 @@ class TableUserModel extends AbstractTableModel { public static final int POS_USER_NAME = 0; public static final int POS_HOST = 1; public static final int POS_TIME_CONNECTED = 2; - public static final int POS_SESSION_ID = 3; - public static final int POS_GAME_INFO = 4; - public static final int POS_USER_STATE = 5; - public static final int POS_CHAT_MUTE = 6; - public static final int POS_CLIENT_VERSION = 7; + public static final int POS_LAST_ACTIVITY = 3; + public static final int POS_SESSION_ID = 4; + public static final int POS_GAME_INFO = 5; + public static final int POS_USER_STATE = 6; + public static final int POS_CHAT_MUTE = 7; + public static final int POS_CLIENT_VERSION = 8; - private final String[] columnNames = new String[]{"User Name", "Host", "Time Connected", "SessionId", "Gameinfo", "User state", "Chat mute", "Client Version"}; + private final String[] columnNames = new String[]{"User Name", "Host", "Time Connected", "Last activity", "SessionId", "Gameinfo", "User state", "Chat mute", "Client Version"}; private UserView[] users = new UserView[0]; private static final DateFormat formatterTime = new SimpleDateFormat("HH:mm:ss"); private static final DateFormat formatterTimeStamp = new SimpleDateFormat("yy-M-dd HH:mm:ss"); @@ -394,6 +393,8 @@ class TableUserModel extends AbstractTableModel { return users[arg0].getHost(); case POS_TIME_CONNECTED: return formatterTime.format(users[arg0].getTimeConnected()); + case POS_LAST_ACTIVITY: + return formatterTime.format(users[arg0].getLastActivity()); case POS_SESSION_ID: return users[arg0].getSessionId(); case POS_GAME_INFO: @@ -544,10 +545,8 @@ class UpdateUsersTask extends SwingWorker> { return true; } for (UserView u1 : previousUsers) { - boolean found = false; for (UserView u2 : usersToCheck) { if (u1.getUserName().equals(u2.getUserName())) { - found = true; String s = u1.getUserName() + ',' + u1.getHost(); if (peopleIps.get(s) == null) { logger.warn("Found new user: " + u1.getUserName() + ',' + u1.getHost()); @@ -561,13 +560,9 @@ class UpdateUsersTask extends SwingWorker> { break; } } - if (!found) { - // some new user replaced old one - return true; - } } // seems nothing has been changed - return false; + return true; } @Override diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index f2ae1f3e4a6..61efe2a6f6a 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -43,7 +43,7 @@ leasePeriod="5000" socketWriteTimeout="10000" maxGameThreads="10" - maxSecondsIdle="600" + maxSecondsIdle="300" minUserNameLength="3" maxUserNameLength="14" invalidUserNamePattern="[^a-z0-9_]" diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index e207bce0d74..a2f346307b1 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -1271,22 +1271,7 @@ public class MageServerImpl implements MageServer { @Override public List execute() throws MageException { - List users = new ArrayList<>(); - for (User user : UserManager.instance.getUsers()) { - users.add(new UserView( - user.getName(), - user.getHost(), - user.getSessionId(), - user.getConnectionTime(), - user.getGameInfo(), - user.getUserState().toString(), - user.getChatLockedUntil(), - user.getClientVersion(), - user.getEmail(), - user.getUserIdStr() - )); - } - return users; + return UserManager.instance.getUserInfoList(); } } diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index e06a5613358..9ffa4121813 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -38,6 +38,7 @@ import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.players.net.UserData; import mage.players.net.UserGroup; +import static mage.server.DisconnectReason.LostConnection; import mage.server.game.GamesRoom; import mage.server.game.GamesRoomManager; import mage.server.util.ConfigSettings; @@ -270,12 +271,13 @@ public class Session { this.isAdmin = true; User user = UserManager.instance.createUser("Admin", host, null).orElse( UserManager.instance.getUserByName("Admin").get()); - UserData adminUserData = UserData.getDefaultUserDataView(); adminUserData.setGroupId(UserGroup.ADMIN.getGroupId()); user.setUserData(adminUserData); if (!UserManager.instance.connectToSession(sessionId, user.getId())) { logger.info("Error connecting Admin!"); + } else { + user.setUserState(User.UserState.Connected); } this.userId = user.getId(); } @@ -329,39 +331,20 @@ public class Session { // because different threads can activate this public void userLostConnection() { - boolean lockSet = false; - try { - if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) { - lockSet = true; - logger.debug("SESSION LOCK SET sessionId: " + sessionId); - } else { - logger.warn("CAN'T GET LOCK - userId: " + userId + " hold count: " + lock.getHoldCount()); - } - Optional _user = UserManager.instance.getUser(userId); - if (!_user.isPresent()) { - return; //user was already disconnected by other thread - } - User user = _user.get(); - if (!user.isConnected()) { - return; - } - if (!user.getSessionId().equals(sessionId)) { - // user already reconnected with another instance - logger.info("OLD SESSION IGNORED - " + user.getName()); - return; - } - // logger.info("LOST CONNECTION - " + user.getName() + " id: " + userId); - UserManager.instance.disconnect(userId, DisconnectReason.LostConnection); - - } catch (InterruptedException ex) { - logger.error("SESSION LOCK lost connection - userId: " + userId, ex); - } finally { - if (lockSet) { - lock.unlock(); - logger.trace("SESSION LOCK UNLOCK sessionId: " + sessionId); - } + Optional _user = UserManager.instance.getUser(userId); + if (!_user.isPresent()) { + return; //user was already disconnected by other thread + } + User user = _user.get(); + if (!user.isConnected()) { + return; + } + if (!user.getSessionId().equals(sessionId)) { + // user already reconnected with another instance + logger.info("OLD SESSION IGNORED - " + user.getName()); + } else { + // logger.info("LOST CONNECTION - " + user.getName() + " id: " + userId); } - } public void kill(DisconnectReason reason) { @@ -397,7 +380,7 @@ public class Session { logger.warn(" - method: " + call.getMethod()); logger.warn(" - cause: " + getBasicCause(ex).toString()); logger.trace("Stack trace:", ex); - userLostConnection(); + SessionManager.instance.disconnect(sessionId, LostConnection); }); } } diff --git a/Mage.Server/src/main/java/mage/server/SessionManager.java b/Mage.Server/src/main/java/mage/server/SessionManager.java index 1b66fce0c13..e32be438972 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/SessionManager.java @@ -125,34 +125,30 @@ public enum SessionManager { public void disconnect(String sessionId, DisconnectReason reason) { Session session = sessions.get(sessionId); if (session != null) { - if (reason != DisconnectReason.AdminDisconnect) { - if (!sessions.containsKey(sessionId)) { - // session was removed meanwhile by another thread so we can return - return; - } - logger.debug("DISCONNECT " + reason.toString() + " - sessionId: " + sessionId); - sessions.remove(sessionId); - switch (reason) { - case Disconnected: // regular session end or wrong client version - if (session.getUserId() != null) { // if wrong client version no userId is set - session.kill(reason); - } - break; - case SessionExpired: // session ends after no reconnect happens in the defined time span - session.kill(reason); - break; - case LostConnection: // user lost connection - session expires countdaoun starts - session.userLostConnection(); - break; - case ConnectingOtherInstance: - break; - default: - logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); - } - } else { - sessions.remove(sessionId); - session.kill(reason); + if (!sessions.containsKey(sessionId)) { + // session was removed meanwhile by another thread so we can return + return; } + logger.debug("DISCONNECT " + reason.toString() + " - sessionId: " + sessionId); + sessions.remove(sessionId); + switch (reason) { + case AdminDisconnect: + session.kill(reason); + break; + case ConnectingOtherInstance: + case Disconnected: // regular session end or wrong client version + UserManager.instance.disconnect(session.getUserId(), reason); + break; + case SessionExpired: // session ends after no reconnect happens in the defined time span + break; + case LostConnection: // user lost connection - session expires countdown starts + session.userLostConnection(); + UserManager.instance.disconnect(session.getUserId(), reason); + break; + default: + logger.trace("endSession: unexpected reason " + reason.toString() + " - sessionId: " + sessionId); + } + } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 388d85bd22e..523a8926714 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -64,7 +64,10 @@ public class User { public enum UserState { - Created, Connected, Disconnected, Reconnected, Expired + Created, // Used if user is created an not connected to the session + Connected, // Used if user is correctly connected + Disconnected, // Used if the user lost connection + Offline // set if the user was disconnected and expired or regularly left XMage. Removed is the user later after some time } private final UUID userId; @@ -164,7 +167,7 @@ public class User { userState = UserState.Connected; logger.trace("USER - created: " + userName + " id: " + userId); } else { - userState = UserState.Reconnected; + userState = UserState.Connected; reconnect(); logger.trace("USER - reconnected: " + userName + " id: " + userId); } @@ -212,23 +215,14 @@ public class User { } public boolean isConnected() { - return userState == UserState.Connected || userState == UserState.Reconnected; + return userState == UserState.Connected; } public String getDisconnectDuration() { long secondsDisconnected = getSecondsDisconnected(); - long secondsLeft; - String sign = ""; - if (secondsDisconnected > (3 * 60)) { - sign = "-"; - secondsLeft = secondsDisconnected - (3 * 60); - } else { - secondsLeft = (3 * 60) - secondsDisconnected; - } - - int minutes = (int) secondsLeft / 60; - int seconds = (int) secondsLeft % 60; - return new StringBuilder(sign).append(Integer.toString(minutes)).append(':').append(seconds > 9 ? seconds : '0' + Integer.toString(seconds)).toString(); + int minutes = (int) secondsDisconnected / 60; + int seconds = (int) secondsDisconnected % 60; + return Integer.toString(minutes) + ':' + (seconds > 9 ? seconds : '0' + Integer.toString(seconds)); } public long getSecondsDisconnected() { @@ -239,6 +233,20 @@ public class User { return connectionTime; } + public Date getLastActivity() { + return lastActivity; + } + + public String getConnectionDuration() { + int minutes = (int) SystemUtil.getDateDiff(connectionTime, new Date(), TimeUnit.SECONDS) / 60; + int hours = 0; + if (minutes > 59) { + hours = (int) minutes / 60; + minutes = minutes - (hours * 60); + } + return Integer.toString(hours) + ":" + (minutes > 9 ? Integer.toString(minutes) : '0' + Integer.toString(minutes)); + } + public void fireCallback(final ClientCallback call) { if (isConnected()) { SessionManager.instance.getSession(sessionId).ifPresent(session @@ -331,19 +339,17 @@ public class User { } lastActivity = new Date(); if (userState == UserState.Disconnected) { // this can happen if user reconnects very fast after disconnect - userState = UserState.Reconnected; + userState = UserState.Connected; } } public boolean isExpired(Date expired) { if (lastActivity.before(expired)) { logger.trace(userName + " is expired!"); - userState = UserState.Expired; return true; } logger.trace("isExpired: User " + userName + " lastActivity: " + lastActivity + " expired: " + expired); return false; - /*userState == UserState.Disconnected && */ } @@ -511,11 +517,15 @@ public class User { tournament++; break; } - - if (!isConnected()) { - tournamentPlayer.setDisconnectInfo(" (discon. " + getDisconnectDuration() + ')'); - } else { - tournamentPlayer.setDisconnectInfo(""); + switch (getUserState()) { + case Disconnected: + tournamentPlayer.setDisconnectInfo(" (discon. " + getDisconnectDuration() + ')'); + break; + case Offline: + tournamentPlayer.setDisconnectInfo(" Offline"); + break; + default: + tournamentPlayer.setDisconnectInfo(""); } } } else { @@ -586,11 +596,18 @@ public class User { return userState; } + public void setUserState(UserState userState) { + this.userState = userState; + } + public String getPingInfo() { - if (isConnected()) { - return pingInfo; - } else { - return " (discon. " + getDisconnectDuration() + ')'; + switch (getUserState()) { + case Disconnected: + return " (discon. " + getDisconnectDuration() + ')'; + case Offline: + return " Offline"; + default: + return pingInfo + " " + getConnectionDuration(); } } diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index f33b3fe0544..0379684bd11 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -33,6 +33,7 @@ import mage.server.User.UserState; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.util.ThreadExecutor; +import mage.view.UserView; import org.apache.log4j.Logger; /** @@ -44,7 +45,12 @@ import org.apache.log4j.Logger; public enum UserManager { instance; + private static final Logger logger = Logger.getLogger(UserManager.class); + protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); + protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor(); + + private List userInfoList = new ArrayList<>(); private static final Logger LOGGER = Logger.getLogger(UserManager.class); @@ -54,6 +60,8 @@ public enum UserManager { UserManager() { expireExecutor.scheduleAtFixedRate(this::checkExpired, 60, 60, TimeUnit.SECONDS); + + userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, 4, 4, TimeUnit.SECONDS); } public Optional createUser(String userName, String host, AuthorizedUser authorizedUser) { @@ -100,10 +108,16 @@ public enum UserManager { } public void disconnect(UUID userId, DisconnectReason reason) { - if (userId != null) { - getUser(userId).ifPresent(user -> user.setSessionId(""));// Session will be set again with new id if user reconnects + Optional user = UserManager.instance.getUser(userId); + if (user.isPresent()) { + user.get().setSessionId(""); + if (reason == DisconnectReason.Disconnected) { + user.get().setUserState(UserState.Offline); + } + } + if (userId != null) { + ChatManager.instance.removeUser(userId, reason); } - ChatManager.instance.removeUser(userId, reason); } public boolean isAdmin(UUID userId) { @@ -148,18 +162,57 @@ public enum 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 set to + * offline status. The user will be removed in validity check after 15 + * minutes of no activities + * */ private void checkExpired() { - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.MINUTE, -3); - List usersToCheck = new ArrayList<>(users.values()); - for (User user : usersToCheck) { - if (user.getUserState() != UserState.Expired && user.isExpired(calendar.getTime())) { - removeUser(user.getId(), DisconnectReason.SessionExpired); + Calendar calendarExp = Calendar.getInstance(); + calendarExp.add(Calendar.MINUTE, -3); + Calendar calendarRemove = Calendar.getInstance(); + calendarRemove.add(Calendar.MINUTE, -8); + List toRemove = new ArrayList<>(); + for (User user : users.values()) { + if (user.getUserState() == UserState.Disconnected || user.getUserState() == UserState.Offline + && user.isExpired(calendarExp.getTime())) { + user.setUserState(UserState.Offline); + } + if (user.getUserState() == UserState.Offline && user.isExpired(calendarRemove.getTime())) { + toRemove.add(user); } } + for (User user : toRemove) { + removeUser(user.getId(), DisconnectReason.SessionExpired); + } + } + + /** + * This method recreated the user list that will be send to all clients + * + */ + private void updateUserInfoList() { + List newUserInfoList = new ArrayList<>(); + for (User user : UserManager.instance.getUsers()) { + newUserInfoList.add(new UserView( + user.getName(), + user.getHost(), + user.getSessionId(), + user.getConnectionTime(), + user.getLastActivity(), + user.getGameInfo(), + user.getUserState().toString(), + user.getChatLockedUntil(), + user.getClientVersion(), + user.getEmail(), + user.getUserIdStr() + )); + } + userInfoList = newUserInfoList; + } + + public List getUserInfoList() { + return userInfoList; } public void handleException(Exception ex) { diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java index 4c4f116a2a6..100190f24ae 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -27,6 +27,12 @@ */ package mage.server.game; +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import mage.MageException; import mage.cards.decks.DeckCardLists; import mage.constants.TableState; @@ -48,13 +54,6 @@ import mage.view.TableView; import mage.view.UsersView; import org.apache.log4j.Logger; -import java.io.Serializable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - /** * @author BetaSteward_at_googlemail.com */ @@ -107,26 +106,28 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { matchView = matchList; List users = new ArrayList<>(); for (User user : UserManager.instance.getUsers()) { - try { - users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), - user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(), - user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo(), - user.getUserData().getGeneralRating(), user.getUserData().getConstructedRating(), - user.getUserData().getLimitedRating())); - } 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() : "", - user.getMatchHistory() != null ? user.getMatchHistory() : "", - user.getMatchQuitRatio(), - user.getTourneyHistory() != null ? user.getTourneyHistory() : "", - user.getTourneyQuitRatio(), - "[exception]", - user.getPingInfo() != null ? user.getPingInfo() : "", - user.getUserData() != null ? user.getUserData().getGeneralRating() : 0, - user.getUserData() != null ? user.getUserData().getConstructedRating() : 0, - user.getUserData() != null ? user.getUserData().getLimitedRating() : 0)); + if (user.getUserState() != User.UserState.Offline && !user.getName().equals("Admin")) { + try { + users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), + user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(), + user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo(), + user.getUserData().getGeneralRating(), user.getUserData().getConstructedRating(), + user.getUserData().getLimitedRating())); + } 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() : "", + user.getMatchHistory() != null ? user.getMatchHistory() : "", + user.getMatchQuitRatio(), + user.getTourneyHistory() != null ? user.getTourneyHistory() : "", + user.getTourneyQuitRatio(), + "[exception]", + user.getPingInfo() != null ? user.getPingInfo() : "", + user.getUserData() != null ? user.getUserData().getGeneralRating() : 0, + user.getUserData() != null ? user.getUserData().getConstructedRating() : 0, + user.getUserData() != null ? user.getUserData().getLimitedRating() : 0)); + } } } diff --git a/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java new file mode 100644 index 00000000000..27c266242e0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalanWanderingKnight.java @@ -0,0 +1,124 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.EquippedMultipleSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author Saga + */ +public class BalanWanderingKnight extends CardImpl { + + private static final String rule = "{this} has double strike as long as two or more Equipment are attached to it."; + + public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT, SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First Strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it. + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), EquippedMultipleSourceCondition.instance, rule); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // {1}{W}: Attach all Equipment you control to Balan. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BalanWanderingKnightEffect(), new ManaCostsImpl("{1}{W}"))); + } + + public BalanWanderingKnight(final BalanWanderingKnight card) { + super(card); + } + + @Override + public BalanWanderingKnight copy() { + return new BalanWanderingKnight(this); + } + + static class BalanWanderingKnightEffect extends OneShotEffect { + + public BalanWanderingKnightEffect() { + super(Outcome.Benefit); + this.staticText = "Attach all Equipment you control to {this}."; + } + + public BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) { + super(effect); + } + + @Override + public BalanWanderingKnightEffect copy() { + return new BalanWanderingKnightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent balan = game.getPermanent(source.getSourceId()); + if (balan != null) { + FilterPermanent filter = new FilterPermanent(); + filter.add(new SubtypePredicate(SubType.EQUIPMENT)); + for (Permanent equipment : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(),game)) { + if (equipment != null) { + //If an Equipment can’t equip, it isn’t attached, and it doesn’t become unattached (if it’s attached to a creature). + if (!balan.cantBeAttachedBy(equipment, game)) { + balan.addAttachment(equipment.getId(), game); + } + } + } + return true; + } + return false; + } + } +} diff --git a/Mage.Sets/src/mage/cards/b/BloodswornSteward.java b/Mage.Sets/src/mage/cards/b/BloodswornSteward.java new file mode 100644 index 00000000000..a4c7aebca38 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodswornSteward.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CommanderPredicate; + +/** + * + * @author Saga + */ +public class BloodswornSteward extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent("Commander creatures"); + static { + filter.add(new CommanderPredicate()); + } + + public BloodswornSteward(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}{R}"); + this.subtype.add(SubType.VAMPIRE, SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Commander creatures you control get +2/+2 and have haste. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 2, Duration.WhileOnBattlefield, filter)); + Effect effect = new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter); + effect.setText("and have haste"); + ability.addEffect(effect); + this.addAbility(ability); + } + + public BloodswornSteward(final BloodswornSteward card) { + super(card); + } + + @Override + public BloodswornSteward copy() { + return new BloodswornSteward(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java b/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java new file mode 100644 index 00000000000..137ec3a0364 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrimsonHonorGuard.java @@ -0,0 +1,113 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.CommanderPredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class CrimsonHonorGuard extends CardImpl { + + public CrimsonHonorGuard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add("Vampire"); + this.subtype.add("Knight"); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // At the beginning of each player's end step, Crimson Honor Guard deals 4 damage to that player unless he or she controls a commander. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new CrimsonHonorGuardEffect(), TargetController.ANY, false)); + } + + public CrimsonHonorGuard(final CrimsonHonorGuard card) { + super(card); + } + + @Override + public CrimsonHonorGuard copy() { + return new CrimsonHonorGuard(this); + } +} + +class CrimsonHonorGuardEffect extends OneShotEffect { + + private final static FilterPermanent filter = new FilterPermanent("Commander"); + + static { + filter.add(new CommanderPredicate()); + } + + public CrimsonHonorGuardEffect() { + super(Outcome.Damage); + } + + @Override + public Effect copy() { + return new CrimsonHonorGuardEffect(); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + if (player != null) { + int numCommanders = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + if (numCommanders == 0) { + player.damage(4, source.getSourceId(), game, false, true); + return true; + } + } + return false; + } + + @Override + public String getText(Mode mode) { + return "{this} deals 4 damage to that player unless he or she controls a commander"; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java new file mode 100644 index 00000000000..664360080e7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java @@ -0,0 +1,98 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TransformedPredicate; + + +/** + * + * @author Saga + */ +public class GrimlockDinobotLeader extends CardImpl{ + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Dinosaurs and Vehicles"); + static { + filter.add(Predicates.or(new SubtypePredicate(SubType.DINOSAUR), new SubtypePredicate(SubType.VEHICLE))); + } + + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("Transformers creatures"); + static { + filter2.add(Predicates.not(new SubtypePredicate(SubType.DINOSAUR))); + filter2.add(Predicates.not(new SubtypePredicate(SubType.VEHICLE))); + filter2.add(Predicates.or(new AbilityPredicate(TransformAbility.class), new TransformedPredicate())); + } + + public GrimlockDinobotLeader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT,CardType.CREATURE}, "{1}{R}{G}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.AUTOBOT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + this.transformable = true; + this.secondSideCardClazz = GrimlockFerociousKing.class; + + // Dinosaurs, Vehicles and other Transformers creatures you control get +2/+0. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 0, Duration.WhileOnBattlefield, filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 0, Duration.WhileOnBattlefield, filter2, true))); + + // {2}: Grimlock, Dinobot Leader becomes Grimlock, Ferocious King. + this.addAbility(new TransformAbility()); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(true), new ManaCostsImpl("{2}"))); + } + + public GrimlockDinobotLeader(final GrimlockDinobotLeader card) { + super(card); + } + + @Override + public GrimlockDinobotLeader copy() { + return new GrimlockDinobotLeader(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java new file mode 100644 index 00000000000..7b301c4f980 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrimlockFerociousKing.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; + +/** + * + * @author Saga + */ +public class GrimlockFerociousKing extends CardImpl{ + + public GrimlockFerociousKing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},""); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + this.color.setRed(true); + this.color.setGreen(true); + this.color.setWhite(true); + + this.transformable = true; + this.nightCard = true; + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // {2}: Grimlock, Ferocious King becomes Grimlock, Dinobot Leader. + this.addAbility(new TransformAbility()); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(false), new ManaCostsImpl("{2}"))); + } + + public GrimlockFerociousKing(final GrimlockFerociousKing card) { + super(card); + } + + @Override + public GrimlockFerociousKing copy() { + return new GrimlockFerociousKing(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java new file mode 100644 index 00000000000..f5e49c19bbb --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java @@ -0,0 +1,123 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.h; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionAllOfChosenSubtypeEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ChosenSubtypePredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author Saga + */ +public class HeraldsHorn extends CardImpl { + + public HeraldsHorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + + // As Herald's Horn enters the battlefield, choose a creature type. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseCreatureTypeEffect(Outcome.BoostCreature))); + + // Creature spells you cast of the chosen type cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostReductionAllOfChosenSubtypeEffect(new FilterCreatureCard("Creature spells you cast of the chosen type"), 1))); + + // At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new HeraldsHornEffect(), TargetController.YOU, false)); + } + + public HeraldsHorn(final HeraldsHorn card) { + super(card); + } + + @Override + public HeraldsHorn copy() { + return new HeraldsHorn(this); + } +} + +class HeraldsHornEffect extends OneShotEffect { + + public HeraldsHornEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand"; + } + + public HeraldsHornEffect(final HeraldsHornEffect effect) { + super(effect); + } + + @Override + public HeraldsHornEffect copy() { + return new HeraldsHornEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = game.getObject(source.getSourceId()); + + // Look at the top card of your library. + if (controller.getLibrary().hasCards()) { + Card card = controller.getLibrary().getFromTop(game); + Cards cards = new CardsImpl(card); + controller.lookAtCards(sourceObject.getIdName(), cards, game); + + // If it's a creature card of the chosen type, you may reveal it and put it into your hand. + FilterCreatureCard filter = new FilterCreatureCard("creature card of the chosen type"); + filter.add(new ChosenSubtypePredicate(source.getSourceId())); + String message = "Reveal the top card of your library and put that card into your hand?"; + if (card != null) { + if (filter.match(card, game) && controller.chooseUse(Outcome.Benefit, message, source, game)) { + controller.moveCards(card, Zone.HAND, source, game); + controller.revealCards(sourceObject.getIdName() + " put into hand", cards, game); + } + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HungryLynx.java b/Mage.Sets/src/mage/cards/h/HungryLynx.java new file mode 100644 index 00000000000..492052b883e --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HungryLynx.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability;import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.permanent.token.DeathtouchRatToken; +import mage.target.Target; +import mage.target.common.TargetOpponent; + +/** + * + * @author Saga + */ +public class HungryLynx extends CardImpl { + + private final static FilterControlledCreaturePermanent filterCat = new FilterControlledCreaturePermanent("Cats"); + static { + filterCat.add(new SubtypePredicate(SubType.CAT)); + } + + private static final FilterCard filterProRat = new FilterCard("Rats"); + static { + filterProRat.add(new SubtypePredicate(SubType.RAT)); + } + + private final static FilterCreaturePermanent filterRat = new FilterCreaturePermanent("a Rat"); + static { + filterRat.add(new SubtypePredicate(SubType.RAT)); + } + + public HungryLynx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); + this.subtype.add(SubType.CAT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Cats you control have protection from Rats. + Effect effect = new GainAbilityAllEffect(new ProtectionAbility(filterProRat), Duration.WhileOnBattlefield, filterCat); + effect.setText("Cats you control have protection from Rats. (They can't be blocked, targeted, or dealt damage by Rats.)"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch. + Ability ability = new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new CreateTokenTargetEffect(new DeathtouchRatToken()), TargetController.YOU, null, false); + Target target = new TargetOpponent(); + ability.addTarget(target); + this.addAbility(ability); + + // Whenever a Rat dies, put a +1/+1 counter on each Cat you control. + Effect effect2 = new AddCountersAllEffect(CounterType.P1P1.createInstance(), filterCat); + effect2.setText("put a +1/+1 counter on each Cat you control"); + this.addAbility(new DiesCreatureTriggeredAbility(effect2, false, filterRat)); + } + + public HungryLynx(final HungryLynx card) { + super(card); + } + + @Override + public HungryLynx copy() { + return new HungryLynx(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java b/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java new file mode 100644 index 00000000000..7936a680836 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NazahnReveredBladesmith.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.n; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandOrOnBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.permanent.EquippedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author spjspj + */ +public class NazahnReveredBladesmith extends CardImpl { + + private static final FilterControlledCreaturePermanent equippedFilter = new FilterControlledCreaturePermanent("equipped creatures you control"); + + static { + equippedFilter.add(new EquippedPredicate()); + equippedFilter.add(new ControllerPredicate(TargetController.YOU)); + } + + private static final FilterCard filter = new FilterCard("Equipment card"); + + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + filter.add(new SubtypePredicate(SubType.EQUIPMENT)); + } + + public NazahnReveredBladesmith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{W}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add("Cat"); + this.subtype.add("Artificer"); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // When Nazahn, Revered Bladesmith enters the battlefield, search your library for an Equipment card and reveal it. If you reveal a card named Hammer of Nazahn this way, put it onto the battlefield. Otherwise, put that card into your hand. Then shuffle your library. + TargetCardInLibrary target = new TargetCardInLibrary(1, 1, filter); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandOrOnBattlefieldEffect(target, true, true, "Hammer of Nazahn"), true)); + + // Whenever an equipped creature you control attacks, you may tap target creature defending player controls. + Ability ability = new AttacksCreatureYouControlTriggeredAbility(new NazahnTapEffect(), false, equippedFilter, true); + ability.addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature defending player controls"))); + this.addAbility(ability); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof AttacksCreatureYouControlTriggeredAbility) { + FilterCreaturePermanent filterDefender = new FilterCreaturePermanent("creature defending player controls"); + for (Effect effect : ability.getEffects()) { + if (effect instanceof NazahnTapEffect) { + filterDefender.add(new ControllerIdPredicate(game.getCombat().getDefendingPlayerId(effect.getTargetPointer().getFirst(game, ability), game))); + break; + } + } + ability.getTargets().clear(); + TargetCreaturePermanent target = new TargetCreaturePermanent(filterDefender); + ability.addTarget(target); + } + } + + public NazahnReveredBladesmith(final NazahnReveredBladesmith card) { + super(card); + } + + @Override + public NazahnReveredBladesmith copy() { + return new NazahnReveredBladesmith(this); + } +} + +class NazahnTapEffect extends TapTargetEffect { + + NazahnTapEffect() { + super(); + } + + NazahnTapEffect(final NazahnTapEffect effect) { + super(effect); + } + + @Override + public NazahnTapEffect copy() { + return new NazahnTapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + permanent.tap(game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java new file mode 100644 index 00000000000..110c064487d --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -0,0 +1,189 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.p; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent.EventType; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author spjspj + */ +public class PatronOfTheVein extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public PatronOfTheVein(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add("Vampire"); + this.subtype.add("Shaman"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Patron of the Vein enters the battlefield, destroy target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), false); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + + // Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control. + Ability ability2 = new PatronOfTheVeinCreatureDiesTriggeredAbility(); + this.addAbility(ability2); + } + + public PatronOfTheVein(final PatronOfTheVein card) { + super(card); + } + + @Override + public PatronOfTheVein copy() { + return new PatronOfTheVein(this); + } +} + +class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { + + public PatronOfTheVeinCreatureDiesTriggeredAbility() { + super(Zone.BATTLEFIELD, new PatronOfTheVeinExileCreatureEffect(), false); + } + + public PatronOfTheVeinCreatureDiesTriggeredAbility(final PatronOfTheVeinCreatureDiesTriggeredAbility ability) { + super(ability); + } + + @Override + public PatronOfTheVeinCreatureDiesTriggeredAbility copy() { + return new PatronOfTheVeinCreatureDiesTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((ZoneChangeEvent) event).isDiesEvent()) { + if (game.getOpponents(this.controllerId).contains(event.getPlayerId())) { + Card creature = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (creature != null && creature.isCreature()) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(creature.getId())); + } + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control"; + } +} + +class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(new SubtypePredicate(SubType.VAMPIRE)); + } + + public PatronOfTheVeinExileCreatureEffect() { + super(Outcome.Benefit); + this.staticText = "exile it and put a +1/+1 counter on each Vampire you control"; + } + + public PatronOfTheVeinExileCreatureEffect(final PatronOfTheVeinExileCreatureEffect effect) { + super(effect); + } + + @Override + public PatronOfTheVeinExileCreatureEffect copy() { + return new PatronOfTheVeinExileCreatureEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); + + if (card != null) { + Effect effect = new ExileTargetEffect(); + effect.setTargetPointer(new FixedTarget(card.getId())); + effect.apply(game, source); + } + + for (Permanent permanent : game.getState().getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + game.informPlayers(sourceObject.getName() + ": Put a +1/+1 counter on " + permanent.getLogName()); + } + return true; + + } +} diff --git a/Mage.Sets/src/mage/cards/p/PistonSledge.java b/Mage.Sets/src/mage/cards/p/PistonSledge.java index d0ceccd9c54..3f49583cac3 100644 --- a/Mage.Sets/src/mage/cards/p/PistonSledge.java +++ b/Mage.Sets/src/mage/cards/p/PistonSledge.java @@ -28,7 +28,6 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -42,9 +41,12 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** * * @author Viserion, North @@ -53,6 +55,10 @@ public class PistonSledge extends CardImpl { private static FilterControlledPermanent filter = new FilterControlledPermanent("an artifact"); + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + } + public PistonSledge (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); this.subtype.add("Equipment"); diff --git a/Mage.Sets/src/mage/cards/q/QasaliSlingers.java b/Mage.Sets/src/mage/cards/q/QasaliSlingers.java new file mode 100644 index 00000000000..90a8d7a4fdf --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QasaliSlingers.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.q; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +/** + * + * @author spjspj + */ +public class QasaliSlingers extends CardImpl { + + public QasaliSlingers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add("Cat"); + this.subtype.add("Warrior"); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Qasali Slingers or another Cat enters the battlefield under your control, you may destroy target artifact or enchantment. + this.addAbility(new QasaliSlingersTriggeredAbility()); + } + + public QasaliSlingers(final QasaliSlingers card) { + super(card); + } + + @Override + public QasaliSlingers copy() { + return new QasaliSlingers(this); + } +} + +class QasaliSlingersTriggeredAbility extends TriggeredAbilityImpl { + + public QasaliSlingersTriggeredAbility() { + super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); + this.addTarget(new TargetPermanent(StaticFilters.ARTIFACT_OR_ENCHANTMENT_PERMANENT)); + } + + public QasaliSlingersTriggeredAbility(final QasaliSlingersTriggeredAbility ability) { + super(ability); + } + + @Override + public QasaliSlingersTriggeredAbility copy() { + return new QasaliSlingersTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + if (permanent.getId().equals(this.getSourceId())) { + return true; + } + if (permanent.hasSubtype(SubType.CAT, game) && permanent.getControllerId().equals(this.getControllerId())) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} or another Cat enters the battlefield under your control, you may destroy target artifact or enchantment."; + } +} diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 19f7ddeb0c3..04b278dcc5b 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -47,7 +47,15 @@ public class Commander2017 extends ExpansionSet { super("Commander 2017 Edition", "C17", ExpansionSet.buildDate(2017, 8, 25), SetType.SUPPLEMENTAL); this.blockName = "Command Zone"; + cards.add(new SetCardInfo("Balan, Wandering Knight", 2, Rarity.RARE, mage.cards.b.BalanWanderingKnight.class)); + cards.add(new SetCardInfo("Bloodsworn Steward", 22, Rarity.RARE, mage.cards.b.BloodswornSteward.class)); + cards.add(new SetCardInfo("Herald's Horn", 53, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class)); + cards.add(new SetCardInfo("Hungry Lynx", 31, Rarity.RARE, mage.cards.h.HungryLynx.class)); + cards.add(new SetCardInfo("Crimson Honor Guard", 23, Rarity.RARE, mage.cards.c.CrimsonHonorGuard.class)); + cards.add(new SetCardInfo("Nazahn, Revered Bladesmith", 44, Rarity.MYTHIC, mage.cards.n.NazahnReveredBladesmith.class)); cards.add(new SetCardInfo("O-Kagachi, Vengeful Kami", 45, Rarity.MYTHIC, mage.cards.o.OKagachiVengefulKami.class)); + cards.add(new SetCardInfo("Patron of the Vein", 20, Rarity.RARE, mage.cards.p.PatronOfTheVein.class)); + cards.add(new SetCardInfo("Qasali Slingers", 33, Rarity.RARE, mage.cards.q.QasaliSlingers.class)); cards.add(new SetCardInfo("Ramos, Dragon Engine", 55, Rarity.MYTHIC, mage.cards.r.RamosDragonEngine.class)); cards.add(new SetCardInfo("Scalelord Reckoner", 6, Rarity.RARE, mage.cards.s.ScalelordReckoner.class)); cards.add(new SetCardInfo("Taigam, Ojutai Master", 46, Rarity.MYTHIC, mage.cards.t.TaigamOjutaiMaster.class)); @@ -55,6 +63,6 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Teferi's Protection", 8, Rarity.RARE, mage.cards.t.TeferisProtection.class)); cards.add(new SetCardInfo("Traverse the Outlands", 34, Rarity.RARE, mage.cards.t.TraverseTheOutlands.class)); cards.add(new SetCardInfo("Wasitora, Nekoru Queen", 49, Rarity.MYTHIC, mage.cards.w.WasitoraNekoruQueen.class)); - + } } diff --git a/Mage.Sets/src/mage/sets/HasconPromo2017.java b/Mage.Sets/src/mage/sets/HasconPromo2017.java index 7b837618266..ad69eeeff86 100644 --- a/Mage.Sets/src/mage/sets/HasconPromo2017.java +++ b/Mage.Sets/src/mage/sets/HasconPromo2017.java @@ -44,6 +44,8 @@ public class HasconPromo2017 extends ExpansionSet { private HasconPromo2017() { super("HASCON Promo 2017", "H17", ExpansionSet.buildDate(2017, 9, 8), SetType.JOKESET); + cards.add(new ExpansionSet.SetCardInfo("Grimlock, Dinobot Leader", 1, Rarity.MYTHIC, mage.cards.g.GrimlockDinobotLeader.class)); + cards.add(new ExpansionSet.SetCardInfo("Grimlock, Ferocious King", 1, Rarity.MYTHIC, mage.cards.g.GrimlockFerociousKing.class)); cards.add(new ExpansionSet.SetCardInfo("Sword of Dungeons & Dragons", 3, Rarity.MYTHIC, mage.cards.s.SwordOfDungeonsAndDragons.class)); } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java new file mode 100644 index 00000000000..35249427434 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/EquippedMultipleSourceCondition.java @@ -0,0 +1,69 @@ +/* + * 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.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * Describes condition when creature is equipped with more than one Equipment. + * + * @author Saga + */ +public enum EquippedMultipleSourceCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); + int countEquipped = 0; + if (permanent != null) { + for (UUID uuid : permanent.getAttachments()) { + Permanent attached = game.getBattlefield().getPermanent(uuid); + if (attached != null && attached.getSubtype(game).contains("Equipment")) { + countEquipped++; + if (countEquipped >= 2) { + return true; + } + } + } + } + return false; + } + + @Override + public String toString() { + return "has multiple Equipments attached"; + } + +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java new file mode 100644 index 00000000000..4366cd0c8c8 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java @@ -0,0 +1,159 @@ +/* + * 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.abilities.effects.common.search; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.SearchEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.util.CardUtil; + +/** + * + * @author LokiX, BetaSteward_at_googlemail.com (spjspj) + */ +public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect { + + private boolean revealCards = false; + private boolean forceShuffle; + private String rulePrefix; + private String nameToPutOnBattlefield = null; + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, String nameToPutOnBattlefield) { + this(target, false, true, nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, String nameToPutOnBattlefield) { + this(target, revealCards, true, nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, boolean forceShuffle, String nameToPutOnBattlefield) { + this(target, revealCards, forceShuffle, "search your library for ", nameToPutOnBattlefield); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(TargetCardInLibrary target, boolean revealCards, boolean forceShuffle, String rulePrefix, String nameToPutOnBattlefield) { + super(target, Outcome.DrawCard); + this.revealCards = revealCards; + this.forceShuffle = forceShuffle; + this.rulePrefix = rulePrefix; + this.nameToPutOnBattlefield = nameToPutOnBattlefield; + setText(); + } + + public SearchLibraryPutInHandOrOnBattlefieldEffect(final SearchLibraryPutInHandOrOnBattlefieldEffect effect) { + super(effect); + this.revealCards = effect.revealCards; + this.forceShuffle = effect.forceShuffle; + this.rulePrefix = effect.rulePrefix; + this.nameToPutOnBattlefield = effect.nameToPutOnBattlefield; + } + + @Override + public SearchLibraryPutInHandOrOnBattlefieldEffect copy() { + return new SearchLibraryPutInHandOrOnBattlefieldEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + target.clearChosen(); + if (controller.searchLibrary(target, game)) { + if (!target.getTargets().isEmpty()) { + Cards cards = new CardsImpl(); + boolean askToPutOntoBf = false; + Card cardToPutOnBf = null; + for (UUID cardId : target.getTargets()) { + Card card = game.getCard(cardId); + if (card != null) { + if (card.getName().equals(nameToPutOnBattlefield)) { + askToPutOntoBf = true; + cardToPutOnBf = card; + } else { + cards.add(card); + } + } + } + if (askToPutOntoBf && cardToPutOnBf != null) { + if (controller.chooseUse(Outcome.PutCardInPlay, "Put " + cardToPutOnBf.getLogName() + " onto the battlefield instead?", source, game)) { + controller.moveCards(cards, Zone.BATTLEFIELD, source, game); + } else { + controller.moveCards(cards, Zone.HAND, source, game); + } + } else { + controller.moveCards(cards, Zone.HAND, source, game); + } + + if (revealCards) { + String name = "Reveal"; + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null) { + name = sourceCard.getIdName(); + } + controller.revealCards(name, cards, game); + } + } + controller.shuffleLibrary(source, game); + return true; + } + if (forceShuffle) { + controller.shuffleLibrary(source, game); + } + return false; + } + + private void setText() { + StringBuilder sb = new StringBuilder(); + sb.append(rulePrefix); + if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { + sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); + sb.append(target.getTargetName()).append(revealCards ? ", reveal them," : "").append(" and put them into your hand"); + } else { + sb.append("a ").append(target.getTargetName()).append(revealCards ? ", reveal it," : "").append(" and put that card into your hand"); + } + if (nameToPutOnBattlefield != null) { + sb.append(". If you reveal a card named " + nameToPutOnBattlefield + " you may put it onto the battlefield instead"); + } + if (forceShuffle) { + sb.append(". Then shuffle your library"); + } else { + sb.append(". If you do, shuffle your library"); + } + staticText = sb.toString(); + } + +} diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index d636f37c137..1bb17a3a628 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -57,6 +57,7 @@ public enum SubType { ATOG("Atog", SubTypeSet.CreatureType, false), ATAT("AT-AT", SubTypeSet.CreatureType, true), AUROCHS("Aurochs", SubTypeSet.CreatureType, false), + AUTOBOT("Autobot", SubTypeSet.CreatureType, true), // H17, Grimlock AVATAR("Avatar", SubTypeSet.CreatureType, false), // B BADGER("Badger", SubTypeSet.CreatureType, false), @@ -100,6 +101,7 @@ public enum SubType { DEMON("Demon", SubTypeSet.CreatureType, false), DESERTER("Deserter", SubTypeSet.CreatureType, false), DEVIL("Devil", SubTypeSet.CreatureType, false), + DINOSAUR("Dinosaur", SubTypeSet.CreatureType, true), // only Grimlock right now, until Ixalan DJINN("Djinn", SubTypeSet.CreatureType, false), DRAGON("Dragon", SubTypeSet.CreatureType, false), DRAKE("Drake", SubTypeSet.CreatureType, false), diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java new file mode 100644 index 00000000000..a3b5ae63e93 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/TransformedPredicate.java @@ -0,0 +1,49 @@ +/* + * 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.filter.predicate.permanent; + +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author Saga + */ +public class TransformedPredicate implements Predicate { + + @Override + public boolean apply(Permanent input, Game game) { + return input.isTransformed(); + } + + @Override + public String toString() { + return "Transformed" ; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java b/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java new file mode 100644 index 00000000000..7f52a9aaaad --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/DeathtouchRatToken.java @@ -0,0 +1,51 @@ +/* +* 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.game.permanent.token; +import mage.constants.CardType; +import mage.MageInt; +import mage.abilities.keyword.DeathtouchAbility; +import mage.constants.SubType; + +/** + * + * @author Saga + */ +public class DeathtouchRatToken extends Token { + + public DeathtouchRatToken() { + super("Rat", "1/1 black Rat creature token with deathtouch"); + this.setExpansionSetCodeForImage("C17"); + this.cardType.add(CardType.CREATURE); + this.color.setBlack(true); + this.subtype.add(SubType.RAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.addAbility(DeathtouchAbility.getInstance()); + } +}